Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] media: mtk-jpeg: only init/cancel work for multi-core variants
From: Icenowy Zheng @ 2026-06-01  7:32 UTC (permalink / raw)
  To: Bin Liu, Mauro Carvalho Chehab, Matthias Brugger,
	AngeloGioacchino Del Regno, Nicolas Dufresne, Fan Wu,
	Hans Verkuil, irui wang, kyrie wu
  Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
	Icenowy Zheng, stable

Single-core variants of this hardware do not use the work at all, and
the worker function is set to NULL, which leads to warnings when
cancelling the work in release callback.

Skip the work init/cancel code when the JPEG hardware isn't multi-core.

Cc: stable@vger.kernel.org
Fixes: 34c519feef3e ("media: mtk-jpeg: fix use-after-free in release path due to uncancelled work")
Fixes: d40e95274925 ("media: mtk-jpeg: reconstructs the initialization mode of worker")
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
 drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
index 8c684756d5fc2..83e54a7ef49c0 100644
--- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
+++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
@@ -1160,7 +1160,8 @@ static int mtk_jpeg_open(struct file *file)
 		goto free;
 	}
 
-	INIT_WORK(&ctx->jpeg_work, jpeg->variant->jpeg_worker);
+	if (jpeg->variant->multi_core)
+		INIT_WORK(&ctx->jpeg_work, jpeg->variant->jpeg_worker);
 	INIT_LIST_HEAD(&ctx->dst_done_queue);
 	spin_lock_init(&ctx->done_queue_lock);
 	v4l2_fh_init(&ctx->fh, vfd);
@@ -1202,7 +1203,8 @@ static int mtk_jpeg_release(struct file *file)
 	struct mtk_jpeg_dev *jpeg = video_drvdata(file);
 	struct mtk_jpeg_ctx *ctx = mtk_jpeg_file_to_ctx(file);
 
-	cancel_work_sync(&ctx->jpeg_work);
+	if (jpeg->variant->multi_core)
+		cancel_work_sync(&ctx->jpeg_work);
 	mutex_lock(&jpeg->lock);
 	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
 	v4l2_ctrl_handler_free(&ctx->ctrl_hdl);
-- 
2.52.0



^ permalink raw reply related

* Re: [PATCH] media: mtk-jpeg: fix use-after-free in release path due to uncancelled work
From: Icenowy Zheng @ 2026-06-01  7:03 UTC (permalink / raw)
  To: Fan Wu, bin.liu, mchehab, matthias.bgg, angelogioacchino.delregno
  Cc: irui.wang, kyrie.wu, hverkuil, linux-media, linux-kernel,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260304031934.147483-1-fanwu01@zju.edu.cn>

在 2026-03-04三的 03:19 +0000,Fan Wu写道:
> The mtk_jpeg_release() function frees the context structure (ctx)
> without
> first cancelling any pending or running work in ctx->jpeg_work. This
> creates a race window where the workqueue callback may still be
> accessing
> the context memory after it has been freed.

This patch leads to frequent kernel warning on my mt8188-geralt-ciri
device:

```
[   42.493013] ------------[ cut here ]------------
[   42.497645] WARNING: kernel/workqueue.c:4302 at
__flush_work+0x34c/0x380, CPU#2: wireplumber/1100
[   42.506521] Modules linked in: snd_seq_dummy snd_hrtimer snd_seq
snd_seq_device aes_ce_ccm rfcomm hid_himax(OE) bnep mtk_vcodec_dec_hw
btusb memconsole_coreboot btmtk btrtl btbcm vpd_sysfs cbmem
nf_conntrack_netbios_ns memconsole framebuffer_coreboot btintel
nf_conntrack_broadcast nft_fib_inet bluetooth nft_fib_ipv4 nft_fib_ipv6
nft_fib mt7921e nft_reject_inet mt7921_common nf_reject_ipv4
nf_reject_ipv6 mt792x_lib nft_reject mt76_connac_lib mt76 nft_ct
mtk_jpeg mt8188_mt6359 snd_soc_hdmi_codec mtk_vcodec_dec airoha_eth
mt6359_accdet v4l2_vp9 airoha_npu mtk_jpeg_enc_hw mtk_vcodec_enc
snd_soc_mt6359 v4l2_h264 mac80211 nft_chain_nat mtk_vcodec_dbgfs
mtk_jpeg_dec_hw mtk_vcodec_common mtk_vpu cfg80211 ip6table_nat
mtk_mdp3 ip6table_mangle v4l2_mem2mem ip6table_raw ofpart
videobuf2_dma_contig dsa_core videobuf2_memops phylink
ip6table_security hsr cmdlinepart videobuf2_v4l2 videobuf2_common
iptable_nat nf_nat bridge videodev cros_ec_sensors spi_nor nf_conntrack
stp mc cros_ec_sensors_core llc mtd
[   42.506764]  industrialio_triggered_buffer kfifo_buf nf_defrag_ipv6
snd_sof_mt8186 rfkill mtk_adsp_common nf_defrag_ipv4 iptable_mangle
mtk_adsp_ipc snd_soc_es8326 snd_soc_max98390 iptable_raw
snd_sof_xtensa_dsp mediatek_cpufreq_hw iptable_security snd_sof_of
snd_soc_mt8188_afe snd_sof snd_soc_dmic snd_soc_mtk_common
snd_sof_utils mt6359_auxadc snd_soc_core snd_compress ac97_bus mtk_scp
mt6577_auxadc snd_pcm_dmaengine snd_pcm mtk_rpmsg lvts_thermal
nf_tables mtk_scp_ipi snd_timer industrialio rpmsg_core snd
ip6table_filter coreboot_table soundcore ip6_tables iptable_filter
ip_tables x_tables joydev mousedev qrtr tun sch_fq_codel fuse nfnetlink
onboard_usb_dev panel_himax_hx83102 cros_usbpd_charger cros_ec_hwmon
cros_ec_sysfs cros_ec_sensorhub cros_usbpd_notify sbs_battery
gpio_cros_ec hwmon cros_charge_control cros_ec_chardev
cros_usbpd_logger cros_ec_debugfs hid_google_hammer hid_multitouch
hid_vivaldi_common cros_ec_keyb i2c_cros_ec_tunnel hid_generic
led_class cros_ec_dev rtc_mt6397 mt6359_regulator
[   42.594635]  tpm_tis_i2c_cr50 xhci_mtk_hcd i2c_hid_of tpm_tis_spi
i2c_hid tpm_tis_core cros_ec_spi cros_ec clk_mt8188_vdo1
clk_mt8188_vdo0 cros_ec_proto mt6397 clk_mt8188_vpp0 clk_mt8188_vpp1
mtk_dp ghash_ce phy_mtk_dp drm_dp_aux_bus phy_mtk_tphy sm4 mediatek_drm
drm_dma_helper drm_display_helper cec clk_mt8188_ipe clk_mt8188_venc
rc_core clk_mt8188_vdec clk_mt8188_cam clk_mt8188_img clk_mt8188_ccu
clk_mt8188_wpe panfrost mtk_mmsys mtk_mutex governor_simpleondemand
phy_mtk_mipi_dsi_drv clk_mt8188_mfg mtk_cmdq_helper nvmem_mtk_efuse
gpu_sched mtk_smi spi_mtk_nor mtu3 clk_mt8188_imp_iic_wrap mtk_sd roles
mmc_hsq udc_core cqhci spmi_mtk_pmif i2c_mt65xx spmi_devres
pwm_mtk_disp spi_mt65xx clk_mt8188_peri_ao clk_mt8188_adsp_audio26m
clk_mt8188_infra_ao spmi clk_mt8188_apmixedsys mtk_pmic_wrap mtk_wdt
mtk_adsp_mailbox mtk_iommu clk_mt8188_topckgen mtk_cmdq_mailbox fixed
ramoops pwm_bl i2c_dev aes_neon_bs aes_neon_blk aes_ce_blk
[   42.765118] CPU: 2 UID: 975 PID: 1100 Comm: wireplumber Tainted: G 
W  OE       7.0.10-aosc-main #1 PREEMPT(lazy) 
[   42.775983] Tainted: [W]=WARN, [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
[   42.782155] Hardware name: Google Ciri sku1 board (DT)
[   42.787286] pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS
BTYPE=--)
[   42.794243] pc : __flush_work+0x34c/0x380
[   42.798251] lr : __cancel_work_sync+0x90/0xe0
[   42.802604] sp : ffff8000854f3980
[   42.805912] x29: ffff8000854f39e0 x28: ffff0000c9530000 x27:
0000000000000000
[   42.813048] x26: 0000000000000000 x25: 0000000000000000 x24:
0000000000000001
[   42.820183] x23: ffffb0d11de2bd40 x22: ffff0000c98db6c0 x21:
0000000000000001
[   42.827319] x20: 0000000000000000 x19: ffff0000c2679f20 x18:
ffff8000851ad068
[   42.834454] x17: 0000000000000000 x16: 0000000000000000 x15:
0000000000000000
[   42.841588] x14: 0000000000000000 x13: 0000000000000000 x12:
0000000000000000
[   42.848723] x11: 0000000000000000 x10: 0000000000000000 x9 :
ffffb0d11993da28
[   42.855858] x8 : 0000000000000000 x7 : 0000000000000000 x6 :
0000000000000000
[   42.862992] x5 : 0000000000000000 x4 : 0000000000000000 x3 :
0000000000000000
[   42.870126] x2 : 0000000000000000 x1 : 0000000000000000 x0 :
0000000000000000
[   42.877262] Call trace:
[   42.879704]  __flush_work+0x34c/0x380 (P)
[   42.883713]  __cancel_work_sync+0x90/0xe0
[   42.887719]  cancel_work_sync+0x20/0x50
[   42.891551]  mtk_jpeg_release+0x38/0xb0 [mtk_jpeg]
[   42.896348]  v4l2_release+0x94/0x118 [videodev]
[   42.900910]  __fput+0xec/0x330
[   42.903968]  fput_close_sync+0x44/0x160
[   42.907803]  __arm64_sys_close+0x44/0xa0
[   42.911723]  invoke_syscall.constprop.0+0x60/0xf0
[   42.916426]  el0_svc_common.constprop.0+0x114/0x140
[   42.921302]  do_el0_svc+0x28/0x58
[   42.924614]  el0_svc+0x44/0x1e0
[   42.927754]  el0t_64_sync_handler+0xc0/0x108
[   42.932019]  el0t_64_sync+0x1b8/0x1c0
[   42.935680] ---[ end trace 0000000000000000 ]---

```

Thanks,
Icenowy

> 
> Race condition:
> 
>     CPU 0 (release)                    CPU 1 (workqueue)
>     ----------------                   ------------------
>     close()
>       mtk_jpeg_release()
>                                        mtk_jpegenc_worker()
>                                          ctx = work->data
>                                          // accessing ctx
> 
>         kfree(ctx)  // freed!
>                                          access ctx  // UAF!
> 
> The work is queued via queue_work() during JPEG encode/decode
> operations
> (via mtk_jpeg_device_run). If the device is closed while work is
> pending
> or running, the work handler will access freed memory.
> 
> Fix this by calling cancel_work_sync() BEFORE acquiring the mutex.
> This
> ordering is critical: if cancel_work_sync() is called after
> mutex_lock(),
> and the work handler also tries to acquire the same mutex, it would
> cause
> a deadlock.
> 
> Note: The open error path does NOT need cancel_work_sync() because
> INIT_WORK() only initializes the work structure - it does not
> schedule
> it. Work is only scheduled later during ioctl operations.
> 
> Fixes: 5fb1c2361e56 ("mtk-jpegenc: add jpeg encode worker interface")
> Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
> ---
>  drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
> b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
> index c01124a349f6..8c684756d5fc 100644
> --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
> +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c
> @@ -1202,6 +1202,7 @@ static int mtk_jpeg_release(struct file *file)
>  	struct mtk_jpeg_dev *jpeg = video_drvdata(file);
>  	struct mtk_jpeg_ctx *ctx = mtk_jpeg_file_to_ctx(file);
>  
> +	cancel_work_sync(&ctx->jpeg_work);
>  	mutex_lock(&jpeg->lock);
>  	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
>  	v4l2_ctrl_handler_free(&ctx->ctrl_hdl);


^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: lorenzo @ 2026-06-01  5:56 UTC (permalink / raw)
  To: Ryder Lee
  Cc: Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	Chui-hao Chiu (邱垂浩),
	AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
	linux-wireless@vger.kernel.org, Sean Wang,
	Bo Jiao (焦波), linux-mediatek@lists.infradead.org,
	matthias.bgg@gmail.com, linux-arm-kernel@lists.infradead.org
In-Reply-To: <7f02be7c4f919413718a0218b3792d4b0a222ca3.camel@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 10719 bytes --]

On May 31, Ryder Lee wrote:
> On Sun, 2026-05-31 at 15:12 +0200, lorenzo@kernel.org wrote:
> > On May 31, Ryder Lee wrote:
> > > On Sun, 2026-05-31 at 14:11 +0200, lorenzo@kernel.org wrote:
> > > > > On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> > > > > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored
> > > > > > at
> > > > > > info->status.status_driver_data via memset(). Since info-
> > > > > > >control
> > > > > > and
> > > > > > info->status are members of the same union in
> > > > > > ieee80211_tx_info,
> > > > > > this overwrites info->control.flags.
> > > > > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is
> > > > > > called
> > > > > > before
> > > > > > mt7996_mac_write_txwi(), which re-reads info->control.flags
> > > > > > to
> > > > > > extract
> > > > > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been
> > > > > > zeroed,
> > > > > > the
> > > > > > link_id always resolves to 0 for frames using global_wcid,
> > > > > > leading to
> > > > > > incorrect TXWI configuration.
> > > > > > Fix this by passing link_id as an explicit parameter to
> > > > > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the
> > > > > > link_id
> > > > > > is
> > > > > > already extracted from info->control.flags before the
> > > > > > destructive
> > > > > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > > > > discovery
> > > > > > callers in mcu.c, use link_conf->link_id directly.
> > > > > > 
> > > > > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > > > > mt7996_mac_write_txwi() for MLO support")
> > > > > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > > > > ---
> > > > > > Changes in v2:
> > > > > > - Do not use link_id in mt7996_mac_write_txwi if it is
> > > > > > IEEE80211_LINK_UNSPECIFIED
> > > > > > - In mt7996_mac_write_txwi() rely on link_id calculated in
> > > > > >   mt7996_tx_prepare_skb().
> > > > > > - Link to v1:
> > > > > > https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
> > > > > >  
> > > > > > ---
> > > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14
> > > > > > ++++----
> > > > > > ----
> > > > > > --
> > > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++-
> > > > > > -
> > > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
> > > > > >  3 files changed, 9 insertions(+), 13 deletions(-)
> > > > > > 
> > > > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > > index c98446057282..95b3078d9667 100644
> > > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct
> > > > > > mt7996_dev
> > > > > > *dev, __le32 *txwi,
> > > > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32
> > > > > > *txwi,
> > > > > >  			   struct sk_buff *skb, struct
> > > > > > mt76_wcid
> > > > > > *wcid,
> > > > > >  			   struct ieee80211_key_conf *key,
> > > > > > int
> > > > > > pid,
> > > > > > -			   enum mt76_txq_id qid, u32
> > > > > > changed)
> > > > > > +			   enum mt76_txq_id qid, u32
> > > > > > changed,
> > > > > > +			   unsigned int link_id)
> > > > > >  {
> > > > > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr
> > > > > > *)skb-
> > > > > > > data;
> > > > > >  	struct ieee80211_tx_info *info =
> > > > > > IEEE80211_SKB_CB(skb);
> > > > > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct
> > > > > > mt7996_dev
> > > > > > *dev, __le32 *txwi,
> > > > > >  	bool is_8023 = info->flags &
> > > > > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > > > > >  	struct mt76_vif_link *mlink = NULL;
> > > > > >  	struct mt7996_vif *mvif;
> > > > > > -	unsigned int link_id;
> > > > > >  	u16 tx_count = 15;
> > > > > >  	u32 val;
> > > > > >  	bool inband_disc = !!(changed &
> > > > > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > > > > @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct
> > > > > > mt7996_dev
> > > > > > *dev, __le32 *txwi,
> > > > > >  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
> > > > > >  				   
> > > > > > BSS_CHANGED_BEACON_ENABLED))
> > > > > > &&
> > > > > > (!inband_disc);
> > > > > >  
> > > > > > -	if (wcid != &dev->mt76.global_wcid)
> > > > > > -		link_id = wcid->link_id;
> > > > > > -	else
> > > > > > -		link_id = u32_get_bits(info->control.flags,
> > > > > > -				      
> > > > > > IEEE80211_TX_CTRL_MLO_LINK);
> > > > > > -
> > > > > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv :
> > > > > > NULL;
> > > > > >  	if (mvif) {
> > > > > >  		if (wcid->offchannel)
> > > > > >  			mlink = rcu_dereference(mvif-
> > > > > > > mt76.offchannel_link);
> > > > > > -		if (!mlink)
> > > > > > +		if (!mlink && link_id !=
> > > > > > IEEE80211_LINK_UNSPECIFIED)
> > > > > >  			mlink = rcu_dereference(mvif-
> > > > > > > mt76.link[link_id]);
> > > > > >  	}
> > > > > >  
> > > > > > @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct
> > > > > > mt76_dev
> > > > > > *mdev, void *txwi_ptr,
> > > > > >  	/* Transmit non qos data by 802.11 header and need
> > > > > > to
> > > > > > fill
> > > > > > txd by host*/
> > > > > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > > > > >  		mt7996_mac_write_txwi(dev, txwi_ptr,
> > > > > > tx_info-
> > > > > > > skb,
> > > > > > wcid, key,
> > > > > > -				      pid, qid, 0);
> > > > > > +				      pid, qid, 0, link_id);
> > > > > >  
> > > > > >  	/* MT7996 and MT7992 require driver to provide the
> > > > > > MAC
> > > > > > TXP
> > > > > > for AddBA
> > > > > >  	 * req
> > > > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > > index 8be40d60ad29..a14c63438923 100644
> > > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct
> > > > > > mt7996_dev
> > > > > > *dev,
> > > > > >  
> > > > > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > > > > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > > NULL,
> > > > > > 0, 0,
> > > > > > -			      BSS_CHANGED_BEACON);
> > > > > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > > > > link_id);
> > > > > >  
> > > > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > > > >  }
> > > > > > @@ -3249,7 +3249,8 @@ int
> > > > > > mt7996_mcu_beacon_inband_discov(struct
> > > > > > mt7996_dev *dev,
> > > > > >  
> > > > > >  	buf = (u8 *)tlv + sizeof(*discov);
> > > > > >  
> > > > > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > > NULL,
> > > > > > 0, 0, changed);
> > > > > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > > NULL,
> > > > > > 0, 0,
> > > > > > +			      changed, link_conf->link_id);
> > > > > >  
> > > > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > > > >  
> > > > > > diff --git
> > > > > > a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct
> > > > > > mt7996_dev
> > > > > > *dev,
> > > > > > u8 band);
> > > > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32
> > > > > > *txwi,
> > > > > >  			   struct sk_buff *skb, struct
> > > > > > mt76_wcid
> > > > > > *wcid,
> > > > > >  			   struct ieee80211_key_conf *key,
> > > > > > int
> > > > > > pid,
> > > > > > -			   enum mt76_txq_id qid, u32
> > > > > > changed);
> > > > > > +			   enum mt76_txq_id qid, u32
> > > > > > changed,
> > > > > > +			   unsigned int link_id);
> > > > > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > > > > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > > > > >  void mt7996_mac_work(struct work_struct *work);
> > > > > > 
> > > > > > ---
> > > > > > base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> > > > > > change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-
> > > > > > 85818a9bb31f
> > > > > > 
> > > > > > Best regards,
> > > > > > 
> > > > > > 
> > > > > We might expand flags further so this still doesn't solve the
> > > > > issue
> > > > > of
> > > > > flags being cleared - it only works for MLO flag. And the
> > > > > developers
> > > > > still won't easily notice that the flags are being cleared.
> > > > 
> > > > My opinion is we should consider just upstream code and then
> > > > change
> > > > it as soon
> > > > as you post this new feature upstream, but I will let Felix
> > > > comments
> > > > on it.
> > > > Moreover, the proposed approach aligns link_id used in
> > > > mt7996_tx_prepare_skb()
> > > > to the one used in mt7996_mac_write_txwi() and fix a possible OOB
> > > > bug
> > > > in
> > > > mt7996_mac_write_txwi().
> > > > 
> > > > Regards,
> > > > Lorenzo
> > > > 
> > > > > 
> > > 
> > > Just to tie in with this patch subject - I'm just thinking of a way
> > > to
> > > solve this once and for all. If the problem is reading zeroed info-
> > > > control.flags, wouldn't it be better to just pass a u32 flags,
> > > something like this:
> > > 
> > > u32 flags = info->control.flags
> > > 
> > > mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0,
> > > 			      changed, flags);
> > > 
> > > We can use all flags then.
> > 
> > what about link_id? Should it be the same between
> > mt7996_tx_prepare_skb()
> > and mt7996_mac_write_txwi()?
> > 
> > 
>  
> I mean the link_id is only corresponds to one specific flags bit of
> mac80211_tx_control_flags. But there are other bits that aren't
> handled. Wouldn't u32 flags make it more cleaner?

Yes, I got your point, but my concern is if we need to sync link_id between
mt7996_tx_prepare_skb() and mt7996_mac_write_txwi(). If so, I guess it is
much better to pass link_id explicitly to mt7996_mac_write_txwi() since it
does not just depended on mac80211_tx_control_flags and I think we should
not duplicate the logic in mt7996_mac_write_txwi(). Got my point?
If in the future (not required now) we need to pass mac80211_tx_control_flags
to mt7996_mac_write_txwi(), we will do it easily.

Regards,
Lorenzo

> 
> Ryder
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: Ryder Lee @ 2026-05-31 19:52 UTC (permalink / raw)
  To: lorenzo@kernel.org
  Cc: Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	Chui-hao Chiu (邱垂浩),
	AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
	linux-wireless@vger.kernel.org, Sean Wang,
	Bo Jiao (焦波), linux-mediatek@lists.infradead.org,
	matthias.bgg@gmail.com, linux-arm-kernel@lists.infradead.org
In-Reply-To: <ahwzwHDF7fT9t7hP@lore-desk>

On Sun, 2026-05-31 at 15:12 +0200, lorenzo@kernel.org wrote:
> On May 31, Ryder Lee wrote:
> > On Sun, 2026-05-31 at 14:11 +0200, lorenzo@kernel.org wrote:
> > > > On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> > > > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored
> > > > > at
> > > > > info->status.status_driver_data via memset(). Since info-
> > > > > >control
> > > > > and
> > > > > info->status are members of the same union in
> > > > > ieee80211_tx_info,
> > > > > this overwrites info->control.flags.
> > > > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is
> > > > > called
> > > > > before
> > > > > mt7996_mac_write_txwi(), which re-reads info->control.flags
> > > > > to
> > > > > extract
> > > > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been
> > > > > zeroed,
> > > > > the
> > > > > link_id always resolves to 0 for frames using global_wcid,
> > > > > leading to
> > > > > incorrect TXWI configuration.
> > > > > Fix this by passing link_id as an explicit parameter to
> > > > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the
> > > > > link_id
> > > > > is
> > > > > already extracted from info->control.flags before the
> > > > > destructive
> > > > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > > > discovery
> > > > > callers in mcu.c, use link_conf->link_id directly.
> > > > > 
> > > > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > > > mt7996_mac_write_txwi() for MLO support")
> > > > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > > > ---
> > > > > Changes in v2:
> > > > > - Do not use link_id in mt7996_mac_write_txwi if it is
> > > > > IEEE80211_LINK_UNSPECIFIED
> > > > > - In mt7996_mac_write_txwi() rely on link_id calculated in
> > > > >   mt7996_tx_prepare_skb().
> > > > > - Link to v1:
> > > > > https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
> > > > >  
> > > > > ---
> > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14
> > > > > ++++----
> > > > > ----
> > > > > --
> > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++-
> > > > > -
> > > > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
> > > > >  3 files changed, 9 insertions(+), 13 deletions(-)
> > > > > 
> > > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > index c98446057282..95b3078d9667 100644
> > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct
> > > > > mt7996_dev
> > > > > *dev, __le32 *txwi,
> > > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32
> > > > > *txwi,
> > > > >  			   struct sk_buff *skb, struct
> > > > > mt76_wcid
> > > > > *wcid,
> > > > >  			   struct ieee80211_key_conf *key,
> > > > > int
> > > > > pid,
> > > > > -			   enum mt76_txq_id qid, u32
> > > > > changed)
> > > > > +			   enum mt76_txq_id qid, u32
> > > > > changed,
> > > > > +			   unsigned int link_id)
> > > > >  {
> > > > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr
> > > > > *)skb-
> > > > > > data;
> > > > >  	struct ieee80211_tx_info *info =
> > > > > IEEE80211_SKB_CB(skb);
> > > > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct
> > > > > mt7996_dev
> > > > > *dev, __le32 *txwi,
> > > > >  	bool is_8023 = info->flags &
> > > > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > > > >  	struct mt76_vif_link *mlink = NULL;
> > > > >  	struct mt7996_vif *mvif;
> > > > > -	unsigned int link_id;
> > > > >  	u16 tx_count = 15;
> > > > >  	u32 val;
> > > > >  	bool inband_disc = !!(changed &
> > > > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > > > @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct
> > > > > mt7996_dev
> > > > > *dev, __le32 *txwi,
> > > > >  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
> > > > >  				   
> > > > > BSS_CHANGED_BEACON_ENABLED))
> > > > > &&
> > > > > (!inband_disc);
> > > > >  
> > > > > -	if (wcid != &dev->mt76.global_wcid)
> > > > > -		link_id = wcid->link_id;
> > > > > -	else
> > > > > -		link_id = u32_get_bits(info->control.flags,
> > > > > -				      
> > > > > IEEE80211_TX_CTRL_MLO_LINK);
> > > > > -
> > > > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv :
> > > > > NULL;
> > > > >  	if (mvif) {
> > > > >  		if (wcid->offchannel)
> > > > >  			mlink = rcu_dereference(mvif-
> > > > > > mt76.offchannel_link);
> > > > > -		if (!mlink)
> > > > > +		if (!mlink && link_id !=
> > > > > IEEE80211_LINK_UNSPECIFIED)
> > > > >  			mlink = rcu_dereference(mvif-
> > > > > > mt76.link[link_id]);
> > > > >  	}
> > > > >  
> > > > > @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct
> > > > > mt76_dev
> > > > > *mdev, void *txwi_ptr,
> > > > >  	/* Transmit non qos data by 802.11 header and need
> > > > > to
> > > > > fill
> > > > > txd by host*/
> > > > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > > > >  		mt7996_mac_write_txwi(dev, txwi_ptr,
> > > > > tx_info-
> > > > > > skb,
> > > > > wcid, key,
> > > > > -				      pid, qid, 0);
> > > > > +				      pid, qid, 0, link_id);
> > > > >  
> > > > >  	/* MT7996 and MT7992 require driver to provide the
> > > > > MAC
> > > > > TXP
> > > > > for AddBA
> > > > >  	 * req
> > > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > index 8be40d60ad29..a14c63438923 100644
> > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct
> > > > > mt7996_dev
> > > > > *dev,
> > > > >  
> > > > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > > > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > NULL,
> > > > > 0, 0,
> > > > > -			      BSS_CHANGED_BEACON);
> > > > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > > > link_id);
> > > > >  
> > > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > > >  }
> > > > > @@ -3249,7 +3249,8 @@ int
> > > > > mt7996_mcu_beacon_inband_discov(struct
> > > > > mt7996_dev *dev,
> > > > >  
> > > > >  	buf = (u8 *)tlv + sizeof(*discov);
> > > > >  
> > > > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > NULL,
> > > > > 0, 0, changed);
> > > > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > > NULL,
> > > > > 0, 0,
> > > > > +			      changed, link_conf->link_id);
> > > > >  
> > > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > > >  
> > > > > diff --git
> > > > > a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct
> > > > > mt7996_dev
> > > > > *dev,
> > > > > u8 band);
> > > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32
> > > > > *txwi,
> > > > >  			   struct sk_buff *skb, struct
> > > > > mt76_wcid
> > > > > *wcid,
> > > > >  			   struct ieee80211_key_conf *key,
> > > > > int
> > > > > pid,
> > > > > -			   enum mt76_txq_id qid, u32
> > > > > changed);
> > > > > +			   enum mt76_txq_id qid, u32
> > > > > changed,
> > > > > +			   unsigned int link_id);
> > > > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > > > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > > > >  void mt7996_mac_work(struct work_struct *work);
> > > > > 
> > > > > ---
> > > > > base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> > > > > change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-
> > > > > 85818a9bb31f
> > > > > 
> > > > > Best regards,
> > > > > 
> > > > > 
> > > > We might expand flags further so this still doesn't solve the
> > > > issue
> > > > of
> > > > flags being cleared - it only works for MLO flag. And the
> > > > developers
> > > > still won't easily notice that the flags are being cleared.
> > > 
> > > My opinion is we should consider just upstream code and then
> > > change
> > > it as soon
> > > as you post this new feature upstream, but I will let Felix
> > > comments
> > > on it.
> > > Moreover, the proposed approach aligns link_id used in
> > > mt7996_tx_prepare_skb()
> > > to the one used in mt7996_mac_write_txwi() and fix a possible OOB
> > > bug
> > > in
> > > mt7996_mac_write_txwi().
> > > 
> > > Regards,
> > > Lorenzo
> > > 
> > > > 
> > 
> > Just to tie in with this patch subject - I'm just thinking of a way
> > to
> > solve this once and for all. If the problem is reading zeroed info-
> > > control.flags, wouldn't it be better to just pass a u32 flags,
> > something like this:
> > 
> > u32 flags = info->control.flags
> > 
> > mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0,
> > 			      changed, flags);
> > 
> > We can use all flags then.
> 
> what about link_id? Should it be the same between
> mt7996_tx_prepare_skb()
> and mt7996_mac_write_txwi()?
> 
> 
 
I mean the link_id is only corresponds to one specific flags bit of
mac80211_tx_control_flags. But there are other bits that aren't
handled. Wouldn't u32 flags make it more cleaner?

Ryder



^ permalink raw reply

* [PATCH] regulator: dt-bindings: mt6311: Convert to DT schema
From: Ninad Naik @ 2026-05-31 16:57 UTC (permalink / raw)
  To: lgirdwood, broonie, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-mediatek, me,
	linux-kernel-mentees, skhan, Ninad Naik

Convert mediatek,mt6311 to DT schema.

Signed-off-by: Ninad Naik <ninadnaik07@gmail.com>
---
 .../regulator/mediatek,mt6311-regulator.yaml  | 72 +++++++++++++++++++
 .../bindings/regulator/mt6311-regulator.txt   | 35 ---------
 2 files changed, 72 insertions(+), 35 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/regulator/mediatek,mt6311-regulator.yaml
 delete mode 100644 Documentation/devicetree/bindings/regulator/mt6311-regulator.txt

diff --git a/Documentation/devicetree/bindings/regulator/mediatek,mt6311-regulator.yaml b/Documentation/devicetree/bindings/regulator/mediatek,mt6311-regulator.yaml
new file mode 100644
index 000000000000..a51db46b0f41
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/mediatek,mt6311-regulator.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/regulator/mediatek,mt6311-regulator.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Mediatek MT6311 Regulator
+
+maintainers:
+  - AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+
+description: |
+  The MediaTek MT6311 is an I2C power management IC that provides one step-down
+  converter and one low-dropout regulator. The regulators are named VDVFS and
+  VBIASN, respectively.
+
+properties:
+  compatible:
+    const: mediatek,mt6311-regulator
+
+  reg:
+    description: I2C slave address.
+    maxItems: 1
+
+  regulators:
+    type: object
+    description: List of regulators provided by this controller.
+
+    patternProperties:
+      "^(VDVFS|VBIASN)$":
+        type: object
+        $ref: regulator.yaml#
+        description: |
+          Regulator nodes.
+        unevaluatedProperties: false
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - regulators
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      mt6311: pmic@6b {
+        compatible = "mediatek,mt6311-regulator";
+        reg = <0x6b>;
+
+        regulators {
+          mt6311_vcpu_reg: VDVFS {
+            regulator-name = "VDVFS";
+            regulator-min-microvolt = <600000>;
+            regulator-max-microvolt = <1400000>;
+            regulator-ramp-delay = <10000>;
+          };
+
+          mt6311_ldo_reg: VBIASN {
+            regulator-name = "VBIASN";
+            regulator-min-microvolt = <200000>;
+            regulator-max-microvolt = <800000>;
+          };
+        };
+      };
+    };
+...
diff --git a/Documentation/devicetree/bindings/regulator/mt6311-regulator.txt b/Documentation/devicetree/bindings/regulator/mt6311-regulator.txt
deleted file mode 100644
index 84d544d8c1b1..000000000000
--- a/Documentation/devicetree/bindings/regulator/mt6311-regulator.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-Mediatek MT6311 Regulator
-
-Required properties:
-- compatible: "mediatek,mt6311-regulator"
-- reg: I2C slave address, usually 0x6b.
-- regulators: List of regulators provided by this controller. It is named
-  to VDVFS and VBIASN.
-  The definition for each of these nodes is defined using the standard binding
-  for regulators at Documentation/devicetree/bindings/regulator/regulator.txt.
-
-The valid names for regulators are:
-BUCK:
-  VDVFS
-LDO:
-  VBIASN
-
-Example:
-	mt6311: pmic@6b {
-		compatible = "mediatek,mt6311-regulator";
-		reg = <0x6b>;
-
-		regulators {
-			mt6311_vcpu_reg: VDVFS {
-				regulator-name = "VDVFS";
-				regulator-min-microvolt = < 600000>;
-				regulator-max-microvolt = <1400000>;
-				regulator-ramp-delay = <10000>;
-			};
-			mt6311_ldo_reg: VBIASN {
-				regulator-name = "VBIASN";
-				regulator-min-microvolt = <200000>;
-				regulator-max-microvolt = <800000>;
-			};
-		};
-	};
-- 
2.54.0



^ permalink raw reply related

* [PATCH] pinctrl: mediatek: mt8167: Fix Schmitt trigger register offset of pins 34-39
From: Luca Leonardo Scorcia @ 2026-05-31 16:23 UTC (permalink / raw)
  To: linux-mediatek
  Cc: Luca Leonardo Scorcia, Sean Wang, Linus Walleij, Matthias Brugger,
	AngeloGioacchino Del Regno, Fabien Parent, linux-gpio,
	linux-kernel, linux-arm-kernel

The correct Schmitt trigger register offset for pins 34-39 is 0xA00. Value
was verified with SoC data sheet.

Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
Fixes: 82d70627e94a ("pinctrl: mediatek: Add MT8167 Pinctrl driver")
---
 drivers/pinctrl/mediatek/pinctrl-mt8167.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8167.c b/drivers/pinctrl/mediatek/pinctrl-mt8167.c
index 143c26622272..c812d614e9d4 100644
--- a/drivers/pinctrl/mediatek/pinctrl-mt8167.c
+++ b/drivers/pinctrl/mediatek/pinctrl-mt8167.c
@@ -244,7 +244,7 @@ static const struct mtk_pin_ies_smt_set mt8167_smt_set[] = {
 	MTK_PIN_IES_SMT_SPEC(24, 25, 0xA00, 12),
 	MTK_PIN_IES_SMT_SPEC(26, 30, 0xA00, 0),
 	MTK_PIN_IES_SMT_SPEC(31, 33, 0xA00, 1),
-	MTK_PIN_IES_SMT_SPEC(34, 39, 0xA900, 2),
+	MTK_PIN_IES_SMT_SPEC(34, 39, 0xA00, 2),
 	MTK_PIN_IES_SMT_SPEC(40, 40, 0xA10, 11),
 	MTK_PIN_IES_SMT_SPEC(41, 43, 0xA00, 10),
 	MTK_PIN_IES_SMT_SPEC(44, 47, 0xA00, 11),
-- 
2.43.0



^ permalink raw reply related

* [PATCH] pinctrl: mediatek: mt8516: Fix Schmitt trigger register offset of pins 34-39
From: Luca Leonardo Scorcia @ 2026-05-31 16:22 UTC (permalink / raw)
  To: linux-mediatek
  Cc: Luca Leonardo Scorcia, Sean Wang, Linus Walleij, Matthias Brugger,
	AngeloGioacchino Del Regno, Fabien Parent, linux-gpio,
	linux-kernel, linux-arm-kernel

The correct Schmitt trigger register offset for pins 34-39 is 0xA00.

Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
Fixes: 264667112ef0 ("pinctrl: mediatek: Add MT8516 Pinctrl driver")
---
 drivers/pinctrl/mediatek/pinctrl-mt8516.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8516.c b/drivers/pinctrl/mediatek/pinctrl-mt8516.c
index abda75d4354e..68d6638e7f4b 100644
--- a/drivers/pinctrl/mediatek/pinctrl-mt8516.c
+++ b/drivers/pinctrl/mediatek/pinctrl-mt8516.c
@@ -244,7 +244,7 @@ static const struct mtk_pin_ies_smt_set mt8516_smt_set[] = {
 	MTK_PIN_IES_SMT_SPEC(24, 25, 0xA00, 12),
 	MTK_PIN_IES_SMT_SPEC(26, 30, 0xA00, 0),
 	MTK_PIN_IES_SMT_SPEC(31, 33, 0xA00, 1),
-	MTK_PIN_IES_SMT_SPEC(34, 39, 0xA900, 2),
+	MTK_PIN_IES_SMT_SPEC(34, 39, 0xA00, 2),
 	MTK_PIN_IES_SMT_SPEC(40, 40, 0xA10, 11),
 	MTK_PIN_IES_SMT_SPEC(41, 43, 0xA00, 10),
 	MTK_PIN_IES_SMT_SPEC(44, 47, 0xA00, 11),
-- 
2.43.0



^ permalink raw reply related

* [PATCH nf-next v3 6/6] selftests: netfilter: nft_flowtable.sh: Add SIT flowtable selftest
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Similar to IPIP, IP6IP6 and IPv4 over IPv6, introduce specific selftest
for SIT flowtable sw acceleration in nft_flowtable.sh

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 tools/testing/selftests/net/netfilter/config       |  1 +
 .../selftests/net/netfilter/nft_flowtable.sh       | 45 ++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/tools/testing/selftests/net/netfilter/config b/tools/testing/selftests/net/netfilter/config
index 979cff56e1f5..c46604574653 100644
--- a/tools/testing/selftests/net/netfilter/config
+++ b/tools/testing/selftests/net/netfilter/config
@@ -30,6 +30,7 @@ CONFIG_IP_SCTP=m
 CONFIG_IPV6=y
 CONFIG_IPV6_MULTIPLE_TABLES=y
 CONFIG_IPV6_TUNNEL=m
+CONFIG_IPV6_SIT=m
 CONFIG_IP_VS=m
 CONFIG_IP_VS_PROTO_TCP=y
 CONFIG_IP_VS_RR=m
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 219339dbaf6e..6527e27b9121 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -597,6 +597,10 @@ ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6
 ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad
 ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
 
+ip -net "$nsr1" link add name sit1 type sit local 192.168.10.1 remote 192.168.10.2 ttl 255
+ip -net "$nsr1" link set sit1 up
+ip -net "$nsr1" addr add fe01:3::1/64 dev sit1 nodad
+
 ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1
 ip -net "$nsr2" link set tun0 up
 ip -net "$nsr2" addr add 192.168.100.2/24 dev tun0
@@ -608,6 +612,10 @@ ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6
 ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad
 ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
 
+ip -net "$nsr2" link add name sit1 type sit local 192.168.10.2 remote 192.168.10.1 ttl 255
+ip -net "$nsr2" link set sit1 up
+ip -net "$nsr2" addr add fe01:3::2/64 dev sit1 nodad
+
 ip -net "$nsr1" route change default via 192.168.100.2
 ip -net "$nsr2" route change default via 192.168.100.1
 
@@ -622,6 +630,7 @@ ip -6 -net "$ns2" route add default via dead:2::1
 
 ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept'
 ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1 accept'
 ip netns exec "$nsr1" nft -a insert rule inet filter forward \
 	'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
 
@@ -648,6 +657,19 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP6IP4 tunnel"; then
 	ret=1
 fi
 
+ip -6 -net "$nsr1" route delete default
+ip -6 -net "$nsr1" route add default via fe01:3::2
+ip -6 -net "$nsr2" route delete default
+ip -6 -net "$nsr2" route add default via fe01:3::1
+
+if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
+	echo "PASS: flow offload for ns1/ns2 SIT tunnel"
+else
+	echo "FAIL: flow offload for ns1/ns2 with SIT tunnel" 1>&2
+	ip netns exec "$nsr1" nft list ruleset
+	ret=1
+fi
+
 # Create vlan tagged devices for IPIP traffic.
 ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10
 ip -net "$nsr1" link set veth1.10 up
@@ -672,6 +694,11 @@ ip -6 -net "$nsr1" route delete default
 ip -6 -net "$nsr1" route add default via fee1:5::2
 ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
 
+ip -net "$nsr1" link add name sit1.10 type sit local 192.168.20.1 remote 192.168.20.2 ttl 255
+ip -net "$nsr1" link set sit1.10 up
+ip -net "$nsr1" addr add fe01:5::1/64 dev sit1.10 nodad
+ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1.10 accept'
+
 ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10
 ip -net "$nsr2" link set veth0.10 up
 ip -net "$nsr2" addr add 192.168.20.2/24 dev veth0.10
@@ -689,6 +716,11 @@ ip -net "$nsr2" link set tun6.10 up
 ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10
 ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad
 ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
+
+ip -net "$nsr2" link add name sit1.10 type sit local 192.168.20.2 remote 192.168.20.1 ttl 255
+ip -net "$nsr2" link set sit1.10 up
+ip -net "$nsr2" addr add fe01:5::2/64 dev sit1.10 nodad
+
 ip -6 -net "$nsr2" route delete default
 ip -6 -net "$nsr2" route add default via fee1:5::1
 
@@ -715,6 +747,19 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP6IP4 tunnel over vlan"; then
 	ret=1
 fi
 
+ip -6 -net "$nsr1" route delete default
+ip -6 -net "$nsr1" route add default via fe01:5::2
+ip -6 -net "$nsr2" route delete default
+ip -6 -net "$nsr2" route add default via fe01:5::1
+
+if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
+	echo "PASS: flow offload for ns1/ns2 SIT tunnel over vlan"
+else
+	echo "FAIL: flow offload for ns1/ns2 with SIT tunnel over vlan" 1>&2
+	ip netns exec "$nsr1" nft list ruleset
+	ret=1
+fi
+
 # Restore the previous configuration
 ip -net "$nsr1" route change default via 192.168.10.2
 ip -net "$nsr2" route change default via 192.168.10.1

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 5/6] net: netfilter: Add SIT tunnel flowtable acceleration
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Introduce sw flowtable acceleration for the TX/RX paths of
SIT tunnels, relying on the netfilter flowtable infrastructure.
The feature can be tested with a forwarding scenario between two
NICs (eth0 and eth1), where a SIT tunnel is used to reach a remote
site via eth1 as the underlay device:

    ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (192.168.2.2)

[IP configuration]

6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff
    inet6 2001:db8:1::2/64 scope global nodad
       valid_lft forever preferred_lft forever
7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.1/24 scope global eth1
       valid_lft forever preferred_lft forever
8: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/sit 192.168.2.1 peer 192.168.2.2
    inet6 2001:db8:200::1/64 scope global nodad
       valid_lft forever preferred_lft forever

$ ip route show
192.168.2.0/24 dev eth1 proto kernel scope link src 192.168.2.1

$ ip -6 route show
2001:db8:1::/64 dev eth0 proto kernel metric 256 pref medium
2001:db8:200::/64 dev tun0 proto kernel metric 256 pref medium
default via 2001:db8:200::2 dev tun0 metric 1024 pref medium

$ nft list ruleset
table inet filter {
    flowtable ft {
        hook ingress priority filter
        devices = { eth0, eth1 }
    }

    chain forward {
        type filter hook forward priority filter; policy accept;
        meta l4proto { tcp, udp } flow add @ft
    }
}

When reproducing this scenario using veth interfaces, the following
results were observed:

- TCP stream received from SIT tunnel:
  - net-next (baseline):                ~118 Gbps
  - net-next + SIT flowtable support: ~148 Gbps

- TCP stream transmitted to SIT tunnel:
  - net-next (baseline):                ~131 Gbps
  - net-next + SIT flowtable support: ~147 Gbps

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 net/ipv6/sit.c                   |  26 ++++
 net/netfilter/nf_flow_table_ip.c | 299 +++++++++++++++++++++------------------
 2 files changed, 186 insertions(+), 139 deletions(-)

diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index 201347b4e127..d1d5ff385d6f 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -1362,6 +1362,31 @@ ipip6_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
 	}
 }
 
+static int ipip6_tunnel_fill_forward_path(struct net_device_path_ctx *ctx,
+					  struct net_device_path *path)
+{
+	struct ip_tunnel *tunnel = netdev_priv(ctx->dev);
+	const struct iphdr *tiph = &tunnel->parms.iph;
+	struct rtable *rt;
+
+	rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, 0, 0, 0,
+			     RT_SCOPE_UNIVERSE);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+
+	path->type = DEV_PATH_TUN;
+	path->tun.src_v4.s_addr = tiph->saddr;
+	path->tun.dst_v4.s_addr = tiph->daddr;
+	path->tun.l3_proto = IPPROTO_IPV6;
+	path->tun.encap_proto = AF_INET;
+	path->dev = ctx->dev;
+
+	ctx->dev = rt->dst.dev;
+	ip_rt_put(rt);
+
+	return 0;
+}
+
 static int
 ipip6_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
 			    void __user *data, int cmd)
@@ -1398,6 +1423,7 @@ static const struct net_device_ops ipip6_netdev_ops = {
 	.ndo_siocdevprivate = ipip6_tunnel_siocdevprivate,
 	.ndo_get_iflink = ip_tunnel_get_iflink,
 	.ndo_tunnel_ctl = ipip6_tunnel_ctl,
+	.ndo_fill_forward_path = ipip6_tunnel_fill_forward_path,
 };
 
 static void ipip6_dev_free(struct net_device *dev)
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index a2c62f6455c8..20ea480426c6 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -336,8 +336,8 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx,
 	if (iph->ttl <= 1)
 		return false;
 
-	if (iph->protocol == IPPROTO_IPIP) {
-		ctx->tun.proto = IPPROTO_IPIP;
+	if (iph->protocol == IPPROTO_IPIP || iph->protocol == IPPROTO_IPV6) {
+		ctx->tun.proto = iph->protocol;
 		ctx->tun.hdr_size = size;
 		ctx->offset += size;
 	}
@@ -468,21 +468,6 @@ static void nf_flow_encap_pop(struct nf_flowtable_ctx *ctx,
 		nf_flow_ip_tunnel_pop(ctx, skb);
 }
 
-static struct flow_offload_tuple_rhash *
-nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
-		       struct nf_flowtable *flow_table, struct sk_buff *skb)
-{
-	struct flow_offload_tuple tuple = {};
-
-	if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
-		return NULL;
-
-	if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0)
-		return NULL;
-
-	return flow_offload_lookup(flow_table, &tuple);
-}
-
 static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
 				   struct nf_flowtable *flow_table,
 				   struct flow_offload_tuple_rhash *tuplehash,
@@ -612,19 +597,33 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
 				    struct flow_offload_tuple *tuple,
 				    __be32 *ip_daddr)
 {
-	struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
 	struct rtable *rt = dst_rtable(tuple->dst_cache);
-	u8 tos = iph->tos, ttl = iph->ttl;
-	__be16 frag_off = iph->frag_off;
-	u32 headroom = sizeof(*iph);
+	__be16 frag_off = 0;
+	struct iphdr *iph;
+	u8 tos = 0, ttl;
+	u32 headroom;
 	int err;
 
+	if (tuple->tun.l3_proto == IPPROTO_IPV6) {
+		struct ipv6hdr *ip6h;
+
+		ip6h = (struct ipv6hdr *)skb_network_header(skb);
+		tos = ipv6_get_dsfield(ip6h);
+		ttl = ip6h->hop_limit;
+	} else {
+		iph = (struct iphdr *)skb_network_header(skb);
+		frag_off = iph->frag_off;
+		tos = iph->tos;
+		ttl = iph->ttl;
+	}
+
 	err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4);
 	if (err)
 		return err;
 
-	skb_set_inner_ipproto(skb, IPPROTO_IPIP);
-	headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len;
+	skb_set_inner_ipproto(skb, tuple->tun.l3_proto);
+	headroom = sizeof(*iph) + LL_RESERVED_SPACE(rt->dst.dev) +
+		   rt->dst.header_len;
 	err = skb_cow_head(skb, headroom);
 	if (err)
 		return err;
@@ -635,6 +634,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
 	/* Push down and install the IP header. */
 	skb_push(skb, sizeof(*iph));
 	skb_reset_network_header(skb);
+	skb->protocol = htons(ETH_P_IP);
 
 	iph = ip_hdr(skb);
 	iph->version	= 4;
@@ -761,18 +761,6 @@ static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb,
 	}
 }
 
-static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb,
-				  struct flow_offload_tuple *tuple,
-				  struct in6_addr **ip6_daddr,
-				  int encap_limit)
-{
-	if (tuple->tun_num)
-		return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, ip6_daddr,
-						  encap_limit);
-
-	return 0;
-}
-
 static int nf_flow_encap_push(struct sk_buff *skb,
 			      struct flow_offload_tuple *tuple,
 			      struct net_device *outdev)
@@ -870,105 +858,6 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb,
 	return NF_STOLEN;
 }
 
-unsigned int
-nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
-			const struct nf_hook_state *state)
-{
-	int encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
-	struct flow_offload_tuple_rhash *tuplehash;
-	struct nf_flowtable *flow_table = priv;
-	struct flow_offload_tuple *other_tuple;
-	enum flow_offload_tuple_dir dir;
-	struct nf_flowtable_ctx ctx = {
-		.in	= state->in,
-	};
-	struct nf_flow_xmit xmit = {};
-	struct in6_addr *ip6_daddr;
-	struct flow_offload *flow;
-	struct neighbour *neigh;
-	struct rtable *rt;
-	__be32 ip_daddr;
-	int ret;
-
-	tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
-	if (!tuplehash)
-		return NF_ACCEPT;
-
-	ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb,
-				      encap_limit);
-	if (ret < 0)
-		return NF_DROP;
-	else if (ret == 0)
-		return NF_ACCEPT;
-
-	if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
-		rt = dst_rtable(tuplehash->tuple.dst_cache);
-		memset(skb->cb, 0, sizeof(struct inet_skb_parm));
-		IPCB(skb)->iif = skb->dev->ifindex;
-		IPCB(skb)->flags = IPSKB_FORWARDED;
-		return nf_flow_xmit_xfrm(skb, state, &rt->dst);
-	}
-
-	dir = tuplehash->tuple.dir;
-	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
-	other_tuple = &flow->tuplehash[!dir].tuple;
-	ip_daddr = other_tuple->src_v4.s_addr;
-
-	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
-				&ip6_daddr, encap_limit) < 0)
-		return NF_DROP;
-
-	switch (tuplehash->tuple.xmit_type) {
-	case FLOW_OFFLOAD_XMIT_NEIGH: {
-		struct dst_entry *dst;
-
-		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
-		if (!xmit.outdev) {
-			flow_offload_teardown(flow);
-			return NF_DROP;
-		}
-		if (other_tuple->tun.encap_proto == AF_INET6 ||
-		    ctx.tun.proto == IPPROTO_IPV6) {
-			struct rt6_info *rt6;
-
-			rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
-			neigh = ip_neigh_gw6(rt6->dst.dev,
-					     rt6_nexthop(rt6, ip6_daddr));
-			dst = &rt6->dst;
-		} else {
-			rt = dst_rtable(tuplehash->tuple.dst_cache);
-			neigh = ip_neigh_gw4(rt->dst.dev,
-					     rt_nexthop(rt, ip_daddr));
-			dst = &rt->dst;
-		}
-		if (IS_ERR(neigh)) {
-			flow_offload_teardown(flow);
-			return NF_DROP;
-		}
-		xmit.dest = neigh->ha;
-		skb_dst_set_noref(skb, dst);
-		break;
-	}
-	case FLOW_OFFLOAD_XMIT_DIRECT:
-		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
-		if (!xmit.outdev) {
-			flow_offload_teardown(flow);
-			return NF_DROP;
-		}
-		xmit.dest = tuplehash->tuple.out.h_dest;
-		xmit.source = tuplehash->tuple.out.h_source;
-		break;
-	default:
-		WARN_ON_ONCE(1);
-		return NF_DROP;
-	}
-	xmit.tuple = other_tuple;
-	xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
-
-	return nf_flow_queue_xmit(state->net, skb, &xmit);
-}
-EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
-
 static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
 				 struct in6_addr *addr,
 				 struct in6_addr *new_addr,
@@ -1153,10 +1042,17 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
 	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
 
 	mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset;
-	if (flow->tuplehash[!dir].tuple.tun_num) {
+	switch (flow->tuplehash[!dir].tuple.tun.encap_proto) {
+	case AF_INET:
+		mtu -= sizeof(struct iphdr);
+		break;
+	case AF_INET6:
 		mtu -= sizeof(*ip6h);
 		if (encap_limit > 0)
 			mtu -= 8; /* encap limit option */
+		break;
+	default:
+		break;
 	}
 
 	if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
@@ -1191,6 +1087,25 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
 	return 1;
 }
 
+static struct flow_offload_tuple_rhash *
+nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
+		       struct nf_flowtable *flow_table, struct sk_buff *skb)
+{
+	struct flow_offload_tuple tuple = {};
+
+	if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
+		return NULL;
+
+	if (ctx->tun.proto == IPPROTO_IPV6) {
+		if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0)
+			return NULL;
+	} else if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) {
+		return NULL;
+	}
+
+	return flow_offload_lookup(flow_table, &tuple);
+}
+
 static struct flow_offload_tuple_rhash *
 nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
 			    struct nf_flowtable *flow_table,
@@ -1211,6 +1126,112 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
 	return flow_offload_lookup(flow_table, &tuple);
 }
 
+unsigned int
+nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
+			const struct nf_hook_state *state)
+{
+	int encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	struct flow_offload_tuple_rhash *tuplehash;
+	struct nf_flowtable *flow_table = priv;
+	struct flow_offload_tuple *other_tuple;
+	enum flow_offload_tuple_dir dir;
+	struct nf_flowtable_ctx ctx = {
+		.in	= state->in,
+	};
+	struct nf_flow_xmit xmit = {};
+	struct in6_addr *ip6_daddr;
+	struct flow_offload *flow;
+	struct neighbour *neigh;
+	struct rtable *rt;
+	__be32 ip_daddr;
+	int ret;
+
+	tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
+	if (!tuplehash)
+		return NF_ACCEPT;
+
+	if (ctx.tun.proto == IPPROTO_IPV6)
+		ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash,
+						   skb, encap_limit);
+	else
+		ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb,
+					      encap_limit);
+	if (ret < 0)
+		return NF_DROP;
+	else if (ret == 0)
+		return NF_ACCEPT;
+
+	if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
+		rt = dst_rtable(tuplehash->tuple.dst_cache);
+		memset(skb->cb, 0, sizeof(struct inet_skb_parm));
+		IPCB(skb)->iif = skb->dev->ifindex;
+		IPCB(skb)->flags = IPSKB_FORWARDED;
+		return nf_flow_xmit_xfrm(skb, state, &rt->dst);
+	}
+
+	dir = tuplehash->tuple.dir;
+	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+	other_tuple = &flow->tuplehash[!dir].tuple;
+	ip_daddr = other_tuple->src_v4.s_addr;
+	ip6_daddr = &other_tuple->src_v6;
+
+	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
+				&ip6_daddr, encap_limit) < 0)
+		return NF_DROP;
+
+	switch (tuplehash->tuple.xmit_type) {
+	case FLOW_OFFLOAD_XMIT_NEIGH: {
+		struct dst_entry *dst;
+
+		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
+		if (!xmit.outdev) {
+			flow_offload_teardown(flow);
+			return NF_DROP;
+		}
+		if (other_tuple->tun.encap_proto == AF_INET6 ||
+		    ctx.tun.proto == IPPROTO_IPV6) {
+			struct rt6_info *rt6;
+
+			rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw6(rt6->dst.dev,
+					     rt6_nexthop(rt6, ip6_daddr));
+			dst = &rt6->dst;
+		} else {
+			rt = dst_rtable(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw4(rt->dst.dev,
+					     rt_nexthop(rt, ip_daddr));
+			dst = &rt->dst;
+		}
+		if (IS_ERR(neigh)) {
+			flow_offload_teardown(flow);
+			return NF_DROP;
+		}
+
+		xmit.dest = neigh->ha;
+		skb_dst_set_noref(skb, dst);
+		break;
+	}
+	case FLOW_OFFLOAD_XMIT_DIRECT:
+		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
+		if (!xmit.outdev) {
+			flow_offload_teardown(flow);
+			return NF_DROP;
+		}
+
+		xmit.dest = tuplehash->tuple.out.h_dest;
+		xmit.source = tuplehash->tuple.out.h_source;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return NF_DROP;
+	}
+	xmit.tuple = other_tuple;
+	xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
+
+	return nf_flow_queue_xmit(state->net, skb, &xmit);
+}
+EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
+
 unsigned int
 nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 			  const struct nf_hook_state *state)
@@ -1228,6 +1249,7 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 	struct flow_offload *flow;
 	struct neighbour *neigh;
 	struct rt6_info *rt;
+	__be32 ip_daddr;
 	int ret;
 
 	tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb);
@@ -1256,10 +1278,11 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 	dir = tuplehash->tuple.dir;
 	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
 	other_tuple = &flow->tuplehash[!dir].tuple;
+	ip_daddr = other_tuple->src_v4.s_addr;
 	ip6_daddr = &other_tuple->src_v6;
 
-	if (nf_flow_tunnel_v6_push(state->net, skb, other_tuple,
-				   &ip6_daddr, encap_limit) < 0)
+	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
+				&ip6_daddr, encap_limit) < 0)
 		return NF_DROP;
 
 	switch (tuplehash->tuple.xmit_type) {
@@ -1273,10 +1296,8 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 		}
 		if (other_tuple->tun.encap_proto == AF_INET ||
 		    ctx.tun.proto == IPPROTO_IPIP) {
-			__be32 ip_daddr = other_tuple->src_v4.s_addr;
 			struct rtable *rt4;
 
-			skb->protocol = htons(ETH_P_IP);
 			rt4 = dst_rtable(tuplehash->tuple.dst_cache);
 			neigh = ip_neigh_gw4(rt4->dst.dev,
 					     rt_nexthop(rt4, ip_daddr));

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 3/6] net: netfilter: Add IPv4 over IPv6 tunnel flowtable acceleration
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Introduce sw flowtable acceleration for the TX/RX paths of
IPv4 over IPv6 tunnels, relying on the netfilter flowtable
infrastructure.
The feature can be tested with a forwarding scenario between two
NICs (eth0 and eth1), where an IPv4 over IPv6 tunnel is used to
reach a remote site via eth1 as the underlay device:

    ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (2001:db8:2::2)

[IP configuration]

6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.2/24 scope global eth0
       valid_lft forever preferred_lft forever
7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff
    inet6 2001:db8:2::1/64 scope global nodad
       valid_lft forever preferred_lft forever
8: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/tunnel6 2001:db8:2::1 peer 2001:db8:2::2 permaddr ce9c:2940:7dcc::
    inet 192.168.100.1/24 scope global tun0
       valid_lft forever preferred_lft forever

$ ip route show
default via 192.168.100.2 dev tun0
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.2
192.168.100.0/24 dev tun0 proto kernel scope link src 192.168.100.1

$ ip -6 route show
2001:db8:2::/64 dev eth1 proto kernel metric 256 pref medium

$ nft list ruleset
table inet filter {
    flowtable ft {
        hook ingress priority filter
        devices = { eth0, eth1 }
    }

    chain forward {
        type filter hook forward priority filter; policy accept;
        meta l4proto { tcp, udp } flow add @ft
    }
}

When reproducing this scenario using veth interfaces, the following
results were observed:

- TCP stream received from IPv4 over IPv6 tunnel:
  - net-next (baseline):                ~126 Gbps
  - net-next + IP6IP flowtable support: ~138 Gbps

- TCP stream transmitted to IPv4 over IPv6 tunnel:
  - net-next (baseline):                ~127 Gbps
  - net-next + IP6IP flowtable support: ~140 Gbps

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 net/netfilter/nf_flow_table_core.c |  14 +++-
 net/netfilter/nf_flow_table_ip.c   | 166 +++++++++++++++++++++++++++----------
 net/netfilter/nf_flow_table_path.c |   6 +-
 3 files changed, 133 insertions(+), 53 deletions(-)

diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 785d8c244a77..5580d28d4bcb 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -76,9 +76,11 @@ struct flow_offload *flow_offload_alloc(struct nf_conn *ct)
 }
 EXPORT_SYMBOL_GPL(flow_offload_alloc);
 
-static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple)
+static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple,
+				   u8 tun_encap_proto)
 {
-	if (flow_tuple->l3proto == NFPROTO_IPV6)
+	if (flow_tuple->l3proto == NFPROTO_IPV6 ||
+	    tun_encap_proto == NFPROTO_IPV6)
 		return rt6_get_cookie(dst_rt6_info(flow_tuple->dst_cache));
 
 	return 0;
@@ -135,10 +137,14 @@ static int flow_offload_fill_route(struct flow_offload *flow,
 		dst_release(dst);
 		break;
 	case FLOW_OFFLOAD_XMIT_XFRM:
-	case FLOW_OFFLOAD_XMIT_NEIGH:
+	case FLOW_OFFLOAD_XMIT_NEIGH: {
+		u8 encap_proto = route->tuple[!dir].in.tun.encap_proto;
+
 		flow_tuple->ifidx = route->tuple[dir].out.ifindex;
 		flow_tuple->dst_cache = dst;
-		flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple);
+		flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple,
+								 encap_proto);
+		}
 		break;
 	default:
 		WARN_ON_ONCE(1);
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 49e9b0308763..a2c62f6455c8 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -191,27 +191,27 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
 		break;
 	}
 
-	switch (inner_proto) {
-	case htons(ETH_P_IP):
-		iph = (struct iphdr *)(skb_network_header(skb) + offset);
-		if (ctx->tun.proto == IPPROTO_IPIP) {
+	if (ctx->tun.proto == IPPROTO_IPIP || ctx->tun.proto == IPPROTO_IPV6) {
+		switch (inner_proto) {
+		case htons(ETH_P_IP):
+			iph = (struct iphdr *)(skb_network_header(skb) +
+					       offset);
 			tuple->tun.dst_v4.s_addr = iph->daddr;
 			tuple->tun.src_v4.s_addr = iph->saddr;
-			tuple->tun.l3_proto = IPPROTO_IPIP;
+			tuple->tun.l3_proto = ctx->tun.proto;
 			tuple->tun.encap_proto = AF_INET;
-		}
-		break;
-	case htons(ETH_P_IPV6):
-		ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset);
-		if (ctx->tun.proto == IPPROTO_IPV6) {
+			break;
+		case htons(ETH_P_IPV6):
+			ip6h = (struct ipv6hdr *)(skb_network_header(skb) +
+						  offset);
 			tuple->tun.dst_v6 = ip6h->daddr;
 			tuple->tun.src_v6 = ip6h->saddr;
-			tuple->tun.l3_proto = IPPROTO_IPV6;
+			tuple->tun.l3_proto = ctx->tun.proto;
 			tuple->tun.encap_proto = AF_INET6;
+			break;
+		default:
+			break;
 		}
-		break;
-	default:
-		break;
 	}
 }
 
@@ -367,9 +367,9 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx,
 	if (hdrlen < 0)
 		return false;
 
-	if (nexthdr == IPPROTO_IPV6) {
+	if (nexthdr == IPPROTO_IPIP || nexthdr == IPPROTO_IPV6) {
 		ctx->tun.hdr_size = hdrlen;
-		ctx->tun.proto = IPPROTO_IPV6;
+		ctx->tun.proto = nexthdr;
 	}
 	ctx->offset += ctx->tun.hdr_size;
 
@@ -388,6 +388,10 @@ static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx,
 
 	skb_pull(skb, ctx->tun.hdr_size);
 	skb_reset_network_header(skb);
+	if (ctx->tun.proto == IPPROTO_IPIP)
+		skb->protocol = htons(ETH_P_IP);
+	else
+		skb->protocol = htons(ETH_P_IPV6);
 }
 
 static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx,
@@ -482,7 +486,7 @@ nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
 static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
 				   struct nf_flowtable *flow_table,
 				   struct flow_offload_tuple_rhash *tuplehash,
-				   struct sk_buff *skb)
+				   struct sk_buff *skb, int encap_limit)
 {
 	enum flow_offload_tuple_dir dir;
 	struct flow_offload *flow;
@@ -493,8 +497,18 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
 	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
 
 	mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset;
-	if (flow->tuplehash[!dir].tuple.tun_num)
+	switch (flow->tuplehash[!dir].tuple.tun.encap_proto) {
+	case AF_INET:
 		mtu -= sizeof(*iph);
+		break;
+	case AF_INET6:
+		mtu -= sizeof(struct ipv6hdr);
+		if (encap_limit > 0)
+			mtu -= 8; /* encap limit option */
+		break;
+	default:
+		break;
+	}
 
 	if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
 		return 0;
@@ -640,16 +654,6 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
 	return 0;
 }
 
-static int nf_flow_tunnel_v4_push(struct net *net, struct sk_buff *skb,
-				  struct flow_offload_tuple *tuple,
-				  __be32 *ip_daddr)
-{
-	if (tuple->tun_num)
-		return nf_flow_tunnel_ipip_push(net, skb, tuple, ip_daddr);
-
-	return 0;
-}
-
 struct ipv6_tel_txoption {
 	struct ipv6_txoptions ops;
 	__u8 dst_opt[8];
@@ -660,18 +664,29 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
 				      struct in6_addr **ip6_daddr,
 				      int encap_limit)
 {
-	struct ipv6hdr *ip6h = (struct ipv6hdr *)skb_network_header(skb);
-	u8 hop_limit = ip6h->hop_limit, proto = IPPROTO_IPV6;
 	struct rtable *rt = dst_rtable(tuple->dst_cache);
-	__u8 dsfield = ipv6_get_dsfield(ip6h);
+	u8 hop_limit, proto = tuple->tun.l3_proto;
 	struct flowi6 fl6 = {
 		.daddr = tuple->tun.src_v6,
 		.saddr = tuple->tun.dst_v6,
 		.flowi6_proto = proto,
 	};
+	struct ipv6hdr *ip6h;
+	__u8 dsfield;
 	int err, mtu;
 	u32 headroom;
 
+	if (tuple->tun.l3_proto == IPPROTO_IPIP) {
+		struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
+
+		dsfield = ipv4_get_dsfield(iph);
+		hop_limit = iph->ttl;
+	} else {
+		ip6h = (struct ipv6hdr *)skb_network_header(skb);
+		dsfield = ipv6_get_dsfield(ip6h);
+		hop_limit = ip6h->hop_limit;
+	}
+
 	err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6);
 	if (err)
 		return err;
@@ -707,12 +722,13 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
 
 		hopt = skb_push(skb, ipv6_optlen(opt.ops.dst1opt));
 		memcpy(hopt, opt.ops.dst1opt, ipv6_optlen(opt.ops.dst1opt));
-		hopt->nexthdr = IPPROTO_IPV6;
+		hopt->nexthdr = proto;
 		proto = NEXTHDR_DEST;
 	}
 
 	skb_push(skb, sizeof(*ip6h));
 	skb_reset_network_header(skb);
+	skb->protocol = htons(ETH_P_IPV6);
 
 	ip6h = ipv6_hdr(skb);
 	ip6_flow_hdr(ip6h, dsfield,
@@ -729,6 +745,22 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
 	return 0;
 }
 
+static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb,
+			       struct flow_offload_tuple *tuple,
+			       __be32 *ip_daddr, struct in6_addr **ip6_daddr,
+			       int encap_limit)
+{
+	switch (tuple->tun.encap_proto) {
+	case AF_INET:
+		return nf_flow_tunnel_ipip_push(net, skb, tuple, ip_daddr);
+	case AF_INET6:
+		return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, ip6_daddr,
+						  encap_limit);
+	default:
+		return 0;
+	}
+}
+
 static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb,
 				  struct flow_offload_tuple *tuple,
 				  struct in6_addr **ip6_daddr,
@@ -842,6 +874,7 @@ unsigned int
 nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
 			const struct nf_hook_state *state)
 {
+	int encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
 	struct flow_offload_tuple_rhash *tuplehash;
 	struct nf_flowtable *flow_table = priv;
 	struct flow_offload_tuple *other_tuple;
@@ -850,6 +883,7 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
 		.in	= state->in,
 	};
 	struct nf_flow_xmit xmit = {};
+	struct in6_addr *ip6_daddr;
 	struct flow_offload *flow;
 	struct neighbour *neigh;
 	struct rtable *rt;
@@ -860,7 +894,8 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
 	if (!tuplehash)
 		return NF_ACCEPT;
 
-	ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
+	ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb,
+				      encap_limit);
 	if (ret < 0)
 		return NF_DROP;
 	else if (ret == 0)
@@ -879,25 +914,41 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
 	other_tuple = &flow->tuplehash[!dir].tuple;
 	ip_daddr = other_tuple->src_v4.s_addr;
 
-	if (nf_flow_tunnel_v4_push(state->net, skb, other_tuple, &ip_daddr) < 0)
+	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
+				&ip6_daddr, encap_limit) < 0)
 		return NF_DROP;
 
 	switch (tuplehash->tuple.xmit_type) {
-	case FLOW_OFFLOAD_XMIT_NEIGH:
-		rt = dst_rtable(tuplehash->tuple.dst_cache);
+	case FLOW_OFFLOAD_XMIT_NEIGH: {
+		struct dst_entry *dst;
+
 		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
 		if (!xmit.outdev) {
 			flow_offload_teardown(flow);
 			return NF_DROP;
 		}
-		neigh = ip_neigh_gw4(rt->dst.dev, rt_nexthop(rt, ip_daddr));
+		if (other_tuple->tun.encap_proto == AF_INET6 ||
+		    ctx.tun.proto == IPPROTO_IPV6) {
+			struct rt6_info *rt6;
+
+			rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw6(rt6->dst.dev,
+					     rt6_nexthop(rt6, ip6_daddr));
+			dst = &rt6->dst;
+		} else {
+			rt = dst_rtable(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw4(rt->dst.dev,
+					     rt_nexthop(rt, ip_daddr));
+			dst = &rt->dst;
+		}
 		if (IS_ERR(neigh)) {
 			flow_offload_teardown(flow);
 			return NF_DROP;
 		}
 		xmit.dest = neigh->ha;
-		skb_dst_set_noref(skb, &rt->dst);
+		skb_dst_set_noref(skb, dst);
 		break;
+	}
 	case FLOW_OFFLOAD_XMIT_DIRECT:
 		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
 		if (!xmit.outdev) {
@@ -1150,8 +1201,12 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
 	if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IPV6)))
 		return NULL;
 
-	if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0)
+	if (ctx->tun.proto == IPPROTO_IPIP) {
+		if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0)
+			return NULL;
+	} else if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) {
 		return NULL;
+	}
 
 	return flow_offload_lookup(flow_table, &tuple);
 }
@@ -1179,8 +1234,12 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 	if (tuplehash == NULL)
 		return NF_ACCEPT;
 
-	ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb,
-					   encap_limit);
+	if (ctx.tun.proto == IPPROTO_IPIP)
+		ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb,
+					      encap_limit);
+	else
+		ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash,
+						   skb, encap_limit);
 	if (ret < 0)
 		return NF_DROP;
 	else if (ret == 0)
@@ -1204,21 +1263,38 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 		return NF_DROP;
 
 	switch (tuplehash->tuple.xmit_type) {
-	case FLOW_OFFLOAD_XMIT_NEIGH:
-		rt = dst_rt6_info(tuplehash->tuple.dst_cache);
+	case FLOW_OFFLOAD_XMIT_NEIGH: {
+		struct dst_entry *dst;
+
 		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
 		if (!xmit.outdev) {
 			flow_offload_teardown(flow);
 			return NF_DROP;
 		}
-		neigh = ip_neigh_gw6(rt->dst.dev, rt6_nexthop(rt, ip6_daddr));
+		if (other_tuple->tun.encap_proto == AF_INET ||
+		    ctx.tun.proto == IPPROTO_IPIP) {
+			__be32 ip_daddr = other_tuple->src_v4.s_addr;
+			struct rtable *rt4;
+
+			skb->protocol = htons(ETH_P_IP);
+			rt4 = dst_rtable(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw4(rt4->dst.dev,
+					     rt_nexthop(rt4, ip_daddr));
+			dst = &rt4->dst;
+		} else {
+			rt = dst_rt6_info(tuplehash->tuple.dst_cache);
+			neigh = ip_neigh_gw6(rt->dst.dev,
+					     rt6_nexthop(rt, ip6_daddr));
+			dst = &rt->dst;
+		}
 		if (IS_ERR(neigh)) {
 			flow_offload_teardown(flow);
 			return NF_DROP;
 		}
 		xmit.dest = neigh->ha;
-		skb_dst_set_noref(skb, &rt->dst);
+		skb_dst_set_noref(skb, dst);
 		break;
+	}
 	case FLOW_OFFLOAD_XMIT_DIRECT:
 		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
 		if (!xmit.outdev) {
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 4d01212d6614..ac9741f5793b 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -213,12 +213,11 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
 	struct dst_entry *tun_dst = NULL;
 	struct flowi fl = {};
 
-	switch (nft_pf(pkt)) {
+	switch (tun->encap_proto) {
 	case NFPROTO_IPV4:
 		fl.u.ip4.daddr = tun->dst_v4.s_addr;
 		fl.u.ip4.saddr = tun->src_v4.s_addr;
 		fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex;
-		fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb));
 		fl.u.ip4.flowi4_mark = pkt->skb->mark;
 		fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC;
 		break;
@@ -226,13 +225,12 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
 		fl.u.ip6.daddr = tun->dst_v6;
 		fl.u.ip6.saddr = tun->src_v6;
 		fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex;
-		fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb));
 		fl.u.ip6.flowi6_mark = pkt->skb->mark;
 		fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC;
 		break;
 	}
 
-	nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt));
+	nf_route(nft_net(pkt), &tun_dst, &fl, false, tun->encap_proto);
 	if (!tun_dst)
 		return -ENOENT;
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 4/6] selftests: netfilter: nft_flowtable.sh: Add IPv4 over IPv6 flowtable selftest
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Similar to IPIP and IP6IP6, introduce specific selftest for IPv4 over IPv6
flowtable sw acceleration in nft_flowtable.sh

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 .../selftests/net/netfilter/nft_flowtable.sh       | 33 +++++++++++++++++++---
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 7a34ef468975..219339dbaf6e 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -579,9 +579,8 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 ""; then
 	ip netns exec "$nsr1" nft list ruleset
 fi
 
-# IPIP tunnel test:
-# Add IPIP tunnel interfaces and check flowtable acceleration.
-test_ipip() {
+# IP tunnel tests:
+test_ip_tnls() {
 if ! ip -net "$nsr1" link add name tun0 type ipip \
      local 192.168.10.1 remote 192.168.10.2 >/dev/null;then
 	echo "SKIP: could not add ipip tunnel"
@@ -594,7 +593,9 @@ ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null
 
 ip -net "$nsr1" link add name tun6 type ip6tnl local fee1:2::1 remote fee1:2::2
 ip -net "$nsr1" link set tun6 up
+ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6
 ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad
+ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
 
 ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1
 ip -net "$nsr2" link set tun0 up
@@ -603,7 +604,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null
 
 ip -net "$nsr2" link add name tun6 type ip6tnl local fee1:2::2 remote fee1:2::1 || ret=1
 ip -net "$nsr2" link set tun6 up
+ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6
 ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad
+ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
 
 ip -net "$nsr1" route change default via 192.168.100.2
 ip -net "$nsr2" route change default via 192.168.100.1
@@ -636,6 +639,15 @@ else
 	ret=1
 fi
 
+ip -net "$nsr1" route change default via 192.168.210.2
+ip -net "$nsr2" route change default via 192.168.210.1
+
+if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP6IP4 tunnel"; then
+	echo "FAIL: flow offload for ns1/ns2 with IP6IP4 tunnel" 1>&2
+	ip netns exec "$nsr1" nft list ruleset
+	ret=1
+fi
+
 # Create vlan tagged devices for IPIP traffic.
 ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10
 ip -net "$nsr1" link set veth1.10 up
@@ -653,7 +665,9 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 a
 
 ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2
 ip -net "$nsr1" link set tun6.10 up
+ip -net "$nsr1" addr add 192.168.220.1/24 dev tun6.10
 ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad
+ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
 ip -6 -net "$nsr1" route delete default
 ip -6 -net "$nsr1" route add default via fee1:5::2
 ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
@@ -672,7 +686,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null
 
 ip -net "$nsr2" link add name tun6.10 type ip6tnl local fee1:4::2 remote fee1:4::1 || ret=1
 ip -net "$nsr2" link set tun6.10 up
+ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10
 ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad
+ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
 ip -6 -net "$nsr2" route delete default
 ip -6 -net "$nsr2" route add default via fee1:5::1
 
@@ -690,6 +706,15 @@ else
 	ret=1
 fi
 
+ip -net "$nsr1" route change default via 192.168.220.2
+ip -net "$nsr2" route change default via 192.168.220.1
+
+if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP6IP4 tunnel over vlan"; then
+	echo "FAIL: flow offload for ns1/ns2 with IP6IP4 tunnel over vlan" 1>&2
+	ip netns exec "$nsr1" nft list ruleset
+	ret=1
+fi
+
 # Restore the previous configuration
 ip -net "$nsr1" route change default via 192.168.10.2
 ip -net "$nsr2" route change default via 192.168.10.1
@@ -782,7 +807,7 @@ ip -net "$nsr1" addr add dead:1::1/64 dev veth0 nodad
 ip -net "$nsr1" link set up dev veth0
 }
 
-test_ipip
+test_ip_tnls
 
 test_bridge
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 2/6] net: netfilter: Add encap_proto to flow_offload_tunnel
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Add encap_proto (AF_INET or AF_INET6) to struct flow_offload_tunnel
to allow its use as part of the hash table key during flowtable entry
lookup.
This is a preliminary change to support IPv4 over IPv6 tunneling via
the flowtable infrastructure for software acceleration.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 include/linux/netdevice.h             | 1 +
 include/net/netfilter/nf_flow_table.h | 1 +
 net/ipv4/ipip.c                       | 1 +
 net/ipv6/ip6_tunnel.c                 | 1 +
 net/netfilter/nf_flow_table_ip.c      | 2 ++
 net/netfilter/nf_flow_table_path.c    | 2 ++
 6 files changed, 8 insertions(+)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 88a5c7f59891..2c97cc60807c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -902,6 +902,7 @@ struct net_device_path {
 			};
 
 			u8	l3_proto;
+			u8	encap_proto;
 		} tun;
 		struct {
 			enum {
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index 7b23b245a5a8..4d406801ec90 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -118,6 +118,7 @@ struct flow_offload_tunnel {
 	};
 
 	u8	l3_proto;
+	u8	encap_proto;
 };
 
 struct flow_offload_tuple {
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index ff95b1b9908e..5425af051d5a 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -369,6 +369,7 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx,
 	path->tun.src_v4.s_addr = tiph->saddr;
 	path->tun.dst_v4.s_addr = tiph->daddr;
 	path->tun.l3_proto = IPPROTO_IPIP;
+	path->tun.encap_proto = AF_INET;
 	path->dev = ctx->dev;
 
 	ctx->dev = rt->dst.dev;
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index f647e687ae4f..8202b33b4f19 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1855,6 +1855,7 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
 		path->type = DEV_PATH_TUN;
 		path->tun.src_v6 = t->parms.laddr;
 		path->tun.dst_v6 = t->parms.raddr;
+		path->tun.encap_proto = AF_INET6;
 		if (ctx->ether_type == cpu_to_be16(ETH_P_IP))
 			path->tun.l3_proto = IPPROTO_IPIP;
 		else
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 9c05a50d6013..49e9b0308763 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -198,6 +198,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
 			tuple->tun.dst_v4.s_addr = iph->daddr;
 			tuple->tun.src_v4.s_addr = iph->saddr;
 			tuple->tun.l3_proto = IPPROTO_IPIP;
+			tuple->tun.encap_proto = AF_INET;
 		}
 		break;
 	case htons(ETH_P_IPV6):
@@ -206,6 +207,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
 			tuple->tun.dst_v6 = ip6h->daddr;
 			tuple->tun.src_v6 = ip6h->saddr;
 			tuple->tun.l3_proto = IPPROTO_IPV6;
+			tuple->tun.encap_proto = AF_INET6;
 		}
 		break;
 	default:
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index b9032dfe3883..4d01212d6614 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -128,6 +128,7 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack,
 				info->tun.src_v6 = path->tun.src_v6;
 				info->tun.dst_v6 = path->tun.dst_v6;
 				info->tun.l3_proto = path->tun.l3_proto;
+				info->tun.encap_proto = path->tun.encap_proto;
 				info->num_tuns++;
 			} else {
 				if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
@@ -274,6 +275,7 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt,
 		route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6;
 		route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6;
 		route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto;
+		route->tuple[!dir].in.tun.encap_proto = info.tun.encap_proto;
 		route->tuple[!dir].in.num_tuns = info.num_tuns;
 	}
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 1/6] net: netfilter: Add ether_type to net_device_path_ctx
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org>

Add an ether_type field to struct net_device_path_ctx to allow IPv6
tunnel drivers to select the appropriate L3 protocol based on the
encapsulated traffic.
Update the airoha and mtk Ethernet drivers to use the new
dev_fill_forward_path() signature.
This is a preliminary patch to enable sw flowtable acceleration for
IPv4 over IPv6 tunnels.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_ppe.c        | 14 +++++++++-----
 drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 13 ++++++++-----
 include/linux/netdevice.h                       |  4 +++-
 net/core/dev.c                                  |  6 ++++--
 net/ipv6/ip6_tunnel.c                           |  5 ++++-
 net/netfilter/nf_flow_table_path.c              |  8 +++++---
 6 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 26da519236bf..c5eccb3a43a1 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -245,7 +245,8 @@ static int airoha_ppe_flow_mangle_ipv4(const struct flow_action_entry *act,
 	return 0;
 }
 
-static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr,
+static int airoha_ppe_get_wdma_info(struct net_device *dev,
+				    const u8 *addr, __be16 ether_type,
 				    struct airoha_wdma_info *info)
 {
 	struct net_device_path_stack stack;
@@ -256,7 +257,7 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr,
 		return -ENODEV;
 
 	rcu_read_lock();
-	err = dev_fill_forward_path(dev, addr, &stack);
+	err = dev_fill_forward_path(dev, addr, ether_type, &stack);
 	rcu_read_unlock();
 	if (err)
 		return err;
@@ -300,7 +301,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 					struct airoha_foe_entry *hwe,
 					struct net_device *dev, int type,
 					struct airoha_flow_data *data,
-					int l4proto)
+					__be16 ether_type, int l4proto)
 {
 	u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
 	int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
@@ -322,7 +323,8 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 	if (dev) {
 		struct airoha_wdma_info info = {};
 
-		if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
+		if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest,
+					      ether_type, &info)) {
 			val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
 			       FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
 					  FE_PSE_PORT_CDM4);
@@ -1047,6 +1049,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
 	struct flow_action_entry *act;
 	struct airoha_foe_entry hwe;
 	int err, i, offload_type;
+	__be16 ether_type = 0;
 	u16 addr_type = 0;
 	u8 l4proto = 0;
 
@@ -1073,6 +1076,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
 		struct flow_match_basic match;
 
 		flow_rule_match_basic(rule, &match);
+		ether_type = match.key->n_proto;
 		l4proto = match.key->ip_proto;
 	} else {
 		return -EOPNOTSUPP;
@@ -1143,7 +1147,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
 		return -EINVAL;
 
 	err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type,
-					   &data, l4proto);
+					   &data, ether_type, l4proto);
 	if (err)
 		return err;
 
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
index cc8c4ef8038f..2601c17b29c8 100644
--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
@@ -89,7 +89,8 @@ mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth)
 }
 
 static int
-mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info)
+mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr,
+		       __be16 ether_type, struct mtk_wdma_info *info)
 {
 	struct net_device_path_stack stack;
 	struct net_device_path *path;
@@ -102,7 +103,7 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i
 		return -1;
 
 	rcu_read_lock();
-	err = dev_fill_forward_path(dev, addr, &stack);
+	err = dev_fill_forward_path(dev, addr, ether_type, &stack);
 	rcu_read_unlock();
 	if (err)
 		return err;
@@ -190,12 +191,12 @@ mtk_flow_get_dsa_port(struct net_device **dev)
 static int
 mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
 			   struct net_device *dev, const u8 *dest_mac,
-			   int *wed_index)
+			   __be16 ether_type, int *wed_index)
 {
 	struct mtk_wdma_info info = {};
 	int pse_port, dsa_port, queue;
 
-	if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+	if (mtk_flow_get_wdma_info(dev, dest_mac, ether_type, &info) == 0) {
 		mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
 				       info.bss, info.wcid, info.amsdu);
 		if (mtk_is_netsys_v2_or_greater(eth)) {
@@ -273,6 +274,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
 	struct mtk_flow_data data = {};
 	struct mtk_foe_entry foe;
 	struct mtk_flow_entry *entry;
+	__be16 ether_type = 0;
 	int offload_type = 0;
 	int wed_index = -1;
 	u16 addr_type = 0;
@@ -319,6 +321,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
 		struct flow_match_basic match;
 
 		flow_rule_match_basic(rule, &match);
+		ether_type = match.key->n_proto;
 		l4proto = match.key->ip_proto;
 	} else {
 		return -EOPNOTSUPP;
@@ -481,7 +484,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
 		mtk_foe_entry_set_pppoe(eth, &foe, data.pppoe.sid);
 
 	err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest,
-					 &wed_index);
+					 ether_type, &wed_index);
 	if (err)
 		return err;
 
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 7309467d7873..88a5c7f59891 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -938,6 +938,7 @@ struct net_device_path_stack {
 struct net_device_path_ctx {
 	const struct net_device *dev;
 	u8			daddr[ETH_ALEN];
+	__be16			ether_type;
 
 	int			num_vlans;
 	struct {
@@ -3403,7 +3404,8 @@ void dev_remove_offload(struct packet_offload *po);
 
 int dev_get_iflink(const struct net_device *dev);
 int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+int dev_fill_forward_path(const struct net_device *dev,
+			  const u8 *daddr, __be16 ether_type,
 			  struct net_device_path_stack *stack);
 struct net_device *dev_get_by_name(struct net *net, const char *name);
 struct net_device *dev_get_by_name_rcu(struct net *net, const char *name);
diff --git a/net/core/dev.c b/net/core/dev.c
index 804e8ad25010..a76a1b58b10f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -750,12 +750,14 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
 	return &stack->path[k];
 }
 
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+int dev_fill_forward_path(const struct net_device *dev,
+			  const u8 *daddr, __be16 ether_type,
 			  struct net_device_path_stack *stack)
 {
 	const struct net_device *last_dev;
 	struct net_device_path_ctx ctx = {
-		.dev	= dev,
+		.dev		= dev,
+		.ether_type	= ether_type,
 	};
 	struct net_device_path *path;
 	int ret = 0;
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 9d1037ac082f..f647e687ae4f 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1855,7 +1855,10 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
 		path->type = DEV_PATH_TUN;
 		path->tun.src_v6 = t->parms.laddr;
 		path->tun.dst_v6 = t->parms.raddr;
-		path->tun.l3_proto = IPPROTO_IPV6;
+		if (ctx->ether_type == cpu_to_be16(ETH_P_IP))
+			path->tun.l3_proto = IPPROTO_IPIP;
+		else
+			path->tun.l3_proto = IPPROTO_IPV6;
 		path->dev = ctx->dev;
 		ctx->dev = dst->dev;
 	}
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 9e88ea6a2eef..b9032dfe3883 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -45,7 +45,8 @@ static bool nft_is_valid_ether_device(const struct net_device *dev)
 static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
 				     const struct dst_entry *dst_cache,
 				     const struct nf_conn *ct,
-				     enum ip_conntrack_dir dir, u8 *ha,
+				     enum ip_conntrack_dir dir,
+				     u8 *ha, __be16 ether_type,
 				     struct net_device_path_stack *stack)
 {
 	const void *daddr = &ct->tuplehash[!dir].tuple.src.u3;
@@ -70,7 +71,7 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
 		return -1;
 
 out:
-	return dev_fill_forward_path(dev, ha, stack);
+	return dev_fill_forward_path(dev, ha, ether_type, stack);
 }
 
 struct nft_forward_info {
@@ -252,7 +253,8 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt,
 	unsigned char ha[ETH_ALEN];
 	int i;
 
-	if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
+	if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, pkt->ethertype,
+				      &stack) >= 0)
 		nft_dev_path_info(&stack, &info, ha, &ft->data);
 
 	if (info.outdev)

-- 
2.54.0



^ permalink raw reply related

* [PATCH nf-next v3 0/6] Add IPv4 over IPv6 and SIT flowtable SW acceleration
From: Lorenzo Bianconi @ 2026-05-31 15:50 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest

Similar to IPIP and IP6I6 tunnels, introduce sw acceleration for IPv4 over
IPv6 and SIT tunnels in the netfilter flowtable infrastructure.

---
Changes in v3:
- Drop nf_flow_tunnel_v4_push and nf_flow_tunnel_v6_push routines
- Rebase on top of net-next tree.
- Link to v2: https://lore.kernel.org/r/20260506-b4-flowtable-sw-accel-ip6ip-v2-0-439fd427726e@kernel.org

Changes in v2:
- Fix MTU check in nf_flow_offload_forward() and in
  nf_flow_offload_ipv6_forward()
- Add SIT sw acceleration support
- Link to v1: https://lore.kernel.org/r/20260505-b4-flowtable-sw-accel-ip6ip-v1-0-9ac39ccc9ea9@kernel.org

---
Lorenzo Bianconi (6):
      net: netfilter: Add ether_type to net_device_path_ctx
      net: netfilter: Add encap_proto to flow_offload_tunnel
      net: netfilter: Add IPv4 over IPv6 tunnel flowtable acceleration
      selftests: netfilter: nft_flowtable.sh: Add IPv4 over IPv6 flowtable selftest
      net: netfilter: Add SIT tunnel flowtable acceleration
      selftests: netfilter: nft_flowtable.sh: Add SIT flowtable selftest

 drivers/net/ethernet/airoha/airoha_ppe.c           |  14 +-
 drivers/net/ethernet/mediatek/mtk_ppe_offload.c    |  13 +-
 include/linux/netdevice.h                          |   5 +-
 include/net/netfilter/nf_flow_table.h              |   1 +
 net/core/dev.c                                     |   6 +-
 net/ipv4/ipip.c                                    |   1 +
 net/ipv6/ip6_tunnel.c                              |   6 +-
 net/ipv6/sit.c                                     |  26 ++
 net/netfilter/nf_flow_table_core.c                 |  14 +-
 net/netfilter/nf_flow_table_ip.c                   | 403 +++++++++++++--------
 net/netfilter/nf_flow_table_path.c                 |  16 +-
 tools/testing/selftests/net/netfilter/config       |   1 +
 .../selftests/net/netfilter/nft_flowtable.sh       |  78 +++-
 13 files changed, 403 insertions(+), 181 deletions(-)
---
base-commit: 8415598365503ced2e3d019491b0a2756c85c494
change-id: 20260505-b4-flowtable-sw-accel-ip6ip-7101034cd147

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: lorenzo @ 2026-05-31 13:12 UTC (permalink / raw)
  To: Ryder Lee
  Cc: linux-mediatek@lists.infradead.org,
	Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com,
	linux-arm-kernel@lists.infradead.org,
	linux-wireless@vger.kernel.org
In-Reply-To: <44c54ed4da0d294c567b3b0ad750f082a6f1be9f.camel@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 8556 bytes --]

On May 31, Ryder Lee wrote:
> On Sun, 2026-05-31 at 14:11 +0200, lorenzo@kernel.org wrote:
> > > On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> > > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> > > > info->status.status_driver_data via memset(). Since info->control
> > > > and
> > > > info->status are members of the same union in ieee80211_tx_info,
> > > > this overwrites info->control.flags.
> > > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called
> > > > before
> > > > mt7996_mac_write_txwi(), which re-reads info->control.flags to
> > > > extract
> > > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed,
> > > > the
> > > > link_id always resolves to 0 for frames using global_wcid,
> > > > leading to
> > > > incorrect TXWI configuration.
> > > > Fix this by passing link_id as an explicit parameter to
> > > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id
> > > > is
> > > > already extracted from info->control.flags before the destructive
> > > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > > discovery
> > > > callers in mcu.c, use link_conf->link_id directly.
> > > > 
> > > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > > mt7996_mac_write_txwi() for MLO support")
> > > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > > ---
> > > > Changes in v2:
> > > > - Do not use link_id in mt7996_mac_write_txwi if it is
> > > > IEEE80211_LINK_UNSPECIFIED
> > > > - In mt7996_mac_write_txwi() rely on link_id calculated in
> > > >   mt7996_tx_prepare_skb().
> > > > - Link to v1:
> > > > https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
> > > >  
> > > > ---
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14 ++++----
> > > > ----
> > > > --
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++--
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
> > > >  3 files changed, 9 insertions(+), 13 deletions(-)
> > > > 
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > index c98446057282..95b3078d9667 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > > >  			   struct sk_buff *skb, struct mt76_wcid
> > > > *wcid,
> > > >  			   struct ieee80211_key_conf *key, int
> > > > pid,
> > > > -			   enum mt76_txq_id qid, u32 changed)
> > > > +			   enum mt76_txq_id qid, u32 changed,
> > > > +			   unsigned int link_id)
> > > >  {
> > > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> > > > > data;
> > > >  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> > > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  	bool is_8023 = info->flags &
> > > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > > >  	struct mt76_vif_link *mlink = NULL;
> > > >  	struct mt7996_vif *mvif;
> > > > -	unsigned int link_id;
> > > >  	u16 tx_count = 15;
> > > >  	u32 val;
> > > >  	bool inband_disc = !!(changed &
> > > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > > @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct
> > > > mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
> > > >  				    BSS_CHANGED_BEACON_ENABLED))
> > > > &&
> > > > (!inband_disc);
> > > >  
> > > > -	if (wcid != &dev->mt76.global_wcid)
> > > > -		link_id = wcid->link_id;
> > > > -	else
> > > > -		link_id = u32_get_bits(info->control.flags,
> > > > -				      
> > > > IEEE80211_TX_CTRL_MLO_LINK);
> > > > -
> > > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
> > > >  	if (mvif) {
> > > >  		if (wcid->offchannel)
> > > >  			mlink = rcu_dereference(mvif-
> > > > > mt76.offchannel_link);
> > > > -		if (!mlink)
> > > > +		if (!mlink && link_id !=
> > > > IEEE80211_LINK_UNSPECIFIED)
> > > >  			mlink = rcu_dereference(mvif-
> > > > > mt76.link[link_id]);
> > > >  	}
> > > >  
> > > > @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> > > > *mdev, void *txwi_ptr,
> > > >  	/* Transmit non qos data by 802.11 header and need to
> > > > fill
> > > > txd by host*/
> > > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > > >  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info-
> > > > >skb,
> > > > wcid, key,
> > > > -				      pid, qid, 0);
> > > > +				      pid, qid, 0, link_id);
> > > >  
> > > >  	/* MT7996 and MT7992 require driver to provide the MAC
> > > > TXP
> > > > for AddBA
> > > >  	 * req
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > index 8be40d60ad29..a14c63438923 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev
> > > > *dev,
> > > >  
> > > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0,
> > > > -			      BSS_CHANGED_BEACON);
> > > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > > link_id);
> > > >  
> > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > >  }
> > > > @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> > > > mt7996_dev *dev,
> > > >  
> > > >  	buf = (u8 *)tlv + sizeof(*discov);
> > > >  
> > > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0, changed);
> > > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0,
> > > > +			      changed, link_conf->link_id);
> > > >  
> > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > >  
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev
> > > > *dev,
> > > > u8 band);
> > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > > >  			   struct sk_buff *skb, struct mt76_wcid
> > > > *wcid,
> > > >  			   struct ieee80211_key_conf *key, int
> > > > pid,
> > > > -			   enum mt76_txq_id qid, u32 changed);
> > > > +			   enum mt76_txq_id qid, u32 changed,
> > > > +			   unsigned int link_id);
> > > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > > >  void mt7996_mac_work(struct work_struct *work);
> > > > 
> > > > ---
> > > > base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> > > > change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-
> > > > 85818a9bb31f
> > > > 
> > > > Best regards,
> > > > 
> > > > 
> > > We might expand flags further so this still doesn't solve the issue
> > > of
> > > flags being cleared - it only works for MLO flag. And the
> > > developers
> > > still won't easily notice that the flags are being cleared.
> > 
> > My opinion is we should consider just upstream code and then change
> > it as soon
> > as you post this new feature upstream, but I will let Felix comments
> > on it.
> > Moreover, the proposed approach aligns link_id used in
> > mt7996_tx_prepare_skb()
> > to the one used in mt7996_mac_write_txwi() and fix a possible OOB bug
> > in
> > mt7996_mac_write_txwi().
> > 
> > Regards,
> > Lorenzo
> > 
> > > 
> 
> Just to tie in with this patch subject - I'm just thinking of a way to
> solve this once and for all. If the problem is reading zeroed info-
> >control.flags, wouldn't it be better to just pass a u32 flags,
> something like this:
> 
> u32 flags = info->control.flags
> 
> mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0,
> 			      changed, flags);
> 
> We can use all flags then.

what about link_id? Should it be the same between mt7996_tx_prepare_skb()
and mt7996_mac_write_txwi()?

Regards,
Lorenzo

> 
> Ryder

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v3 2/4] ASoC: dt-bindings: Add Airoha AN7581 AFE with WM8960 Codec schema
From: Krzysztof Kozlowski @ 2026-05-31 12:45 UTC (permalink / raw)
  To: Christian Marangi
  Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jaroslav Kysela, Takashi Iwai, Matthias Brugger,
	AngeloGioacchino Del Regno, Philipp Zabel, Cyril Chao,
	Kuninori Morimoto, Chen-Yu Tsai, linux-sound, devicetree,
	linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-3-ansuelsmth@gmail.com>

On Thu, May 28, 2026 at 07:48:30PM +0200, Christian Marangi wrote:
> Add YAML schema for Airoha AN7581 AFE with the specific WM8960 i2c Codec.

DT schema. There is no YAML schema.

Also, looks like sound card, not AFE... but previous patch said it is
a sound card (while it was AFE), so completely confusing...

> 
> This gives example on how to define and connect the AFE driver with the
> WM9860 for full functionality.
> 
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
>  .../bindings/sound/airoha,an7581-wm8960.yaml  | 71 +++++++++++++++++++
>  1 file changed, 71 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
> 
> diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
> new file mode 100644
> index 000000000000..b637c294657f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
> @@ -0,0 +1,71 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/airoha,an7581-wm8960.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Airoha AN7581 sound card with WM8960 codec
> +
> +maintainers:
> +  - Christian Marangi <ansuelsmth@gmail.com>
> +
> +allOf:
> +  - $ref: sound-card-common.yaml#
> +
> +properties:
> +  compatible:
> +    const: airoha,an7581-wm8960-sound
> +
> +  platform:
> +    type: object
> +

additionalProperties go here. Looks at other Mediatek sound cards.

> +    properties:
> +      sound-dai:
> +        items:
> +          - description: The phandle of AN7581 platform.
> +
> +    required:
> +      - sound-dai
> +
> +    additionalProperties: false
> +
> +  codec:
> +    type: object
> +
> +    properties:
> +      sound-dai:
> +        items:
> +          - description: The phandle of WM8960 i2c codec.
> +
> +    required:
> +      - sound-dai
> +
> +    additionalProperties: false
> +
> +unevaluatedProperties: false

This goes after "required:".


> +
> +required:
> +  - compatible

Best regards,
Krzysztof



^ permalink raw reply

* Re: [PATCH v3 1/4] ASoC: dt-bindings: Add Airoha AN7581 AFE Sound card.
From: Krzysztof Kozlowski @ 2026-05-31 12:39 UTC (permalink / raw)
  To: Christian Marangi
  Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jaroslav Kysela, Takashi Iwai, Matthias Brugger,
	AngeloGioacchino Del Regno, Philipp Zabel, Cyril Chao,
	Kuninori Morimoto, Chen-Yu Tsai, linux-sound, devicetree,
	linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260528174840.28644-2-ansuelsmth@gmail.com>

On Thu, May 28, 2026 at 07:48:29PM +0200, Christian Marangi wrote:
> Add YAML schema for Airoha AN7581 AFE SoC sound card.
>

Nit: subject, drop final stop.


> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
>  .../bindings/sound/airoha,an7581-afe.yaml     | 41 +++++++++++++++++++
>  1 file changed, 41 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
> 
> diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
> new file mode 100644
> index 000000000000..80d9e87f1470
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
> @@ -0,0 +1,41 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/airoha,an7581-afe.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Airoha AFE PCM controller for AN7581
> +
> +maintainers:
> +  - Christian Marangi <ansuelsmth@gmail.com>
> +
> +properties:
> +  compatible:
> +    const: airoha,an7581-afe
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1

This looks incomplete - missing at least clocks. Are you sure that AFE
here is so limited, comparing to other Mediatek designs?

Best regards,
Krzysztof



^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: Ryder Lee @ 2026-05-31 12:28 UTC (permalink / raw)
  To: lorenzo@kernel.org
  Cc: linux-mediatek@lists.infradead.org,
	Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com,
	linux-arm-kernel@lists.infradead.org,
	linux-wireless@vger.kernel.org
In-Reply-To: <ahwla_obwnwr2cZo@lore-desk>

On Sun, 2026-05-31 at 14:11 +0200, lorenzo@kernel.org wrote:
> > On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> > > info->status.status_driver_data via memset(). Since info->control
> > > and
> > > info->status are members of the same union in ieee80211_tx_info,
> > > this overwrites info->control.flags.
> > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called
> > > before
> > > mt7996_mac_write_txwi(), which re-reads info->control.flags to
> > > extract
> > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed,
> > > the
> > > link_id always resolves to 0 for frames using global_wcid,
> > > leading to
> > > incorrect TXWI configuration.
> > > Fix this by passing link_id as an explicit parameter to
> > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id
> > > is
> > > already extracted from info->control.flags before the destructive
> > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > discovery
> > > callers in mcu.c, use link_conf->link_id directly.
> > > 
> > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > mt7996_mac_write_txwi() for MLO support")
> > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > ---
> > > Changes in v2:
> > > - Do not use link_id in mt7996_mac_write_txwi if it is
> > > IEEE80211_LINK_UNSPECIFIED
> > > - In mt7996_mac_write_txwi() rely on link_id calculated in
> > >   mt7996_tx_prepare_skb().
> > > - Link to v1:
> > > https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
> > >  
> > > ---
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14 ++++----
> > > ----
> > > --
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++--
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
> > >  3 files changed, 9 insertions(+), 13 deletions(-)
> > > 
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > index c98446057282..95b3078d9667 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> > > *dev, __le32 *txwi,
> > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > >  			   struct sk_buff *skb, struct mt76_wcid
> > > *wcid,
> > >  			   struct ieee80211_key_conf *key, int
> > > pid,
> > > -			   enum mt76_txq_id qid, u32 changed)
> > > +			   enum mt76_txq_id qid, u32 changed,
> > > +			   unsigned int link_id)
> > >  {
> > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> > > > data;
> > >  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > *dev, __le32 *txwi,
> > >  	bool is_8023 = info->flags &
> > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > >  	struct mt76_vif_link *mlink = NULL;
> > >  	struct mt7996_vif *mvif;
> > > -	unsigned int link_id;
> > >  	u16 tx_count = 15;
> > >  	u32 val;
> > >  	bool inband_disc = !!(changed &
> > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct
> > > mt7996_dev
> > > *dev, __le32 *txwi,
> > >  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
> > >  				    BSS_CHANGED_BEACON_ENABLED))
> > > &&
> > > (!inband_disc);
> > >  
> > > -	if (wcid != &dev->mt76.global_wcid)
> > > -		link_id = wcid->link_id;
> > > -	else
> > > -		link_id = u32_get_bits(info->control.flags,
> > > -				      
> > > IEEE80211_TX_CTRL_MLO_LINK);
> > > -
> > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
> > >  	if (mvif) {
> > >  		if (wcid->offchannel)
> > >  			mlink = rcu_dereference(mvif-
> > > > mt76.offchannel_link);
> > > -		if (!mlink)
> > > +		if (!mlink && link_id !=
> > > IEEE80211_LINK_UNSPECIFIED)
> > >  			mlink = rcu_dereference(mvif-
> > > > mt76.link[link_id]);
> > >  	}
> > >  
> > > @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> > > *mdev, void *txwi_ptr,
> > >  	/* Transmit non qos data by 802.11 header and need to
> > > fill
> > > txd by host*/
> > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > >  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info-
> > > >skb,
> > > wcid, key,
> > > -				      pid, qid, 0);
> > > +				      pid, qid, 0, link_id);
> > >  
> > >  	/* MT7996 and MT7992 require driver to provide the MAC
> > > TXP
> > > for AddBA
> > >  	 * req
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > index 8be40d60ad29..a14c63438923 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev
> > > *dev,
> > >  
> > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0,
> > > -			      BSS_CHANGED_BEACON);
> > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > link_id);
> > >  
> > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > >  }
> > > @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> > > mt7996_dev *dev,
> > >  
> > >  	buf = (u8 *)tlv + sizeof(*discov);
> > >  
> > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0, changed);
> > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0,
> > > +			      changed, link_conf->link_id);
> > >  
> > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > >  
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev
> > > *dev,
> > > u8 band);
> > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > >  			   struct sk_buff *skb, struct mt76_wcid
> > > *wcid,
> > >  			   struct ieee80211_key_conf *key, int
> > > pid,
> > > -			   enum mt76_txq_id qid, u32 changed);
> > > +			   enum mt76_txq_id qid, u32 changed,
> > > +			   unsigned int link_id);
> > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > >  void mt7996_mac_work(struct work_struct *work);
> > > 
> > > ---
> > > base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> > > change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-
> > > 85818a9bb31f
> > > 
> > > Best regards,
> > > 
> > > 
> > We might expand flags further so this still doesn't solve the issue
> > of
> > flags being cleared - it only works for MLO flag. And the
> > developers
> > still won't easily notice that the flags are being cleared.
> 
> My opinion is we should consider just upstream code and then change
> it as soon
> as you post this new feature upstream, but I will let Felix comments
> on it.
> Moreover, the proposed approach aligns link_id used in
> mt7996_tx_prepare_skb()
> to the one used in mt7996_mac_write_txwi() and fix a possible OOB bug
> in
> mt7996_mac_write_txwi().
> 
> Regards,
> Lorenzo
> 
> > 

Just to tie in with this patch subject - I'm just thinking of a way to
solve this once and for all. If the problem is reading zeroed info-
>control.flags, wouldn't it be better to just pass a u32 flags,
something like this:

u32 flags = info->control.flags

mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0,
			      changed, flags);

We can use all flags then.

Ryder

^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: lorenzo @ 2026-05-31 12:11 UTC (permalink / raw)
  To: Ryder Lee
  Cc: Shayne Chen (陳軒丞), nbd@nbd.name,
	AngeloGioacchino Del Regno,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com,
	linux-wireless@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, Roy-CH Luo,
	linux-mediatek@lists.infradead.org, linux-kernel@vger.kernel.org
In-Reply-To: <ee0e584cb2ad1b536d327eb89342d1646fa96570.camel@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 7190 bytes --]

> On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> > info->status.status_driver_data via memset(). Since info->control and
> > info->status are members of the same union in ieee80211_tx_info,
> > this overwrites info->control.flags.
> > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called before
> > mt7996_mac_write_txwi(), which re-reads info->control.flags to
> > extract
> > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed, the
> > link_id always resolves to 0 for frames using global_wcid, leading to
> > incorrect TXWI configuration.
> > Fix this by passing link_id as an explicit parameter to
> > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id is
> > already extracted from info->control.flags before the destructive
> > mt76_tx_status_skb_add() call. For the beacon and inband discovery
> > callers in mcu.c, use link_conf->link_id directly.
> > 
> > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > mt7996_mac_write_txwi() for MLO support")
> > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > ---
> > Changes in v2:
> > - Do not use link_id in mt7996_mac_write_txwi if it is
> > IEEE80211_LINK_UNSPECIFIED
> > - In mt7996_mac_write_txwi() rely on link_id calculated in
> >   mt7996_tx_prepare_skb().
> > - Link to v1:
> > https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
> >  
> > ---
> >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14 ++++--------
> > --
> >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++--
> >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
> >  3 files changed, 9 insertions(+), 13 deletions(-)
> > 
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > index c98446057282..95b3078d9667 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> > *dev, __le32 *txwi,
> >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> >  			   struct sk_buff *skb, struct mt76_wcid
> > *wcid,
> >  			   struct ieee80211_key_conf *key, int pid,
> > -			   enum mt76_txq_id qid, u32 changed)
> > +			   enum mt76_txq_id qid, u32 changed,
> > +			   unsigned int link_id)
> >  {
> >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> > >data;
> >  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > *dev, __le32 *txwi,
> >  	bool is_8023 = info->flags &
> > IEEE80211_TX_CTL_HW_80211_ENCAP;
> >  	struct mt76_vif_link *mlink = NULL;
> >  	struct mt7996_vif *mvif;
> > -	unsigned int link_id;
> >  	u16 tx_count = 15;
> >  	u32 val;
> >  	bool inband_disc = !!(changed &
> > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > *dev, __le32 *txwi,
> >  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
> >  				    BSS_CHANGED_BEACON_ENABLED)) &&
> > (!inband_disc);
> >  
> > -	if (wcid != &dev->mt76.global_wcid)
> > -		link_id = wcid->link_id;
> > -	else
> > -		link_id = u32_get_bits(info->control.flags,
> > -				       IEEE80211_TX_CTRL_MLO_LINK);
> > -
> >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
> >  	if (mvif) {
> >  		if (wcid->offchannel)
> >  			mlink = rcu_dereference(mvif-
> > >mt76.offchannel_link);
> > -		if (!mlink)
> > +		if (!mlink && link_id != IEEE80211_LINK_UNSPECIFIED)
> >  			mlink = rcu_dereference(mvif-
> > >mt76.link[link_id]);
> >  	}
> >  
> > @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> > *mdev, void *txwi_ptr,
> >  	/* Transmit non qos data by 802.11 header and need to fill
> > txd by host*/
> >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> >  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb,
> > wcid, key,
> > -				      pid, qid, 0);
> > +				      pid, qid, 0, link_id);
> >  
> >  	/* MT7996 and MT7992 require driver to provide the MAC TXP
> > for AddBA
> >  	 * req
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > index 8be40d60ad29..a14c63438923 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev,
> >  
> >  	buf = (u8 *)bcn + sizeof(*bcn);
> >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> > 0, 0,
> > -			      BSS_CHANGED_BEACON);
> > +			      BSS_CHANGED_BEACON, link_conf-
> > >link_id);
> >  
> >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> >  }
> > @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> > mt7996_dev *dev,
> >  
> >  	buf = (u8 *)tlv + sizeof(*discov);
> >  
> > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> > 0, 0, changed);
> > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> > 0, 0,
> > +			      changed, link_conf->link_id);
> >  
> >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> >  
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > index 0dc4198fcf8b..0d6488522ba7 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev *dev,
> > u8 band);
> >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> >  			   struct sk_buff *skb, struct mt76_wcid
> > *wcid,
> >  			   struct ieee80211_key_conf *key, int pid,
> > -			   enum mt76_txq_id qid, u32 changed);
> > +			   enum mt76_txq_id qid, u32 changed,
> > +			   unsigned int link_id);
> >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> >  void mt7996_mac_work(struct work_struct *work);
> > 
> > ---
> > base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> > change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-85818a9bb31f
> > 
> > Best regards,
> > 
> > 
> We might expand flags further so this still doesn't solve the issue of
> flags being cleared - it only works for MLO flag. And the developers
> still won't easily notice that the flags are being cleared.

My opinion is we should consider just upstream code and then change it as soon
as you post this new feature upstream, but I will let Felix comments on it.
Moreover, the proposed approach aligns link_id used in mt7996_tx_prepare_skb()
to the one used in mt7996_mac_write_txwi() and fix a possible OOB bug in
mt7996_mac_write_txwi().

Regards,
Lorenzo

>  
> Our current approach is to move memset after mt7996_mac_write_txwi().
> It's more flexible and we don't have to constantly change the function
> parameters just for the flags.
> 
> Ryder
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: lorenzo @ 2026-05-31 12:02 UTC (permalink / raw)
  To: Ryder Lee
  Cc: linux-mediatek@lists.infradead.org,
	Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	AngeloGioacchino Del Regno,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com,
	linux-arm-kernel@lists.infradead.org,
	linux-wireless@vger.kernel.org
In-Reply-To: <d756865286a14cf8402510817bd702228013fe8a.camel@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 7475 bytes --]

On May 31, Ryder Lee wrote:
> On Sun, 2026-05-31 at 09:09 +0200, lorenzo@kernel.org wrote:
> > > On Sat, 2026-05-30 at 17:25 +0200, Lorenzo Bianconi wrote:
> > > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> > > > info->status.status_driver_data via memset(). Since info->control
> > > > and
> > > > info->status are members of the same union in ieee80211_tx_info,
> > > > this overwrites info->control.flags.
> > > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called
> > > > before
> > > > mt7996_mac_write_txwi(), which re-reads info->control.flags to
> > > > extract
> > > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed,
> > > > the
> > > > link_id always resolves to 0 for frames using global_wcid,
> > > > leading to
> > > > incorrect TXWI configuration.
> > > > Fix this by passing link_id as an explicit parameter to
> > > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id
> > > > is
> > > > already extracted from info->control.flags before the destructive
> > > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > > discovery
> > > > callers in mcu.c, use link_conf->link_id directly.
> > > > 
> > > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > > mt7996_mac_write_txwi() for MLO support")
> > > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > > ---
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 9 +++------
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    | 5 +++--
> > > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 3 ++-
> > > >  3 files changed, 8 insertions(+), 9 deletions(-)
> > > > 
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > index c98446057282..2d3f80b3e41a 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > > >  			   struct sk_buff *skb, struct mt76_wcid
> > > > *wcid,
> > > >  			   struct ieee80211_key_conf *key, int
> > > > pid,
> > > > -			   enum mt76_txq_id qid, u32 changed)
> > > > +			   enum mt76_txq_id qid, u32 changed,
> > > > +			   unsigned int link_id)
> > > >  {
> > > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> > > > > data;
> > > >  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> > > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  	bool is_8023 = info->flags &
> > > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > > >  	struct mt76_vif_link *mlink = NULL;
> > > >  	struct mt7996_vif *mvif;
> > > > -	unsigned int link_id;
> > > >  	u16 tx_count = 15;
> > > >  	u32 val;
> > > >  	bool inband_disc = !!(changed &
> > > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > > @@ -876,9 +876,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > > *dev, __le32 *txwi,
> > > >  
> > > >  	if (wcid != &dev->mt76.global_wcid)
> > > >  		link_id = wcid->link_id;
> > > > -	else
> > > > -		link_id = u32_get_bits(info->control.flags,
> > > > -				      
> > > > IEEE80211_TX_CTRL_MLO_LINK);
> > > >  
> > > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
> > > >  	if (mvif) {
> > > > @@ -1096,7 +1093,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> > > > *mdev, void *txwi_ptr,
> > > >  	/* Transmit non qos data by 802.11 header and need to
> > > > fill
> > > > txd by host*/
> > > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > > >  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info-
> > > > >skb,
> > > > wcid, key,
> > > > -				      pid, qid, 0);
> > > > +				      pid, qid, 0, link_id);
> > > >  
> > > >  	/* MT7996 and MT7992 require driver to provide the MAC
> > > > TXP
> > > > for AddBA
> > > >  	 * req
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > index 8be40d60ad29..a14c63438923 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev
> > > > *dev,
> > > >  
> > > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0,
> > > > -			      BSS_CHANGED_BEACON);
> > > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > > link_id);
> > > >  
> > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > >  }
> > > > @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> > > > mt7996_dev *dev,
> > > >  
> > > >  	buf = (u8 *)tlv + sizeof(*discov);
> > > >  
> > > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0, changed);
> > > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > > NULL,
> > > > 0, 0,
> > > > +			      changed, link_conf->link_id);
> > > >  
> > > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > > >  
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev
> > > > *dev,
> > > > u8 band);
> > > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > > >  			   struct sk_buff *skb, struct mt76_wcid
> > > > *wcid,
> > > >  			   struct ieee80211_key_conf *key, int
> > > > pid,
> > > > -			   enum mt76_txq_id qid, u32 changed);
> > > > +			   enum mt76_txq_id qid, u32 changed,
> > > > +			   unsigned int link_id);
> > > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > > >  void mt7996_mac_work(struct work_struct *work);
> > > 
> > > The reason we didn't make the same change is because we use other
> > > control flags (IEEE80211_TX_CTRL*) of info->control.flags not just
> > > MLO
> > > one. So with this change we still need to copy over the other flags
> > > and
> > > pass them in as well.
> > 
> > Do you mean you are using info->control.flags in
> > mt7996_mac_write_txwi() in
> > some downstream code? If so, I guess you can use a similar approach
> > and
> > just pass the required field. Copy the full ieee80211_tx_info struct
> > on
> > per-packet basis seems unnecessary.
> > 
> > Regards,
> > Lorenzo
> > > 
> > > 
> 
> We will use 3 mac80211_tx_control_flags (i.e.
> IEEE80211_TX_CTRL_RATE_INJECT), so it's better to at least copy info-
> >control.flags directly and pass them in. If you pass each
> individually, the function will end up with 12 parameters, and if more
> flags are needed in the future, you will have to add even more.

But this is necessary for non-upstream code IIUC. Are you going to post it
upstream along with the fix?
I guess it is better to align the link_id used in mt7996_mac_write_txwi()
to the one used in mt7996_mac_write_txwi(). Please take a look to v3.

Regards,
Lorenzo

> 
> Ryder

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v2] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: Ryder Lee @ 2026-05-31 10:23 UTC (permalink / raw)
  To: Shayne Chen (陳軒丞), lorenzo@kernel.org,
	nbd@nbd.name, AngeloGioacchino Del Regno,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com
  Cc: linux-wireless@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, Roy-CH Luo,
	linux-mediatek@lists.infradead.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260531-mt76_tx_status_skb_add-overwrite-fix-v2-1-b73c4b4a9798@kernel.org>

On Sun, 2026-05-31 at 10:55 +0200, Lorenzo Bianconi wrote:
> mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> info->status.status_driver_data via memset(). Since info->control and
> info->status are members of the same union in ieee80211_tx_info,
> this overwrites info->control.flags.
> In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called before
> mt7996_mac_write_txwi(), which re-reads info->control.flags to
> extract
> IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed, the
> link_id always resolves to 0 for frames using global_wcid, leading to
> incorrect TXWI configuration.
> Fix this by passing link_id as an explicit parameter to
> mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id is
> already extracted from info->control.flags before the destructive
> mt76_tx_status_skb_add() call. For the beacon and inband discovery
> callers in mcu.c, use link_conf->link_id directly.
> 
> Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> mt7996_mac_write_txwi() for MLO support")
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> Changes in v2:
> - Do not use link_id in mt7996_mac_write_txwi if it is
> IEEE80211_LINK_UNSPECIFIED
> - In mt7996_mac_write_txwi() rely on link_id calculated in
>   mt7996_tx_prepare_skb().
> - Link to v1:
> https://lore.kernel.org/r/20260530-mt76_tx_status_skb_add-overwrite-fix-v1-1-e2c3151c391a@kernel.org
>  
> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 14 ++++--------
> --
>  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  5 +++--
>  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  3 ++-
>  3 files changed, 9 insertions(+), 13 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> index c98446057282..95b3078d9667 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> *dev, __le32 *txwi,
>  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
>  			   struct sk_buff *skb, struct mt76_wcid
> *wcid,
>  			   struct ieee80211_key_conf *key, int pid,
> -			   enum mt76_txq_id qid, u32 changed)
> +			   enum mt76_txq_id qid, u32 changed,
> +			   unsigned int link_id)
>  {
>  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> >data;
>  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> *dev, __le32 *txwi,
>  	bool is_8023 = info->flags &
> IEEE80211_TX_CTL_HW_80211_ENCAP;
>  	struct mt76_vif_link *mlink = NULL;
>  	struct mt7996_vif *mvif;
> -	unsigned int link_id;
>  	u16 tx_count = 15;
>  	u32 val;
>  	bool inband_disc = !!(changed &
> (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> @@ -874,17 +874,11 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> *dev, __le32 *txwi,
>  	bool beacon = !!(changed & (BSS_CHANGED_BEACON |
>  				    BSS_CHANGED_BEACON_ENABLED)) &&
> (!inband_disc);
>  
> -	if (wcid != &dev->mt76.global_wcid)
> -		link_id = wcid->link_id;
> -	else
> -		link_id = u32_get_bits(info->control.flags,
> -				       IEEE80211_TX_CTRL_MLO_LINK);
> -
>  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
>  	if (mvif) {
>  		if (wcid->offchannel)
>  			mlink = rcu_dereference(mvif-
> >mt76.offchannel_link);
> -		if (!mlink)
> +		if (!mlink && link_id != IEEE80211_LINK_UNSPECIFIED)
>  			mlink = rcu_dereference(mvif-
> >mt76.link[link_id]);
>  	}
>  
> @@ -1096,7 +1090,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> *mdev, void *txwi_ptr,
>  	/* Transmit non qos data by 802.11 header and need to fill
> txd by host*/
>  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
>  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb,
> wcid, key,
> -				      pid, qid, 0);
> +				      pid, qid, 0, link_id);
>  
>  	/* MT7996 and MT7992 require driver to provide the MAC TXP
> for AddBA
>  	 * req
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> index 8be40d60ad29..a14c63438923 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev,
>  
>  	buf = (u8 *)bcn + sizeof(*bcn);
>  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> 0, 0,
> -			      BSS_CHANGED_BEACON);
> +			      BSS_CHANGED_BEACON, link_conf-
> >link_id);
>  
>  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
>  }
> @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> mt7996_dev *dev,
>  
>  	buf = (u8 *)tlv + sizeof(*discov);
>  
> -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> 0, 0, changed);
> +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL,
> 0, 0,
> +			      changed, link_conf->link_id);
>  
>  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
>  
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> index 0dc4198fcf8b..0d6488522ba7 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev *dev,
> u8 band);
>  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
>  			   struct sk_buff *skb, struct mt76_wcid
> *wcid,
>  			   struct ieee80211_key_conf *key, int pid,
> -			   enum mt76_txq_id qid, u32 changed);
> +			   enum mt76_txq_id qid, u32 changed,
> +			   unsigned int link_id);
>  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
>  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
>  void mt7996_mac_work(struct work_struct *work);
> 
> ---
> base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
> change-id: 20260530-mt76_tx_status_skb_add-overwrite-fix-85818a9bb31f
> 
> Best regards,
> 
> 
We might expand flags further so this still doesn't solve the issue of
flags being cleared - it only works for MLO flag. And the developers
still won't easily notice that the flags are being cleared.
 
Our current approach is to move memset after mt7996_mac_write_txwi().
It's more flexible and we don't have to constantly change the function
parameters just for the flags.

Ryder


^ permalink raw reply

* Re: [PATCH] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()
From: Ryder Lee @ 2026-05-31 10:07 UTC (permalink / raw)
  To: lorenzo@kernel.org
  Cc: linux-mediatek@lists.infradead.org,
	Shayne Chen (陳軒丞), nbd@nbd.name, Roy-CH Luo,
	AngeloGioacchino Del Regno,
	Chui-hao Chiu (邱垂浩), Sean Wang,
	Bo Jiao (焦波), matthias.bgg@gmail.com,
	linux-arm-kernel@lists.infradead.org,
	linux-wireless@vger.kernel.org
In-Reply-To: <ahvewFh2NuB5MO8t@lore-desk>

On Sun, 2026-05-31 at 09:09 +0200, lorenzo@kernel.org wrote:
> > On Sat, 2026-05-30 at 17:25 +0200, Lorenzo Bianconi wrote:
> > > mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at
> > > info->status.status_driver_data via memset(). Since info->control
> > > and
> > > info->status are members of the same union in ieee80211_tx_info,
> > > this overwrites info->control.flags.
> > > In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called
> > > before
> > > mt7996_mac_write_txwi(), which re-reads info->control.flags to
> > > extract
> > > IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed,
> > > the
> > > link_id always resolves to 0 for frames using global_wcid,
> > > leading to
> > > incorrect TXWI configuration.
> > > Fix this by passing link_id as an explicit parameter to
> > > mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id
> > > is
> > > already extracted from info->control.flags before the destructive
> > > mt76_tx_status_skb_add() call. For the beacon and inband
> > > discovery
> > > callers in mcu.c, use link_conf->link_id directly.
> > > 
> > > Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework
> > > mt7996_mac_write_txwi() for MLO support")
> > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > ---
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mac.c    | 9 +++------
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    | 5 +++--
> > >  drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 3 ++-
> > >  3 files changed, 8 insertions(+), 9 deletions(-)
> > > 
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > index c98446057282..2d3f80b3e41a 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> > > @@ -856,7 +856,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev
> > > *dev, __le32 *txwi,
> > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > >  			   struct sk_buff *skb, struct mt76_wcid
> > > *wcid,
> > >  			   struct ieee80211_key_conf *key, int
> > > pid,
> > > -			   enum mt76_txq_id qid, u32 changed)
> > > +			   enum mt76_txq_id qid, u32 changed,
> > > +			   unsigned int link_id)
> > >  {
> > >  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-
> > > > data;
> > >  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> > > @@ -866,7 +867,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > *dev, __le32 *txwi,
> > >  	bool is_8023 = info->flags &
> > > IEEE80211_TX_CTL_HW_80211_ENCAP;
> > >  	struct mt76_vif_link *mlink = NULL;
> > >  	struct mt7996_vif *mvif;
> > > -	unsigned int link_id;
> > >  	u16 tx_count = 15;
> > >  	u32 val;
> > >  	bool inband_disc = !!(changed &
> > > (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP |
> > > @@ -876,9 +876,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev
> > > *dev, __le32 *txwi,
> > >  
> > >  	if (wcid != &dev->mt76.global_wcid)
> > >  		link_id = wcid->link_id;
> > > -	else
> > > -		link_id = u32_get_bits(info->control.flags,
> > > -				      
> > > IEEE80211_TX_CTRL_MLO_LINK);
> > >  
> > >  	mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL;
> > >  	if (mvif) {
> > > @@ -1096,7 +1093,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev
> > > *mdev, void *txwi_ptr,
> > >  	/* Transmit non qos data by 802.11 header and need to
> > > fill
> > > txd by host*/
> > >  	if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
> > >  		mt7996_mac_write_txwi(dev, txwi_ptr, tx_info-
> > > >skb,
> > > wcid, key,
> > > -				      pid, qid, 0);
> > > +				      pid, qid, 0, link_id);
> > >  
> > >  	/* MT7996 and MT7992 require driver to provide the MAC
> > > TXP
> > > for AddBA
> > >  	 * req
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > index 8be40d60ad29..a14c63438923 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> > > @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev
> > > *dev,
> > >  
> > >  	buf = (u8 *)bcn + sizeof(*bcn);
> > >  	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0,
> > > -			      BSS_CHANGED_BEACON);
> > > +			      BSS_CHANGED_BEACON, link_conf-
> > > > link_id);
> > >  
> > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > >  }
> > > @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct
> > > mt7996_dev *dev,
> > >  
> > >  	buf = (u8 *)tlv + sizeof(*discov);
> > >  
> > > -	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0, changed);
> > > +	mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid,
> > > NULL,
> > > 0, 0,
> > > +			      changed, link_conf->link_id);
> > >  
> > >  	memcpy(buf + MT_TXD_SIZE, skb->data, skb->len);
> > >  
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > index 0dc4198fcf8b..0d6488522ba7 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
> > > @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev
> > > *dev,
> > > u8 band);
> > >  void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi,
> > >  			   struct sk_buff *skb, struct mt76_wcid
> > > *wcid,
> > >  			   struct ieee80211_key_conf *key, int
> > > pid,
> > > -			   enum mt76_txq_id qid, u32 changed);
> > > +			   enum mt76_txq_id qid, u32 changed,
> > > +			   unsigned int link_id);
> > >  void mt7996_mac_update_beacons(struct mt7996_phy *phy);
> > >  void mt7996_mac_set_coverage_class(struct mt7996_phy *phy);
> > >  void mt7996_mac_work(struct work_struct *work);
> > 
> > The reason we didn't make the same change is because we use other
> > control flags (IEEE80211_TX_CTRL*) of info->control.flags not just
> > MLO
> > one. So with this change we still need to copy over the other flags
> > and
> > pass them in as well.
> 
> Do you mean you are using info->control.flags in
> mt7996_mac_write_txwi() in
> some downstream code? If so, I guess you can use a similar approach
> and
> just pass the required field. Copy the full ieee80211_tx_info struct
> on
> per-packet basis seems unnecessary.
> 
> Regards,
> Lorenzo
> > 
> > 

We will use 3 mac80211_tx_control_flags (i.e.
IEEE80211_TX_CTRL_RATE_INJECT), so it's better to at least copy info-
>control.flags directly and pass them in. If you pass each
individually, the function will end up with 12 parameters, and if more
flags are needed in the future, you will have to add even more.

Ryder

^ permalink raw reply

* [PATCH] wifi: mt76: mt7996: Fix possible NULL pointer dereference in mt7996_mac_write_txwi_80211()
From: Lorenzo Bianconi @ 2026-05-31  9:38 UTC (permalink / raw)
  To: Felix Fietkau, Ryder Lee, Shayne Chen, Sean Wang,
	Matthias Brugger, AngeloGioacchino Del Regno, Peter Chiu, Bo Jiao
  Cc: linux-wireless, linux-arm-kernel, linux-mediatek,
	Lorenzo Bianconi

For injected frames (e.g. via radiotap), mac80211 can pass
info->control.vif = NULL, as explicitly noted in struct ieee80211_tx_info.
Check vif pointer before executing ieee80211_vif_is_mld() in
mt7996_mac_write_txwi_80211 routine in order to avoid a possible NULL
pointer dereference.

Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework mt7996_mac_write_txwi() for MLO support")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index c98446057282..5ed2c73d5c75 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -757,6 +757,7 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
 	bool multicast = is_multicast_ether_addr(hdr->addr1);
 	u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
 	__le16 fc = hdr->frame_control, sc = hdr->seq_ctrl;
+	struct ieee80211_vif *vif = info->control.vif;
 	u16 seqno = le16_to_cpu(sc);
 	bool hw_bigtk = false;
 	u8 fc_type, fc_stype;
@@ -819,7 +820,7 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
 		txwi[3] |= cpu_to_le32(MT_TXD3_REM_TX_COUNT);
 	}
 
-	if (multicast && ieee80211_vif_is_mld(info->control.vif)) {
+	if (multicast && vif && ieee80211_vif_is_mld(vif)) {
 		val = MT_TXD3_SN_VALID |
 		      FIELD_PREP(MT_TXD3_SEQ, IEEE80211_SEQ_TO_SN(seqno));
 		txwi[3] |= cpu_to_le32(val);
@@ -839,12 +840,12 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi,
 		txwi[3] &= ~cpu_to_le32(MT_TXD3_HW_AMSDU);
 	}
 
-	if (ieee80211_vif_is_mld(info->control.vif) &&
+	if (vif && ieee80211_vif_is_mld(vif) &&
 	    (multicast || unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE))))
 		txwi[5] |= cpu_to_le32(MT_TXD5_FL);
 
 	if (ieee80211_is_nullfunc(fc) && ieee80211_has_a4(fc) &&
-	    ieee80211_vif_is_mld(info->control.vif)) {
+	    vif && ieee80211_vif_is_mld(vif)) {
 		txwi[5] |= cpu_to_le32(MT_TXD5_FL);
 		txwi[6] |= cpu_to_le32(MT_TXD6_DIS_MAT);
 	}

---
base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
change-id: 20260531-mt7996_mac_write_txwi_80211-null-ptr-deref-8ce8d08cf53a

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply related

* Re: [PATCH v2 0/4] MediaTek MT6735+MT6328 SoC/PMIC pair base support
From: Yassine Oudjana @ 2026-05-31  9:16 UTC (permalink / raw)
  To: y.oudjana
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
	Liam Girdwood, Mark Brown, Dmitry Torokhov, devicetree,
	linux-kernel, linux-arm-kernel, linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>

On Sunday, May 31st, 2026 at 11:10 AM, Yassine Oudjana via B4 Relay <devnull+y.oudjana.protonmail.com@kernel.org> wrote:

> These patches are part of a larger effort to support the MT6735 SoC family in
> mainline Linux. More patches (unsent or sent and pending review or revision)
> can be found here[1].
> 
> This series adds base support for the MediaTek MT6735 SoC and MT6328 PMIC pair.
> This includes PMIC wrapper support on the SoC side and regulators and keys on
> the PMIC side. The PMIC has other blocks such as an audio codec and battery
> charger which can be supported in the future.
> 
> Changes since v1:
> - Fit regulator definitions in 2 lines and 100 characters whenever possible.
> - Remove get_mode and set_mode ops for regulators that don't support them.

Forgot to mention:
- Remove MD register definitions.

> 
> [1] https://gitlab.com/mt6735-mainline/linux/-/commits/mt6735-staging
> 
> Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
> ---
> Yassine Oudjana (4):
>       dt-bindings: mediatek: pwrap: Add MT6735 compatible
>       soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
>       regulator: Add driver for MediaTek MT6328 PMIC regulators
>       Input: mtk-pmic-keys - Add support for MT6328
> 
>  .../bindings/soc/mediatek/mediatek,pwrap.yaml      |   1 +
>  drivers/input/keyboard/mtk-pmic-keys.c             |  15 +
>  drivers/regulator/Kconfig                          |   9 +
>  drivers/regulator/Makefile                         |   1 +
>  drivers/regulator/mt6328-regulator.c               | 500 +++++++++++++++++++++
>  drivers/soc/mediatek/mtk-pmic-wrap.c               | 177 +++++++-
>  include/linux/regulator/mt6328-regulator.h         |  49 ++
>  7 files changed, 749 insertions(+), 3 deletions(-)
> ---
> base-commit: 7da7f07112610a520567421dd2ffcb51beaefbcc
> change-id: 20260531-mt6735-pwrap-mt6328-pmic-de481d5c5e89
> 
> Best regards,
> --
> Yassine Oudjana <y.oudjana@protonmail.com>
> 
> 
>


^ permalink raw reply

* [PATCH] wifi: mt76: mt7996: Fix possible token leak in mt7996_tx_prepare_skb()
From: Lorenzo Bianconi @ 2026-05-31  9:10 UTC (permalink / raw)
  To: Felix Fietkau, Ryder Lee, Shayne Chen, Sean Wang,
	Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-wireless, linux-arm-kernel, linux-mediatek,
	Lorenzo Bianconi

If link_conf or link_sta lookup fails in mt7996_tx_prepare_skb routine,
mt7996 driver leaks an already allocated tx token. Fix the issue
releasing the token in case of error.

Fixes: 7ef0c7ad735b0 ("wifi: mt76: mt7996: Implement MLD address translation for EAPOL")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 8 ++++++--
 drivers/net/wireless/mediatek/mt76/tx.c         | 2 +-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index c98446057282..8c56344d211b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -1067,11 +1067,11 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 
 		link_conf = rcu_dereference(vif->link_conf[wcid->link_id]);
 		if (!link_conf)
-			return -EINVAL;
+			goto error_relase_token;
 
 		link_sta = rcu_dereference(sta->link[wcid->link_id]);
 		if (!link_sta)
-			return -EINVAL;
+			goto error_relase_token;
 
 		dma_sync_single_for_cpu(mdev->dma_dev, tx_info->buf[1].addr,
 					tx_info->buf[1].len, DMA_TO_DEVICE);
@@ -1176,6 +1176,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 	tx_info->nbuf = MT_CT_DMA_BUF_NUM;
 
 	return 0;
+
+error_relase_token:
+	mt76_token_release(mdev, id, NULL);
+	return -EINVAL;
 }
 
 u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id)
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 22f9690634c9..f96d9c471853 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -933,7 +933,7 @@ mt76_token_release(struct mt76_dev *dev, int token, bool *wake)
 #endif
 	}
 
-	if (dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR &&
+	if (wake && dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR &&
 	    dev->phy.q_tx[0]->blocked)
 		*wake = true;
 

---
base-commit: 4913f44167cf35a9536e9eec7352e15b2de0c573
change-id: 20260531-mt7996_tx_prepare_skb-token-leack-82e240d8c66f

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox