Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next] octeontx2-af: Use LMAC_AF_ERR codes in CGX handlers
From: nshettyj @ 2026-07-17  5:11 UTC (permalink / raw)
  To: netdev, linux-kernel
  Cc: Hariprasad Kelam, Nitin Shetty J, Sunil Goutham, Ratheesh Kannoth,
	Geetha sowjanya, Subbaraya Sundeep, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni

From: Hariprasad Kelam <hkelam@marvell.com>

When a netdev that is not mapped to a MAC block (cgx/rpm) requests a
MAC feature, the AF driver currently returns generic errors like -EPERM.

Replace these generic error codes with driver-specific LMAC_AF_ERR_*
error codes to provide better debugging information.

Signed-off-by: Hariprasad Kelam <hkelam@marvell.com>
Signed-off-by: Nitin Shetty J <nshettyj@marvell.com>
---
 .../ethernet/marvell/octeontx2/af/rvu_cgx.c   | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
index 4ff3935ed3fe..5558bb66bf9f 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
@@ -709,7 +709,7 @@ int rvu_mbox_handler_cgx_mac_addr_add(struct rvu *rvu,
 	int rc = 0;
 
 	if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	if (rvu_npc_exact_has_match_table(rvu))
 		return rvu_npc_exact_mac_addr_add(rvu, req, rsp);
@@ -732,7 +732,7 @@ int rvu_mbox_handler_cgx_mac_addr_del(struct rvu *rvu,
 	u8 cgx_id, lmac_id;
 
 	if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	if (rvu_npc_exact_has_match_table(rvu))
 		return rvu_npc_exact_mac_addr_del(rvu, req, rsp);
@@ -789,7 +789,7 @@ int rvu_mbox_handler_cgx_promisc_enable(struct rvu *rvu, struct msg_req *req,
 	u8 cgx_id, lmac_id;
 
 	if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	/* Disable drop on non hit rule */
 	if (rvu_npc_exact_has_match_table(rvu))
@@ -808,7 +808,7 @@ int rvu_mbox_handler_cgx_promisc_disable(struct rvu *rvu, struct msg_req *req,
 	u8 cgx_id, lmac_id;
 
 	if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	/* Disable drop on non hit rule */
 	if (rvu_npc_exact_has_match_table(rvu))
@@ -877,7 +877,7 @@ static int rvu_cgx_config_linkevents(struct rvu *rvu, u16 pcifunc, bool en)
 	u8 cgx_id, lmac_id;
 
 	if (!is_cgx_config_permitted(rvu, pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
 
@@ -915,7 +915,7 @@ int rvu_mbox_handler_cgx_get_linkinfo(struct rvu *rvu, struct msg_req *req,
 	pf = rvu_get_pf(rvu->pdev, req->hdr.pcifunc);
 
 	if (!is_pf_cgxmapped(rvu, pf))
-		return -ENODEV;
+		return LMAC_AF_ERR_PF_NOT_MAPPED;
 
 	rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
 
@@ -975,7 +975,7 @@ static int rvu_cgx_config_intlbk(struct rvu *rvu, u16 pcifunc, bool en)
 	u8 cgx_id, lmac_id;
 
 	if (!is_cgx_config_permitted(rvu, pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
 	mac_ops = get_mac_ops(rvu_cgx_pdata(cgx_id, rvu));
@@ -1178,7 +1178,7 @@ int rvu_mbox_handler_cgx_set_fec_param(struct rvu *rvu,
 	u8 cgx_id, lmac_id;
 
 	if (!is_pf_cgxmapped(rvu, pf))
-		return -EPERM;
+		return LMAC_AF_ERR_PF_NOT_MAPPED;
 
 	if (req->fec == OTX2_FEC_OFF)
 		req->fec = OTX2_FEC_NONE;
@@ -1197,7 +1197,7 @@ int rvu_mbox_handler_cgx_get_aux_link_info(struct rvu *rvu, struct msg_req *req,
 		return LMAC_AF_ERR_FIRMWARE_DATA_NOT_MAPPED;
 
 	if (!is_pf_cgxmapped(rvu, pf))
-		return -EPERM;
+		return LMAC_AF_ERR_PF_NOT_MAPPED;
 
 	rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
 
@@ -1226,7 +1226,7 @@ int rvu_mbox_handler_cgx_set_link_mode(struct rvu *rvu,
 		return LMAC_AF_ERR_FIRMWARE_DATA_NOT_MAPPED;
 
 	if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc))
-		return -EPERM;
+		return LMAC_AF_ERR_PERM_DENIED;
 
 	rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_idx, &lmac);
 	cgxd = rvu_cgx_pdata(cgx_idx, rvu);
-- 
2.48.1


^ permalink raw reply related

* Re: [Patch net-next v2 2/2] net: phy: Add driver for Motorcomm Quad 2.5GbE phy
From: Kyle Switch @ 2026-07-17  5:31 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Frank.Sae, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	netdev, linux-kernel, jianmin.wang, xiaolin.xu, ming.xu, jie.han
In-Reply-To: <6bb415f2-5e41-4002-90e5-3d37d881b391@lunn.ch>



On 7/17/26 01:16, Andrew Lunn wrote:
>> +#define YT8824_RSSR_SPACE_MASK			BIT(0)
>> +#define YT8824_RSSR_FIBER_SPACE			(0x1)
>> +#define YT8824_RSSR_UTP_SPACE			(0x0)
> 
> Since there are only two pages, one bit:
> 
>> +/**
>> + * yt8824_read_page() - read reg page
>> + * @phydev: a pointer to a &struct phy_device
>> + *
>> + * returns current reg space of yt8824 (YT8824_RSSR_FIBER_SPACE/
>> + * YT8824_RSSR_UTP_SPACE) or negative errno code
>> + */
>> +static int yt8824_read_page(struct phy_device *phydev)
>> +{
>> +	int old_page;
>> +
>> +	old_page = ytphy_read_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG);
>> +	if (old_page < 0)
>> +		return old_page;
>> +
>> +	if ((old_page & YT8824_RSSR_SPACE_MASK) == YT8824_RSSR_FIBER_SPACE)
>> +		return YT8824_RSSR_FIBER_SPACE;
>> +
>> +	return YT8824_RSSR_UTP_SPACE;
> 
> You can simplify this to just
> 
>     return old_page & YT8824_RSSR_SPACE_MASK;
> 

Ans: Fixed in v3 patch, Please help check in updated patch.

>> +static int yt8824_write_page(struct phy_device *phydev, int page)
>> +{
>> +	int old_page;
>> +	u16 data;
>> +
>> +	old_page = ytphy_read_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG);
>> +	data = old_page & (~(0x1));
> 
> Use YT8824_RSSR_SPACE_MASK here.
> 
>> +	data |= page;
>> +
>> +	return ytphy_write_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG, data);
>> +};
>> +
>> +/**
>> + * configuration YT8824 to one template test mode.
>> + */
>> +static int yt8824_soft_reset_step1_paged(struct phy_device *phydev,
>> +					 int reg_space)
>> +{
>> +	int old_page;
>> +	int ret = 0;
>> +
>> +	old_page = phy_select_page(phydev, reg_space);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	if (old_page >= 0) {
> 
> What is the purpose of this if ()?

Ans: remove the unnecessary and redundant judgment in v3 patch.

> 
>> +		if (reg_space == YT8824_RSSR_UTP_SPACE) {
>> +			ret =  __phy_write_mmd(phydev, 0x1, 0x0084, 0x2000);
> 
> No magic numbers. 0x0084 is probably not a vendor register either, so
> please use its proper name. And is 0x2000 a value, or should it bit it
> use the BIT() macro?

Ans: Fixed in v3 patch.

> 
>> +			ret = __phy_read(phydev, MII_BMCR);
>> +			if (ret < 0)
>> +				goto err_restore_page;
>> +			ret |= BMCR_RESET;
>> +			ret = __phy_write(phydev, MII_BMCR, ret);
>> +			if (ret < 0)
>> +				goto err_restore_page;
>> +			do {
>> +				msleep(50);
>> +				ret = __phy_read(phydev, MII_BMCR);
>> +				if (ret < 0)
>> +					goto err_restore_page;
>> +			} while ((ret & BMCR_RESET) && --retry);
> 
> 
>> +			ret = __phy_read(phydev, MII_BMCR);
>> +			if (ret < 0)
>> +				goto err_restore_page;
>> +			/* disable isolation */
>> +			ret &= ~BIT(10);
> 
> That is probably a standard BMCR bit, so there should be a#define for
> it.

Ans: Fix all BMCR bit in V3.

> 
>> +			/* soft reset */
>> +			ret |= BMCR_RESET;
>> +			ret = __phy_write(phydev, MII_BMCR, ret);
>> +			if (ret < 0)
>> +				return ret;
>> +			do {
>> +				msleep(50);
>> +				ret = __phy_read(phydev, MII_BMCR);
>> +				if (ret < 0)
>> +					goto err_restore_page;
>> +			} while ((ret & BMCR_RESET) && --retry);
>> +			if (ret & BMCR_RESET)
>> +				goto err_restore_page;
> 
> Look at all the duplicated code here. Please add some helpers.
> Also take a look at phy_device.c, and follow what it does.

Ans: Fixed in v3 patch using phy_read_poll_timeout to wait softret done.

> 
>> +	if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
>> +		/* invalid test mode */
>> +		ret = yt8824_soft_reset_step1_paged(phydev,
>> +						    YT8824_RSSR_UTP_SPACE);
> 
> Why not call this function
> yt8824_soft_reset_invalid_test_mode_paged(). Having the comment is a
> big hint your function naming is bad.

Ans: Fixed in v3 patch, to rename the function based on 
its approximate functionality

> 
>> +static int yt8824_internal_config_init_paged(struct phy_device *phydev,
>> +					     int reg_space)
>> +{
>> +	struct yt8521_priv *priv = phydev->priv;
>> +	int old_page;
>> +	int port = 0;
>> +	int ret = 0;
>> +
>> +	old_page = phy_select_page(phydev, reg_space);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	port = phydev->mdio.addr - priv->phy_base_addr;
>> +	ret = ytphy_write_ext(phydev, 0x1, 0x3);
>> +	if (ret < 0)
>> +		goto err_restore_page;
>> +	ret = __phy_write(phydev, MII_BMCR, 0x1900);
> 
> All the magic numbers need to be replaced. But this is especially bad,
> because BMCR is well documented and has all its bits covered with
> existing #define's.
> 

Ans: All standard BMCR registers operations have been replaced in the v3 patch.
However, for some extend registers, they are designed to optimize 
the performance of phy8824. If they are also replaced, the entire function may 
become bloated, so they have not been replaced for the time being.

>> +	netdev_info(phydev->attached_dev,
>> +		    "%s done, phy addr: %d, phy base addr = %d\n",
>> +		    __func__, phydev->mdio.addr, priv->phy_base_addr);
> 
> Very unusual. This is a PHY driver, so it should be using
> phy_info(). But i also think this should be _dgb().

Ans: yes, it will be fixed in V3 using phydev_dbg().

> 
> 	Andrew

^ permalink raw reply

* [syzbot] [usb?] KASAN: slab-out-of-bounds Read in usbatm_usb_disconnect
From: syzbot @ 2026-07-17  5:39 UTC (permalink / raw)
  To: 3chas3, duncan.sands, gregkh, linux-atm-general, linux-kernel,
	linux-usb, netdev, syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    58717b2a1365 Merge tag 'sound-7.2-rc4' of git://git.kernel..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=115dfb89580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=48ef5c5c0f192153
dashboard link: https://syzkaller.appspot.com/bug?extid=e62a973f8322b3bbe3ac
compiler:       gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
syz repro:      https://syzkaller.appspot.com/x/repro.syz?x=16b104b9580000
C reproducer:   https://syzkaller.appspot.com/x/repro.c?x=155dfb89580000

Downloadable assets:
disk image (non-bootable): https://storage.googleapis.com/syzbot-assets/d900f083ada3/non_bootable_disk-58717b2a.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/f102e0277861/vmlinux-58717b2a.xz
kernel image: https://storage.googleapis.com/syzbot-assets/d532ca1493c0/bzImage-58717b2a.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+e62a973f8322b3bbe3ac@syzkaller.appspotmail.com

usb 5-1: [ueagle-atm] loading firmware ueagle-atm/eagleI.fw
usb 5-1: USB disconnect, device number 2
==================================================================
BUG: KASAN: slab-out-of-bounds in __mutex_lock_common kernel/locking/mutex.c:625 [inline]
BUG: KASAN: slab-out-of-bounds in __mutex_lock+0x15ca/0x1bd0 kernel/locking/mutex.c:821
Read of size 8 at addr ffff888033d7a460 by task kworker/1:1/56

CPU: 1 UID: 0 PID: 56 Comm: kworker/1:1 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: usb_hub_wq hub_event
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x13d/0x4b0 mm/kasan/report.c:482
 kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
 __mutex_lock_common kernel/locking/mutex.c:625 [inline]
 __mutex_lock+0x15ca/0x1bd0 kernel/locking/mutex.c:821
 usbatm_usb_disconnect+0x70/0x7e0 drivers/usb/atm/usbatm.c:1221
 uea_disconnect+0x279/0x2b0 drivers/usb/atm/ueagle-atm.c:2605
 usb_unbind_interface+0x1dd/0x9e0 drivers/usb/core/driver.c:458
 device_remove drivers/base/dd.c:618 [inline]
 device_remove+0x12a/0x180 drivers/base/dd.c:610
 __device_release_driver drivers/base/dd.c:1349 [inline]
 device_release_driver_internal+0x44e/0x620 drivers/base/dd.c:1372
 bus_remove_device+0x2bc/0x560 drivers/base/bus.c:664
 device_del+0x376/0x9b0 drivers/base/core.c:3961
 usb_disable_device+0x367/0x810 drivers/usb/core/message.c:1478
 usb_disconnect+0x2e2/0x9a0 drivers/usb/core/hub.c:2345
 hub_port_connect drivers/usb/core/hub.c:5407 [inline]
 hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
 port_event drivers/usb/core/hub.c:5871 [inline]
 hub_event+0x1c4f/0x4a60 drivers/usb/core/hub.c:5953
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
 kthread+0x370/0x450 kernel/kthread.c:436
 ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>

Allocated by task 5834:
 kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5362 [inline]
 __kmalloc_node_track_caller_noprof+0x331/0x830 mm/slub.c:5497
 kmemdup_noprof+0x29/0x60 mm/util.c:138
 kmemdup_noprof include/linux/fortify-string.h:715 [inline]
 xt_register_table+0x129/0x9a0 net/netfilter/x_tables.c:1580
 ip6t_register_table+0x137/0x2c0 net/ipv6/netfilter/ip6_tables.c:1755
 ip6table_mangle_table_init+0x40/0x60 net/ipv6/netfilter/ip6table_mangle.c:85
 xt_find_table_lock+0x2db/0x520 net/netfilter/x_tables.c:1353
 xt_request_find_table_lock+0x28/0xf0 net/netfilter/x_tables.c:1378
 get_info+0x1a7/0x770 net/ipv6/netfilter/ip6_tables.c:977
 do_ip6t_get_ctl+0x231/0xc30 net/ipv6/netfilter/ip6_tables.c:1669
 nf_getsockopt+0x7c/0xe0 net/netfilter/nf_sockopt.c:116
 ipv6_getsockopt+0x207/0x2a0 net/ipv6/ipv6_sockglue.c:1464
 tcp_getsockopt+0xa1/0x110 net/ipv4/tcp.c:4864
 do_sock_getsockopt+0x50a/0x6e0 net/socket.c:2474
 __sys_getsockopt+0x148/0x260 net/socket.c:2505
 __do_sys_getsockopt net/socket.c:2512 [inline]
 __se_sys_getsockopt net/socket.c:2509 [inline]
 __x64_sys_getsockopt+0xbd/0x160 net/socket.c:2509
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 13:
 kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2705 [inline]
 slab_free mm/slub.c:6405 [inline]
 kfree+0x22b/0x6c0 mm/slub.c:6720
 __ip6t_unregister_table net/ipv6/netfilter/ip6_tables.c:1729 [inline]
 ip6t_unregister_table_exit+0x1b7/0x230 net/ipv6/netfilter/ip6_tables.c:1773
 ops_exit_list net/core/net_namespace.c:199 [inline]
 ops_undo_list+0x2ee/0xab0 net/core/net_namespace.c:252
 cleanup_net+0x499/0x920 net/core/net_namespace.c:702
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
 kthread+0x370/0x450 kernel/kthread.c:436
 ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

The buggy address belongs to the object at ffff888033d7a400
 which belongs to the cache kmalloc-96 of size 96
The buggy address is located 0 bytes to the right of
 allocated 96-byte region [ffff888033d7a400, ffff888033d7a460)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888033d7a780 pfn:0x33d7a
flags: 0xfff00000000200(workingset|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000200 ffff88801bc42280 ffffea0000b94790 ffffea0000d07d10
raw: ffff888033d7a780 000000080020001f 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1169, tgid 1169 (kworker/u33:9), ts 17888008252, free_ts 0
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1859
 prep_new_page mm/page_alloc.c:1867 [inline]
 get_page_from_freelist+0xf48/0x3530 mm/page_alloc.c:3946
 __alloc_frozen_pages_noprof+0x299/0x2dc0 mm/page_alloc.c:5304
 alloc_slab_page mm/slub.c:3294 [inline]
 allocate_slab mm/slub.c:3408 [inline]
 new_slab+0xa2/0x650 mm/slub.c:3454
 refill_objects+0xe3/0x410 mm/slub.c:7338
 refill_sheaf mm/slub.c:2832 [inline]
 __pcs_replace_empty_main+0x376/0x680 mm/slub.c:4703
 alloc_from_pcs mm/slub.c:4801 [inline]
 slab_alloc_node mm/slub.c:4933 [inline]
 __kmalloc_cache_node_noprof+0x542/0x6c0 mm/slub.c:5530
 _kmalloc_node_noprof include/linux/slab.h:1174 [inline]
 __get_vm_area_node+0x101/0x330 mm/vmalloc.c:3215
 __vmalloc_node_range_noprof+0x228/0x1630 mm/vmalloc.c:4042
 __vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4143
 alloc_thread_stack_node kernel/fork.c:359 [inline]
 dup_task_struct kernel/fork.c:929 [inline]
 copy_process+0x7fb/0x8030 kernel/fork.c:2115
 kernel_clone+0x176/0x9d0 kernel/fork.c:2748
 user_mode_thread+0xcc/0x110 kernel/fork.c:2824
 call_usermodehelper_exec_work kernel/umh.c:171 [inline]
 call_usermodehelper_exec_work+0xcb/0x180 kernel/umh.c:157
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
page_owner free stack trace missing

Memory state around the buggy address:
 ffff888033d7a300: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc
 ffff888033d7a380: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc
>ffff888033d7a400: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
                                                       ^
 ffff888033d7a480: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
 ffff888033d7a500: 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc
==================================================================


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* [Patch net-next v3] net: phy: Add driver for Motorcomm Quad 2.5GbE phy
From: Kyle Switch @ 2026-07-17  5:48 UTC (permalink / raw)
  To: Frank.Sae, andrew, hkallweit1, linux, davem, edumazet, kuba,
	pabeni
  Cc: netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang, jie.han

Add a driver for motorcomm yt8824 quad 2.5G ethernet phy, supports
2.5G/1000M/100M/10M speed.

Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---

changes in v3:
1. Using common apis defined in phy_package.c to handle shared top
extend register space.
2. Add dts demo in motorcomm,yt8xxx.yaml.
3. Fix unnecessary redundant judgments.
4. Fix BMCR registers operation using magic number.
5. Rename funtion based on its approximate functionality.

changes in v2:
1. Remove duplicate code and replace it with existing api. 

 .../bindings/net/motorcomm,yt8xxx.yaml        |   30 +
 drivers/net/phy/motorcomm.c                   | 1808 ++++++++++++++++-
 2 files changed, 1750 insertions(+), 88 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/motorcomm,yt8xxx.yaml b/Documentation/devicetree/bindings/net/motorcomm,yt8xxx.yaml
index 26688e2302ea..e7592468f658 100644
--- a/Documentation/devicetree/bindings/net/motorcomm,yt8xxx.yaml
+++ b/Documentation/devicetree/bindings/net/motorcomm,yt8xxx.yaml
@@ -149,3 +149,33 @@ examples:
             motorcomm,auto-sleep-disabled;
         };
     };
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy-package@4 {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            compatible = "motorcomm,phy8824";
+            reg = <4>;
+
+            motorcomm,interface-mode = "usxgmii";
+
+            ethernet-phy@4 {
+                reg = <0>;
+            };
+
+            ethernet-phy@5 {
+                reg = <5>;
+            };
+
+            ethernet-phy@6 {
+                reg = <6>;
+            };
+
+            ethernet-phy@7 {
+                reg = <7>;
+            };
+        };
+    };
diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 5071605a1a11..c02a0ea04b31 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -1,23 +1,32 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * Motorcomm 8511/8521/8522/8531/8531S/8821 PHY driver.
+ * Motorcomm 8511/8521/8522/8531/8531S/8821/8824 PHY driver.
  *
  * Author: Peter Geis <pgwipeout@gmail.com>
  * Author: Frank <Frank.Sae@motor-comm.com>
+ * Author: Kyle <kyle.switch@motor-comm.com>
  */
 
 #include <linux/etherdevice.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/property.h>
 
+#include "phylib.h"
+
+/* define PHY8824 top extend addr offset internal and external*/
+#define INTERNAL_PHY8824_TOP_EXTEND_OFFSET    (5)
+#define EXTERNAL_PHY8824_TOP_EXTEND_OFFSET    (4)
+
 #define PHY_ID_YT8511		0x0000010a
 #define PHY_ID_YT8521		0x0000011a
 #define PHY_ID_YT8522		0x4f51e928
 #define PHY_ID_YT8531		0x4f51e91b
 #define PHY_ID_YT8531S		0x4f51e91a
 #define PHY_ID_YT8821		0x4f51ea19
+#define PHY_ID_YT8824		0x4f51e8b8
 /* YT8521/YT8531S/YT8821 Register Overview
  *	UTP Register space	|	FIBER Register space
  *  ------------------------------------------------------------
@@ -29,6 +38,18 @@
  *  ------------------------------------------------------------
  */
 
+/* YT8824 Register Overview
+ *	UTP Register space	|	FIBER Register space
+ *  ------------------------------------------------------------
+ * |	UTP MII			|	FIBER MII	        |
+ * |	UTP MMD			|				|
+ * |	UTP Extended		|	FIBER Extended		|
+ * |	UTP Top Extended	|	FIBER Top Extended	|
+ *  ------------------------------------------------------------
+ * |			Common Top Extended			|
+ *  ------------------------------------------------------------
+ */
+
 /* 0x10 ~ 0x15 , 0x1E and 0x1F are common MII registers of yt phy */
 
 /* Specific Function Control Register */
@@ -354,7 +375,7 @@
 
 #define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG		0x4B5
 #define YT8821_UTP_EXT_MU_FINE_FR_F_FFE			GENMASK(14, 12)
-#define YT8821_UTP_EXT_MU_FINE_FR_F_FBE			GENMASK(10, 8)
+#define YT8821_UTP_EXT_MU_FINE_FR_F_FBE	GENMASK(10, 8)
 
 #define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG		0x4D2
 #define YT8821_UTP_EXT_VGA_LPF1_CAP_OTHER		GENMASK(7, 4)
@@ -375,6 +396,16 @@
 #define YT8821_CHIP_MODE_AUTO_BX2500_SGMII	0
 #define YT8821_CHIP_MODE_FORCE_BX2500		1
 
+#define YT8824_RSSR_SPACE_MASK			BIT(0)
+#define YT8824_RSSR_FIBER_SPACE			(0x1)
+#define YT8824_RSSR_UTP_SPACE			(0x0)
+#define YT8824_UTP_TEMPLATE_MODE_CTRL		(0x84)
+#define YT8824_UTP_TEMPLATE_MODE_MASK		GENMASK(15, 13)
+#define YT8824_UTP_TEMPLATE_TEST_MODE1		BIT(13)
+#define YT8824_SDS_CFG_MIN_PRE_MASK		GENMASK(3, 0)
+#define YT8824_SDS_EN_FILL_PRE			BIT(13)
+#define YT8824_SDS_TX_PRE_PADDING		(0x7)
+
 struct yt8521_priv {
 	/* combo_advertising is used for case of YT8521 in combo mode,
 	 * this means that yt8521 may work in utp or fiber mode which depends
@@ -393,6 +424,11 @@ struct yt8521_priv {
 	u8 reg_page;
 };
 
+struct yt8824_shared_priv {
+	unsigned int interface_mode;
+	unsigned int offset;
+};
+
 /**
  * ytphy_read_ext() - read a PHY's extended register
  * @phydev: a pointer to a &struct phy_device
@@ -431,6 +467,55 @@ static int ytphy_read_ext_with_lock(struct phy_device *phydev, u16 regnum)
 	return ret;
 }
 
+/**
+ * ytphy_read_top_ext() - read a PHY's top extended register for YT8824
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_read_top_ext(struct phy_device *phydev, u16 regnum)
+{
+	struct yt8824_shared_priv *shared_priv;
+	unsigned int offset;
+	int ret;
+
+	shared_priv = phy_package_get_priv(phydev);
+	offset = shared_priv->offset;
+	ret = __phy_package_write(phydev, offset, YTPHY_PAGE_SELECT, regnum);
+	if (ret < 0)
+		return ret;
+
+	return __phy_package_read(phydev, offset, YTPHY_PAGE_DATA);
+}
+
+/**
+ * ytphy_write_top_ext() - read a PHY's top extended register for YT8824
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_write_top_ext(struct phy_device *phydev, u16 regnum,
+			       u16 val)
+{
+	struct yt8824_shared_priv *shared_priv;
+	unsigned int offset;
+	int ret;
+
+	shared_priv = phy_package_get_priv(phydev);
+	offset = shared_priv->offset;
+	ret = __phy_package_write(phydev, offset, YTPHY_PAGE_SELECT, regnum);
+	if (ret < 0)
+		return ret;
+
+	return __phy_package_write(phydev, offset, YTPHY_PAGE_DATA, val);
+}
+
 /**
  * ytphy_write_ext() - write a PHY's extended register
  * @phydev: a pointer to a &struct phy_device
@@ -3059,99 +3144,1645 @@ static int yt8821_resume(struct phy_device *phydev)
 	return yt8821_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0);
 }
 
-static struct phy_driver motorcomm_phy_drvs[] = {
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
-		.name		= "YT8511 Gigabit Ethernet",
-		.config_init	= yt8511_config_init,
-		.suspend	= genphy_suspend,
-		.resume		= genphy_resume,
-		.read_page	= yt8511_read_page,
-		.write_page	= yt8511_write_page,
-	},
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
-		.name		= "YT8521 Gigabit Ethernet",
-		.get_features	= yt8521_get_features,
-		.probe		= yt8521_probe,
-		.read_page	= yt8521_read_page,
-		.write_page	= yt8521_write_page,
-		.get_wol	= ytphy_get_wol,
-		.set_wol	= ytphy_set_wol,
-		.config_aneg	= yt8521_config_aneg,
-		.aneg_done	= yt8521_aneg_done,
-		.config_init	= yt8521_config_init,
-		.read_status	= yt8521_read_status,
-		.soft_reset	= yt8521_soft_reset,
-		.suspend	= yt8521_suspend,
-		.resume		= yt8521_resume,
-		.led_hw_is_supported = yt8521_led_hw_is_supported,
-		.led_hw_control_set = yt8521_led_hw_control_set,
-		.led_hw_control_get = yt8521_led_hw_control_get,
-	},
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8522),
-		.name		= "YT8522 100 Megabit Ethernet",
-		.config_aneg	= genphy_config_aneg,
-		.config_init	= yt8522_config_init,
-		.suspend	= genphy_suspend,
-		.resume		= genphy_resume,
-	},
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
-		.name		= "YT8531 Gigabit Ethernet",
-		.probe		= yt8531_probe,
-		.config_init	= yt8531_config_init,
-		.suspend	= genphy_suspend,
-		.resume		= genphy_resume,
-		.get_wol	= ytphy_get_wol,
-		.set_wol	= yt8531_set_wol,
-		.link_change_notify = yt8531_link_change_notify,
-		.led_hw_is_supported = yt8521_led_hw_is_supported,
-		.led_hw_control_set = yt8521_led_hw_control_set,
-		.led_hw_control_get = yt8521_led_hw_control_get,
-	},
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
-		.name		= "YT8531S Gigabit Ethernet",
-		.get_features	= yt8521_get_features,
-		.probe		= yt8521_probe,
-		.read_page	= yt8521_read_page,
-		.write_page	= yt8521_write_page,
-		.get_wol	= ytphy_get_wol,
-		.set_wol	= ytphy_set_wol,
-		.config_aneg	= yt8521_config_aneg,
-		.aneg_done	= yt8521_aneg_done,
-		.config_init	= yt8521_config_init,
-		.read_status	= yt8521_read_status,
-		.soft_reset	= yt8521_soft_reset,
-		.suspend	= yt8521_suspend,
-		.resume		= yt8521_resume,
-	},
-	{
-		PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
-		.name			= "YT8821 2.5Gbps PHY",
-		.get_features		= yt8821_get_features,
-		.read_page		= yt8521_read_page,
-		.write_page		= yt8521_write_page,
-		.get_wol		= ytphy_get_wol,
-		.set_wol		= ytphy_set_wol,
-		.config_aneg		= genphy_config_aneg,
-		.aneg_done		= yt8821_aneg_done,
-		.config_init		= yt8821_config_init,
-		.get_rate_matching	= yt8821_get_rate_matching,
-		.read_status		= yt8821_read_status,
-		.soft_reset		= yt8821_soft_reset,
-		.suspend		= yt8821_suspend,
-		.resume			= yt8821_resume,
+/**
+ * yt8824_read_page() - read reg page
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns current reg space of yt8824 (YT8824_RSSR_FIBER_SPACE/
+ * YT8824_RSSR_UTP_SPACE) or negative errno code
+ */
+static int yt8824_read_page(struct phy_device *phydev)
+{
+	int old_page;
+
+	old_page = ytphy_read_top_ext(phydev, YT8521_REG_SPACE_SELECT_REG);
+	if (old_page < 0)
+		return old_page;
+
+	return old_page & YT8824_RSSR_SPACE_MASK;
+};
+
+/**
+ * yt8824_write_page() - write reg page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8824_RSSR_FIBER_SPACE/YT8824_RSSR_UTP_SPACE) to write.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_write_page(struct phy_device *phydev, int page)
+{
+	int old_page;
+	u16 data;
+
+	old_page = ytphy_read_top_ext(phydev, YT8521_REG_SPACE_SELECT_REG);
+	data = old_page & (~YT8824_RSSR_SPACE_MASK);
+	data |= page;
+
+	return ytphy_write_top_ext(phydev, YT8521_REG_SPACE_SELECT_REG, data);
+};
+
+/**
+ * configuration YT8824 to one template test mode.
+ */
+static int yt8824_utp_invalid_test_mode_paged(struct phy_device *phydev,
+					      int reg_space)
+{
+	int old_page;
+	int ret = 0;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (reg_space == YT8824_RSSR_UTP_SPACE) {
+		ret = __phy_read_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL);
+		if (ret < 0)
+			goto err_restore_page;
+		ret &= ~YT8824_UTP_TEMPLATE_MODE_MASK;
+		ret |= YT8824_UTP_TEMPLATE_TEST_MODE1;
+		ret = __phy_write_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL, ret);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * enable YT8824 serdes isolate.
+ */
+static int yt8824_sds_isolate_paged(struct phy_device *phydev,
+				    int reg_space)
+{
+	int old_page;
+	int ret = 0;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+		ret = __phy_read(phydev, MII_BMCR);
+		if (ret < 0)
+			goto err_restore_page;
+		/* isolation */
+		ret |= BMCR_ISOLATE;
+		ret = __phy_write(phydev, MII_BMCR, ret);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * do YT8824 UTP softreset.
+ */
+static int yt8824_utp_softreset_paged(struct phy_device *phydev,
+				      int reg_space)
+{
+	int old_page;
+	int ret = 0;
+	int val;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+	if (reg_space == YT8824_RSSR_UTP_SPACE) {
+		ret = __phy_read(phydev, MII_BMCR);
+		if (ret < 0)
+			goto err_restore_page;
+		ret |= BMCR_RESET;
+		ret = __phy_write(phydev, MII_BMCR, ret);
+		if (ret < 0)
+			goto err_restore_page;
+		/* wait until softreset done. */
+		ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+					    50000, 600000, true);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * disable YT8824 template test mode.
+ */
+static int yt8824_utp_normal_test_mode_paged(struct phy_device *phydev,
+					     int reg_space)
+{
+	int old_page;
+	int ret;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (reg_space == YT8824_RSSR_UTP_SPACE) {
+		/* normal mode */
+		ret = __phy_read_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL);
+		if (ret < 0)
+			goto err_restore_page;
+		ret &= ~YT8824_UTP_TEMPLATE_MODE_MASK;
+		ret = __phy_write_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL, ret);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * disable YT8824 serdes isolate and sds softreset.
+ */
+static int yt8824_sds_isolate_and_softreset_paged(struct phy_device *phydev,
+						  int reg_space)
+{
+	int old_page;
+	int ret = 0;
+	int val;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+	if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+		ret = __phy_read(phydev, MII_BMCR);
+		if (ret < 0)
+			goto err_restore_page;
+		/* disable isolation */
+		ret &= ~BMCR_ISOLATE;
+		/* soft reset */
+		ret |= BMCR_RESET;
+		ret = __phy_write(phydev, MII_BMCR, ret);
+		if (ret < 0)
+			return ret;
+		/* wait until softreset done. */
+		ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+					    50000, 600000, true);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_soft_reset() - called to issue a PHY software reset
+ * @phydev: a pointer to a &struct phy_device
+ * for internal YT8824
+ * 1) set utp template test mode
+ * 2) utp restart
+ * 3) disable utp template test mode
+ * for external YT8824
+ * 1) set utp template test mode
+ * 2) enable serdes isolate
+ * 3) utp restart
+ * 4) disable utp template test mode
+ * 5) disable serdes isolate
+ * returns 0 or negative errno code
+ */
+static int yt8824_soft_reset(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		ret = yt8824_utp_softreset_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+	} else {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds isolation */
+		ret = yt8824_sds_isolate_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* utp soft reset */
+		ret = yt8824_utp_softreset_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds soft reset and disable isolation */
+		ret = yt8824_sds_isolate_and_softreset_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+/**
+ * external YT8824 init
+ */
+static int yt8824_config_init_paged(struct phy_device *phydev, int reg_space)
+{
+	struct yt8824_shared_priv *shared_priv;
+	int ret = 0, old_page;
+	u16 val_1, val_2, val_3, tmp;
+	u16 data = 0;
+	int port;
+
+	shared_priv = phy_package_get_priv(phydev);
+	if ((phydev->mdio.addr - shared_priv->offset) < 2)
+		port = 0;
+	else
+		port = 2;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+		/* read efuse */
+		val_1 = ytphy_read_top_ext(phydev, 0xa13e);
+		if (val_1 < 0)
+			goto err_restore_page;
+
+		val_2 = ytphy_read_top_ext(phydev, 0xa13f);
+		if (val_2 < 0)
+			goto err_restore_page;
+
+		val_3 = ytphy_read_top_ext(phydev, 0xa140);
+		if (val_3 < 0)
+			goto err_restore_page;
+
+		if (port == 0) {
+			/* Serdes optimization */
+			ret = ytphy_write_ext(phydev, 0x04be, 0x000d);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x049f, 0x7ded);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x04a9, 0x009f);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* analog CDR */
+			ret = ytphy_write_ext(phydev, 0x0406, 0x0800);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* optimized VCO */
+			ret = ytphy_write_ext(phydev, 0x0438, 0x9024);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0439, 0x00c0);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* optimized PLL lock */
+			ret = ytphy_read_ext(phydev, 0x0429);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret &= ~(BIT(13) | BIT(12));
+			tmp = (val_1 & (BIT(7) | BIT(6)) >> 6);
+			ret |= (tmp << 12);
+			ret = ytphy_write_ext(phydev, 0x0429, ret);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_read_ext(phydev, 0x0441);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret &= ~(BIT(1) | BIT(0));
+			tmp = (val_1 & (BIT(5) | BIT(4)) >> 4);
+			ret |= tmp;
+			ret = ytphy_write_ext(phydev, 0x0441, ret);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_read_ext(phydev, 0x042b);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret &= ~(BIT(13) | BIT(12));
+			tmp = (val_3 & (BIT(1) | BIT(0)));
+			ret |= (tmp << 12);
+			ret = ytphy_write_ext(phydev, 0x042b, ret);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x043a, 0x1006);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x042a, 0xf070);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* cable length threshold */
+			ret = ytphy_write_ext(phydev, 0x0491, 0x007f);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0492, 0x7f7f);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* Serdes training threshold */
+			ret = ytphy_write_ext(phydev, 0x0454, 0x0f14);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0497, 0x0a44);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* digital eye diagram of SerDes */
+			ret = ytphy_write_ext(phydev, 0x04cd, 0x0000);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* Serdes LDO */
+			ret = ytphy_read_ext(phydev, 0x04b5);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret &= ~(BIT(6) | BIT(5) | BIT(4));
+			tmp = (val_2 & (BIT(4) | BIT(3) | BIT(2)) >> 2);
+			ret |= (tmp << 4);
+			ret = ytphy_write_ext(phydev, 0x04b5, ret);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_read_ext(phydev, 0x04b4);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret &= ~(BIT(10) | BIT(9) | BIT(8));
+			tmp = (val_2 & (BIT(7) | BIT(6) | BIT(5)) >> 5);
+			ret |= (tmp << 8);
+			ret = ytphy_write_ext(phydev, 0x04b4, ret);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* optimized Serdes RX */
+			ret = ytphy_write_ext(phydev, 0x04af, 0x45e3);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x048a, 0x0fff);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0408, 0x7c00);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x04d6, 0x007f);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x044f, 0xff08);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* optimized Serdes TX */
+			ret = ytphy_write_ext(phydev, 0x048e, 0x7d00);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x000d, 0x0606);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* Serdes manual config */
+			ret = ytphy_write_ext(phydev, 0x04b0, 0x0804);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x04b1, 0x7074);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x04af, 0x45e7);
+			if (ret < 0)
+				goto err_restore_page;
+
+			/* restart calibration */
+			ret = ytphy_write_ext(phydev, 0x0003, 0x5603);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0492, 0x7fff);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x0492, 0x7f7f);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x2000, 0x0040);
+			if (ret < 0)
+				goto err_restore_page;
+
+			ret = ytphy_write_ext(phydev, 0x2000, 0x0000);
+			if (ret < 0)
+				goto err_restore_page;
+		}
+
+		/* TX preamble padded to 8; RX IPG always > 8 */
+		data &= ~YT8824_SDS_CFG_MIN_PRE_MASK;
+		data |= YT8824_SDS_TX_PRE_PADDING;
+		data |= YT8824_SDS_EN_FILL_PRE;
+		ret = __phy_write(phydev, MII_RESV1, data);
+		if (ret < 0)
+			goto err_restore_page;
+
+		data = __phy_read(phydev, MII_BMCR);
+		if (data < 0)
+			goto err_restore_page;
+		data |= BMCR_RESET;
+		data |= BMCR_ANENABLE;
+		ret = __phy_write(phydev, MII_BMCR, data);
+		if (ret < 0)
+			goto err_restore_page;
+	} else if (reg_space == YT8824_RSSR_UTP_SPACE) {
+		/* power down */
+		data = __phy_read(phydev, MII_BMCR);
+		if (data < 0)
+			goto err_restore_page;
+		data |= BMCR_PDOWN;
+		ret = __phy_write(phydev, MII_BMCR, data);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* pll calibration */
+		ret = ytphy_write_ext(phydev, 0x0001, 0x0003);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa20e, 0x0cba);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa20a, 0xc3f1);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa20c, 0x1620);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2b6, 0x0a00);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2b6, 0x0e00);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimization utp */
+		ret = ytphy_write_ext(phydev, 0x0001, 0x0003);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* enable nibble */
+		ret = ytphy_write_ext(phydev, 0xa003, 0x0003);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* idle err detect enable */
+		ret = ytphy_write_ext(phydev, 0x03d0, 0x5210);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized 2.5G long cable performance */
+		ret = ytphy_write_ext(phydev, 0x0372, 0x5038);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x037c, 0x6068);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0388, 0x00a0);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized fast retrain */
+		ret = ytphy_write_ext(phydev, 0x0359, 0x2140);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x000c, 0xc1a0);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* 2.5G template tone */
+		ret = ytphy_write_ext(phydev, 0xa2fa, 0x0083);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x04e2, 0x0149);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized 2.5G template */
+		ret = ytphy_write_ext(phydev, 0x047e, 0x3939);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x047f, 0x3939);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0480, 0x3939);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0481, 0x3939);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized 1000M cable length threshold */
+		ret = ytphy_write_ext(phydev, 0x0336, 0xab0a);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0340, 0x301d);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* 100M template amplitude */
+		ret = ytphy_write_ext(phydev, 0x046e, 0x4545);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x046f, 0x4545);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0470, 0x4545);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0471, 0x4545);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized 100M cable length threshold */
+		ret = ytphy_write_ext(phydev, 0x030b, 0xaa1d);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x071f, 0x0036);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* 10M template amplitude */
+		ret = ytphy_write_ext(phydev, 0x046b, 0x1818);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x046c, 0x1818);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized 10M cable length threshold */
+		ret = ytphy_write_ext(phydev, 0x0466, 0x6c6c);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0467, 0x6c6c);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0468, 0x6c6c);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0469, 0x6c6c);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimize utp 1000M performance */
+		ret = ytphy_write_ext(phydev, 0x034a, 0xff03);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x00f8, 0xb3ff);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0059, 0x4040);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x032c, 0x5094);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x032d, 0xd094);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x032e, 0x5308);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x0322, 0x6440);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x04d3, 0x5220);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x04d2, 0x5220);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized EMC CS */
+		ret = ytphy_write_ext(phydev, 0x00c8, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x00be, 0x6406);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0x037a, 0x40ff);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* optimized EMC RE */
+		ret = ytphy_write_ext(phydev, 0x0482, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2d5, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2d6, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2d7, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa2d8, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa218, 0x006e);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa01d, 0xfff0);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa01e, 0xfff0);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa01d, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, 0xa01e, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * internal YT8824 init
+ */
+static int yt8824_internal_config_init_paged(struct phy_device *phydev,
+					     int reg_space)
+{
+	struct yt8824_shared_priv *shared_priv;
+	int old_page;
+	int port = 0;
+	int ret = 0;
+	u16 data;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	shared_priv = phy_package_get_priv(phydev);
+	if ((phydev->mdio.addr - shared_priv->offset) < 2)
+		port = 0;
+	else
+		port = 2;
+	ret = ytphy_write_ext(phydev, 0x1, 0x3);
+	if (ret < 0)
+		goto err_restore_page;
+	data = __phy_read(phydev, MII_BMCR);
+	if (data < 0)
+		goto err_restore_page;
+	data |= BMCR_PDOWN;
+	ret = __phy_write(phydev, MII_BMCR, data);
+	if (ret < 0)
+		goto err_restore_page;
+	if (port == 0 || port == 2) {
+		ret = ytphy_write_ext(phydev, 0xa20e, 0xcba);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa20a, 0xc3f1);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa20c, 0x1620);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2b6, 0xa00);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2b6, 0xe00);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa003, 0x3);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+	ret = ytphy_write_ext(phydev, 0x3d0, 0x5210);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x372, 0x5038);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x37c, 0x6068);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x388, 0xa0);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x359, 0x2140);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_top_ext(phydev, 0xa2fa, 0x83);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x4e2, 0x149);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 2.5G tempate */
+	ret = ytphy_write_ext(phydev, 0x47e, 0x3939);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x47f, 0x3939);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x480, 0x3939);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x481, 0x3939);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 1000 cable length threshold */
+	ret = ytphy_write_ext(phydev, 0x336, 0xab0a);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x340, 0x301d);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 1000 performance */
+	ret = ytphy_write_ext(phydev, 0x34a, 0xff03);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0xf8, 0xb3ff);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x32c, 0x5094);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x32d, 0xd094);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x32e, 0x5308);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x322, 0x6440);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x4d3, 0x5220);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x4d2, 0x5220);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 100 tempate */
+	ret = ytphy_write_ext(phydev, 0x46e, 0x4545);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x46f, 0x4545);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x470, 0x4545);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x471, 0x4545);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 100 cable length threshold */
+	ret = ytphy_write_ext(phydev, 0x30b, 0xaa1d);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x71f, 0x36);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 10 tempate */
+	ret = ytphy_write_ext(phydev, 0x46b, 0x1818);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x46c, 0x1818);
+	if (ret < 0)
+		goto err_restore_page;
+	/* 10 tempate MAU*/
+	ret = ytphy_write_ext(phydev, 0x466, 0x6c6c);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x467, 0x6c6c);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x468, 0x6c6c);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x469, 0x6c6c);
+	if (ret < 0)
+		goto err_restore_page;
+	/* EMC CS */
+	ret = ytphy_write_ext(phydev, 0xc8, 0xfff);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0xbe, 0x6406);
+	if (ret < 0)
+		goto err_restore_page;
+	ret = ytphy_write_ext(phydev, 0x37a, 0x40ff);
+	if (ret < 0)
+		goto err_restore_page;
+	/* EMC RE*/
+	ret = ytphy_write_ext(phydev, 0x482, 0xffff);
+	if (ret < 0)
+		goto err_restore_page;
+	if (port == 0 || port == 2) {
+		ret = ytphy_write_ext(phydev, 0x482, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2d5, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2d6, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2d7, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa2d8, 0x1f1f);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa218, 0x6e);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa01d, 0xfff0);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa01e, 0xfff0);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa01d, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+		ret = ytphy_write_ext(phydev, 0xa01e, 0xffff);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+	ret = ytphy_write_ext(phydev, 0xc, 0x41a1);
+	if (ret < 0)
+		goto err_restore_page;
+	data = __phy_read_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL);
+	if (data < 0)
+		goto err_restore_page;
+	data &= ~YT8824_UTP_TEMPLATE_MODE_MASK;
+	data |= YT8824_UTP_TEMPLATE_TEST_MODE1;
+	ret = __phy_write_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL, data);
+	data = __phy_read(phydev, MII_BMCR);
+	if (data < 0)
+		goto err_restore_page;
+	data |= BMCR_RESET;
+	data |= BMCR_ANENABLE;
+	ret = __phy_write(phydev, MII_BMCR, data);
+	if (ret < 0)
+		goto err_restore_page;
+	data = __phy_read_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL);
+	if (data < 0)
+		goto err_restore_page;
+	data &= ~YT8824_UTP_TEMPLATE_MODE_MASK;
+	ret = __phy_write_mmd(phydev, 0x1, YT8824_UTP_TEMPLATE_MODE_CTRL, data);
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_config_init() - phy initializatioin
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Returns: 0 or negative errno code
+ */
+static int yt8824_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+		ret = yt8824_internal_config_init_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = yt8824_config_init_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+		ret = yt8824_config_init_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+	}
+	ret = yt8824_soft_reset(phydev);
+	if (ret < 0)
+		return ret;
+
+	phydev_dbg(phydev, "%s done, phy addr: %d\n",
+		   __func__, phydev->mdio.addr);
+
+	return 0;
+}
+
+static int yt8824_config_intr(struct phy_device *phydev)
+{
+	struct yt8824_shared_priv *shared_priv;
+	int ret = 0, old_page;
+	int port;
+
+	shared_priv = phy_package_get_priv(phydev);
+	if ((phydev->mdio.addr - shared_priv->offset) < 2)
+		port = 0;
+	else
+		port = 2;
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* top ext reg 0xa000
+		 * bit6 int_polarity 1'b0 low active, 1'b1 high active
+		 */
+		ret = ytphy_read_top_ext(phydev, YT8521_REG_SPACE_SELECT_REG);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_top_ext(phydev, YT8521_REG_SPACE_SELECT_REG, ret & (~BIT(6)));
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* top ext reg 0xa019
+		 * bit5 intr_phy_pulse_en 1'b0 level, 1'b1 pulse
+		 */
+		ret = ytphy_read_top_ext(phydev, 0xa019);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_top_ext(phydev, 0xa019, ret | BIT(5));
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* top ext reg 0xa018 phy_pulse_lth bit14:8
+		 * top ext reg 0xa019 timer_tick_sel bit8:7
+		 * bit14:8 phy_pulse_lth bit8:7 timer_tick_sel   pulse width
+		 * 0x007a		 0x0002		         10ms pulse width
+		 * 0x0064		 0x0001		         1ms pulse width
+		 * 0x004e		 0x0000		         100ms pulse width
+		 * 0x0009		 0x0000		         10ms pulse width(default)
+		 */
+		ret = ytphy_read_top_ext(phydev, 0xa018);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret &= ~0x7f00;
+		ret |= (0x0009 << 8);
+		ret = ytphy_write_top_ext(phydev, 0xa018, ret);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_read_top_ext(phydev, 0xa019);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret &= ~0x180;
+		ret |= (0x0000 << 7);
+		ret = ytphy_write_top_ext(phydev, 0xa019, ret);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* top ext reg 0xa01c interrupt state(Read Clear)
+		 * bit11 PHY3 interrupt 1: phy3 link up/down interrupt happened, 0: interrupt not happened
+		 * bit10 PHY2 interrupt 1: phy2 link up/down interrupt happened, 0: interrupt not happened
+		 * bit9  PHY1 interrupt 1: phy1 link up/down interrupt happened, 0: interrupt not happened
+		 * bit8  PHY0 interrupt 1: phy0 link up/down interrupt happened, 0: interrupt not happened
+		 *
+		 * bit7  PHY3 link down 1: phy3 link down interrupt happened, 0: interrupt not happened
+		 * bit6  PHY2 link down 1: phy2 link down interrupt happened, 0: interrupt not happened
+		 * bit5  PHY1 link down 1: phy1 link down interrupt happened, 0: interrupt not happened
+		 * bit4  PHY0 link down 1: phy0 link down interrupt happened, 0: interrupt not happened
+		 *
+		 * bit3  PHY3 link up 1: phy3 link up interrupt happened, 0: interrupt not happened
+		 * bit2  PHY2 link up 1: phy2 link up interrupt happened, 0: interrupt not happened
+		 * bit1  PHY1 link up 1: phy1 link up interrupt happened, 0: interrupt not happened
+		 * bit0  PHY0 link up 1: phy0 link up interrupt happened, 0: interrupt not happened
+		 */
+		ret = ytphy_read_top_ext(phydev, 0xa01c);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* top ext reg 0xa01a
+		 * bit5 intn_wol_mode 1'b1 enable intn_wol pin output
+		 * bit4 intn_mode 1'b1 enable intn pin output
+		 */
+		ret = ytphy_read_top_ext(phydev, 0xa01a);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret &= ~BIT(5);
+		ret |= BIT(4);
+		ret = ytphy_write_top_ext(phydev, 0xa01a, ret);
+		if (ret < 0)
+			goto err_restore_page;
+
+		/* top ext reg 0xa01b interrupt mask
+		 * bit11 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+		 * bit7 1'b1 enable interrupt signal(link down) output to interrupt pin
+		 * bit3 1'b1 enable interrupt signal(link up) output to interrupt pin
+		 *
+		 * bit10 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+		 * bit6 1'b1 enable interrupt signal(link down) output to interrupt pin
+		 * bit2 1'b1 enable interrupt signal(link up) output to interrupt pin
+		 *
+		 * bit9 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+		 * bit5 1'b1 enable interrupt signal(link down) output to interrupt pin
+		 * bit1 1'b1 enable interrupt signal(link up) output to interrupt pin
+		 *
+		 * bit8 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+		 * bit4 1'b1 enable interrupt signal(link down) output to interrupt pin
+		 * bit0 1'b1 enable interrupt signal(link up) output to interrupt pin
+		 */
+		ret = ytphy_read_top_ext(phydev, 0xa01b);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret |= (BIT(port + 8) | BIT(port + 4) | BIT(port));
+		ret = ytphy_write_top_ext(phydev, 0xa01b, ret);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+static irqreturn_t yt8824_handle_interrupt(struct phy_device *phydev)
+{
+	int ret = 0, old_page;
+
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	/* top ext reg 0xa01c interrupt state(Read Clear)
+	 * bit11 PHY3 interrupt 1: phy3 link up/down interrupt happened, 0: interrupt not happened
+	 * bit10 PHY2 interrupt 1: phy2 link up/down interrupt happened, 0: interrupt not happened
+	 * bit9  PHY1 interrupt 1: phy1 link up/down interrupt happened, 0: interrupt not happened
+	 * bit8  PHY0 interrupt 1: phy0 link up/down interrupt happened, 0: interrupt not happened
+	 * bit7  PHY3 link down 1: phy3 link down interrupt happened, 0: interrupt not happened
+	 * bit6  PHY2 link down 1: phy2 link down interrupt happened, 0: interrupt not happened
+	 * bit5  PHY1 link down 1: phy1 link down interrupt happened, 0: interrupt not happened
+	 * bit4  PHY0 link down 1: phy0 link down interrupt happened, 0: interrupt not happened
+	 * bit3  PHY3 link up 1: phy3 link up interrupt happened, 0: interrupt not happened
+	 * bit2  PHY2 link up 1: phy2 link up interrupt happened, 0: interrupt not happened
+	 * bit1  PHY1 link up 1: phy1 link up interrupt happened, 0: interrupt not happened
+	 * bit0  PHY0 link up 1: phy0 link up interrupt happened, 0: interrupt not happened
+	 */
+	ret = ytphy_read_top_ext(phydev, 0xa01c);
+	if (ret < 0)
+		goto err_restore_page;
+
+	phy_trigger_machine(phydev);
+
+err_restore_page:
+	phy_restore_page(phydev, old_page, ret);
+	if (ret > 0)
+		return IRQ_HANDLED;
+	else
+		return IRQ_NONE;
+}
+
+/**
+ * yt8824_config_aneg()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_aneg_done(struct phy_device *phydev)
+{
+	int link = 0;
+	int old_page;
+	int ret = 0;
+
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	link = !!(__phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG) &
+			     YTPHY_SSR_LINK);
+
+	phydev_dbg(phydev, "%s, phy addr: %d, link_utp: %d\n",
+		   __func__, phydev->mdio.addr, link);
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_read_status_paged() -  determines the speed and duplex of one page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8824_RSSR_FIBER_SPACE/YT8824_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 1 (utp or fiber link),0 (no link) or negative errno code
+ */
+static int yt8824_read_status_paged(struct phy_device *phydev, int page,
+				    int *status, int *lpa)
+{
+	int old_page;
+	int ret = 0;
+
+	page &= YT8824_RSSR_SPACE_MASK;
+	old_page = phy_select_page(phydev, page);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	ret = __phy_read(phydev, MII_LPA);
+	*lpa = ret;
+	if (ret < 0)
+		goto err_restore_page;
+
+	ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+	*status = ret;
+	if (ret < 0)
+		goto err_restore_page;
+
+	ret = !!(*status & YTPHY_SSR_LINK);
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_read_status() -  determines the negotiated speed and duplex
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_read_status(struct phy_device *phydev)
+{
+	int link;
+	int lpa;
+	int val;
+
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+	phydev->link = 0;
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+
+	link = yt8824_read_status_paged(phydev,
+					YT8824_RSSR_UTP_SPACE, &val, &lpa);
+	if (link < 0)
+		return link;
+
+	if (link) {
+		phydev->link = 1;
+		phydev->pause = !!(lpa & BIT(10));
+		phydev->asym_pause = !!(lpa & BIT(11));
+
+		/* update speed & duplex */
+		yt8821_adjust_status(phydev, val);
+	} else {
+		phydev->link = 0;
+		phydev->pause = 0;
+		phydev->asym_pause = 0;
+		phydev->speed = SPEED_UNKNOWN;
+		phydev->duplex = DUPLEX_UNKNOWN;
+	}
+
+	return 0;
+}
+
+/**
+ * yt8824_utp_power_on()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_utp_power_on(struct phy_device *phydev,
+			       int reg_space)
+{
+	int old_page;
+	int ret;
+
+	old_page = phy_select_page(phydev, reg_space);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	ret = __phy_read(phydev, MII_BMCR);
+	if (ret < 0)
+		goto err_restore_page;
+
+	ret &= ~BMCR_PDOWN;
+	ret &= ~BMCR_ISOLATE;
+
+	ret = __phy_write(phydev, MII_BMCR, ret);
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_utp_power_down()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_utp_power_down(struct phy_device *phydev,
+				 int reg_space)
+{
+	int ret = 0, old_page;
+
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	ret = __phy_read(phydev, MII_BMCR);
+	if (ret < 0)
+		goto err_restore_page;
+
+	ret = __phy_write(phydev, MII_BMCR, ret | BMCR_PDOWN);
+	if (ret < 0)
+		goto err_restore_page;
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_power_on()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ * need WA like softreset
+ */
+static int yt8824_power_on(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		/* utp power on */
+		ret = yt8824_utp_power_on(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+	} else {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds isolation */
+		ret = yt8824_sds_isolate_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* utp power on */
+		ret = yt8824_utp_power_on(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds soft reset and disable isolation */
+		ret = yt8824_sds_isolate_and_softreset_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int yt8824_resume(struct phy_device *phydev)
+{
+	return yt8824_power_on(phydev);
+}
+
+static int yt8824_power_down(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		/* utp power down */
+		ret = yt8824_utp_power_down(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+	} else {
+		/* invalid test mode */
+		ret = yt8824_utp_invalid_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds isolation */
+		ret = yt8824_sds_isolate_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* utp power down */
+		ret = yt8824_utp_power_down(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* normal mode */
+		ret = yt8824_utp_normal_test_mode_paged(phydev, YT8824_RSSR_UTP_SPACE);
+		if (ret < 0)
+			return ret;
+
+		/* sds soft reset and disable isolation */
+		ret = yt8824_sds_isolate_and_softreset_paged(phydev,
+							     YT8824_RSSR_FIBER_SPACE);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int yt8824_suspend(struct phy_device *phydev)
+{
+	return yt8824_power_down(phydev);
+}
+
+/**
+ * yt8824_config_aneg()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_config_aneg(struct phy_device *phydev)
+{
+	int phy_ctrl = 0;
+	int old_page;
+	int ret;
+
+	old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+			      phydev->advertising))
+		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+				     MDIO_AN_10GBT_CTRL,
+				     MDIO_AN_10GBT_CTRL_ADV2_5G,
+				     phy_ctrl);
+	if (ret)
+		goto err_restore_page;
+
+	ret = genphy_config_aneg(phydev);
+	if (ret)
+		goto err_restore_page;
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+static int yt8824_phy_package_probe_once(struct phy_device *phydev)
+{
+	struct yt8824_shared_priv *priv = phy_package_get_priv(phydev);
+	struct device_node *np = phy_package_get_node(phydev);
+	const char *interface_mode_name;
+
+	priv->interface_mode = PHY_INTERFACE_MODE_NA;
+	priv->offset = INTERNAL_PHY8824_TOP_EXTEND_OFFSET;
+	if (!of_property_read_string(np, "motorcomm,interface-mode",
+				     &interface_mode_name)) {
+		if (!strcasecmp(interface_mode_name,
+				phy_modes(PHY_INTERFACE_MODE_USXGMII))) {
+			priv->interface_mode = PHY_INTERFACE_MODE_USXGMII;
+			priv->offset = EXTERNAL_PHY8824_TOP_EXTEND_OFFSET;
+		} else if (!strcasecmp(interface_mode_name,
+				       phy_modes(PHY_INTERFACE_MODE_INTERNAL))) {
+			priv->interface_mode = PHY_INTERFACE_MODE_INTERNAL;
+			priv->offset = INTERNAL_PHY8824_TOP_EXTEND_OFFSET;
+		} else {
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/**
+ * yt8824_probe()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct yt8824_shared_priv *shared_priv;
+	struct yt8521_priv *priv;
+	int ret;
+
+	ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv));
+	if (ret)
+		return ret;
+
+	if (phy_package_probe_once(phydev)) {
+		ret = yt8824_phy_package_probe_once(phydev);
+		if (ret)
+			return ret;
+	}
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static struct phy_driver motorcomm_phy_drvs[] = {
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
+		.name		= "YT8511 Gigabit Ethernet",
+		.config_init	= yt8511_config_init,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+		.read_page	= yt8511_read_page,
+		.write_page	= yt8511_write_page,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
+		.name		= "YT8521 Gigabit Ethernet",
+		.get_features	= yt8521_get_features,
+		.probe		= yt8521_probe,
+		.read_page	= yt8521_read_page,
+		.write_page	= yt8521_write_page,
+		.get_wol	= ytphy_get_wol,
+		.set_wol	= ytphy_set_wol,
+		.config_aneg	= yt8521_config_aneg,
+		.aneg_done	= yt8521_aneg_done,
+		.config_init	= yt8521_config_init,
+		.read_status	= yt8521_read_status,
+		.soft_reset	= yt8521_soft_reset,
+		.suspend	= yt8521_suspend,
+		.resume		= yt8521_resume,
+		.led_hw_is_supported = yt8521_led_hw_is_supported,
+		.led_hw_control_set = yt8521_led_hw_control_set,
+		.led_hw_control_get = yt8521_led_hw_control_get,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8522),
+		.name		= "YT8522 100 Megabit Ethernet",
+		.config_aneg	= genphy_config_aneg,
+		.config_init	= yt8522_config_init,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
+		.name		= "YT8531 Gigabit Ethernet",
+		.probe		= yt8531_probe,
+		.config_init	= yt8531_config_init,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+		.get_wol	= ytphy_get_wol,
+		.set_wol	= yt8531_set_wol,
+		.link_change_notify = yt8531_link_change_notify,
+		.led_hw_is_supported = yt8521_led_hw_is_supported,
+		.led_hw_control_set = yt8521_led_hw_control_set,
+		.led_hw_control_get = yt8521_led_hw_control_get,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
+		.name		= "YT8531S Gigabit Ethernet",
+		.get_features	= yt8521_get_features,
+		.probe		= yt8521_probe,
+		.read_page	= yt8521_read_page,
+		.write_page	= yt8521_write_page,
+		.get_wol	= ytphy_get_wol,
+		.set_wol	= ytphy_set_wol,
+		.config_aneg	= yt8521_config_aneg,
+		.aneg_done	= yt8521_aneg_done,
+		.config_init	= yt8521_config_init,
+		.read_status	= yt8521_read_status,
+		.soft_reset	= yt8521_soft_reset,
+		.suspend	= yt8521_suspend,
+		.resume		= yt8521_resume,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
+		.name			= "YT8821 2.5Gbps PHY",
+		.get_features		= yt8821_get_features,
+		.read_page		= yt8521_read_page,
+		.write_page		= yt8521_write_page,
+		.get_wol		= ytphy_get_wol,
+		.set_wol		= ytphy_set_wol,
+		.config_aneg		= genphy_config_aneg,
+		.aneg_done		= yt8821_aneg_done,
+		.config_init		= yt8821_config_init,
+		.get_rate_matching	= yt8821_get_rate_matching,
+		.read_status		= yt8821_read_status,
+		.soft_reset		= yt8821_soft_reset,
+		.suspend		= yt8821_suspend,
+		.resume			= yt8821_resume,
+	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8824),
+		.name			= "YT8824 Quad Ports 2.5Gbps Ethernet",
+		.get_features		= yt8821_get_features,
+		.read_page		= yt8824_read_page,
+		.write_page		= yt8824_write_page,
+		.config_intr            = yt8824_config_intr,
+		.handle_interrupt	= yt8824_handle_interrupt,
+		.probe		        = yt8824_probe,
+		.config_aneg		= yt8824_config_aneg,
+		.aneg_done		= yt8824_aneg_done,
+		.config_init		= yt8824_config_init,
+		.read_status		= yt8824_read_status,
+		.soft_reset		= yt8824_soft_reset,
+		.suspend		= yt8824_suspend,
+		.resume			= yt8824_resume,
 	},
 };
 
 module_phy_driver(motorcomm_phy_drvs);
 
-MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 PHY driver");
+MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821/8824 PHY driver");
 MODULE_AUTHOR("Peter Geis");
 MODULE_AUTHOR("Frank");
+MODULE_AUTHOR("Kyle");
 MODULE_LICENSE("GPL");
 
 static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
@@ -3161,6 +4792,7 @@ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8821) },
+	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8824) },
 	{ /* sentinel */ }
 };
 
-- 
2.25.1


^ permalink raw reply related

* Re: [PATCH net-next 0/3] net: nexthop: per-nexthop UDP dst port for fdb (VXLAN) nexthops
From: Jack Ma @ 2026-07-17  6:07 UTC (permalink / raw)
  To: Ido Schimmel, netdev
  Cc: David Ahern, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Jack Ma
In-Reply-To: <20260716113321.GA1636541@shredder>

On Thu, Jul 16, 2026 at 02:33:21PM +0300, Ido Schimmel wrote:
> OK, I understand. Each tenant has its own VNI, but it can have multiple
> HA sets on the same node and you want to distinguish between them using
> the UDP destination port.

Yes, that is exactly it.

> I don't particularly like NHA_FDB_PORT because it's an FDB-specific
> encapsulation attribute and these attributes naturally belong under
> NHA_ENCAP. That being said, I fail to see a better alternative given
> that FDB nexthops don't use NHA_ENCAP / LWT infra and probably
> shouldn't. [...]
> If you are going to post v2, please provide a more detailed motivation
> in the cover letter.

Understood, and I agree with the tension. I would not move FDB nexthops
onto NHA_ENCAP / LWT either, so I will keep the FDB-specific surface as
small as I can (see the VNI answer below). v2 will carry a fuller
motivation in the cover letter.

> In an earlier reply you wrote "shared inner MAC/IP". The "MAC" part was
> intentional because you need to load balance pure L2 traffic? IOW, if
> your traffic is only L3, can we do the load balancing using multipath
> routes in the overlay instead of using FDB nexthop groups?

Yes, the MAC was intentional, but not because the payload is pure L2,
it can be IP. The reason is that the whole HA set is addressed as one
anycast identity, a single overlay IP and MAC. That is deliberate. One
stable address keeps failover transparent, we just drop a dead replica
from the group with no client re-resolution. The address is also
consumed one layer up as a single stable endpoint, so giving each
replica its own IP would push multi-address handling and health
checking into that consumer, which is not available to us today.

> Related: What is the use case for NHA_FDB_VNI? In my experience, the VNI
> is only translated when routing traffic between tenants. Why do you need
> this in an FDB nexthop?

No strong use case, and your read matches mine. I will drop the
NHA_FDB_VNI mention from v2 rather than carry a speculative attribute
with no user.

Thanks again for the careful review.

Jack

^ permalink raw reply

* Re: [v2 net-next] net: rnpgbe: Delete a null pointer check in rnpgbe_rm_adapter()
From: Markus Elfring @ 2026-07-17  6:10 UTC (permalink / raw)
  To: Andrew Lunn, netdev, kernel-janitors
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	MD Danish Anwar, Michael Grzeschik, Paolo Abeni,
	Uwe Kleine-König, Vadim Fedorenko, Yibo Dong, LKML,
	Dan Carpenter
In-Reply-To: <2d60fe2b-06cf-4c35-98a0-fc0f8034180b@lunn.ch>

>> The check for the pointer “mucse” was finally identified as undesirable.
> I'm not sure finally is appropriate. It was one of the first questions
> i asked. Also, i expect anybody looking at a Coccinelle report to take
> the time to understand the code and decide what the correct fix is. We
> have too many developers doing the minimum to make the tool happy,
> without actually thinking.
> 
> Please also include the reasoning behind this. The commit message is
> all about "Why?" Why is it undesirable?
Why could a redundant sanity check be tolerated for a while?

Regards,
Markus

^ permalink raw reply

* [PATCH] selftests/net: use ARRAY_SIZE macro in skf_net_off.c
From: longlong yan @ 2026-07-17  6:25 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, horms, shuah, netdev,
	linux-kselftest, linux-kernel
  Cc: longlong yan

The ARRAY_SIZE macro is more compact and more formal in linux source.

Fixes: fcd7132cb1f9 ("selftests/net: test sk_filter support for SKF_NET_OFF on frags")
Signed-off-by: longlong yan <yanlonglong@kylinos.cn>
---
 tools/testing/selftests/net/skf_net_off.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/net/skf_net_off.c b/tools/testing/selftests/net/skf_net_off.c
index 1fdf61d6cd7f..d4e38d326c39 100644
--- a/tools/testing/selftests/net/skf_net_off.c
+++ b/tools/testing/selftests/net/skf_net_off.c
@@ -46,6 +46,7 @@
 #include <sys/types.h>
 #include <sys/uio.h>
 #include <unistd.h>
+#include "kselftest.h"
 
 static bool cfg_do_filter;
 static bool cfg_do_frags;
@@ -92,7 +93,7 @@ static void sk_set_filter(int fd)
 	};
 
 	struct sock_fprog filter = {
-		sizeof(filter_code) / sizeof(filter_code[0]),
+		ARRAY_SIZE(filter_code),
 		filter_code,
 	};
 
@@ -159,7 +160,7 @@ static void tun_write(int fd)
 	iov[4].iov_base = &payload;
 	iov[4].iov_len  = sizeof(payload);
 
-	ret = writev(fd, iov, sizeof(iov) / sizeof(iov[0]));
+	ret = writev(fd, iov, ARRAY_SIZE(iov));
 	if (ret <= 0)
 		error(1, errno, "writev");
 }
@@ -183,7 +184,7 @@ static void raw_read(int fd)
 	iov[1].iov_len = sizeof(payload);
 
 	msg.msg_iov = iov;
-	msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+	msg.msg_iovlen = ARRAY_SIZE(iov);
 
 	ret = recvmsg(fd, &msg, 0);
 	if (ret <= 0)
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v2 0/3] af_unix: Fix priority inversion issue
From: Nam Cao @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
  Cc: Nam Cao

Hi,

While auditing AF_UNIX sockets, I noticed that the sendmsg() code paths may
block on the garbage collector running as workqueue. This can cause
priority inversion and latency for real-time users.

The implementation does kindly avoid blocking "sane users". However, it is
impossible to tell whether the kernel's definition of "sane users"
accurately describes all users out there.

Digging into history and figuring out the reasons why sendmsg() needs to
wait for garbage collector, it is determined that those reasons no longer
apply.

The first patch resolves another issue reported by Sashiko in v1, paves the
way for the second patch to remove the block. The last one is a simple
post-cleanup.

v2:
  - Add patch [1/3]
  - Rebase the other two patches onto the new patch
  - Change commit message to be more precise

Nam Cao (3):
  af_unix: Schedule the garbage collector at task exit
  af_unix: Do not wait for garbage collector in sendmsg()
  af_unix: Clean up unix_schedule_gc()

 include/net/af_unix.h |  5 +++++
 kernel/exit.c         |  2 ++
 net/unix/af_unix.c    |  2 +-
 net/unix/af_unix.h    |  1 -
 net/unix/garbage.c    | 16 +---------------
 5 files changed, 9 insertions(+), 17 deletions(-)

-- 
2.47.3


^ permalink raw reply

* [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit
From: Nam Cao @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
  Cc: Nam Cao
In-Reply-To: <cover.1784268931.git.namcao@linutronix.de>

When a task exits while still having dead cyclic reference AF_UNIX sockets,
those sockets stay behind indefinitely until the garbage collector gets
scheduled by an unrelated reason. This can be observed with the program
below.

Resolve this issue by scheduling the garbage collector during task exit,
after the task's file descriptors have been closed.

static int send_fd(int unix_fd, int fd)
{
        struct msghdr msgh;
        struct cmsghdr *cmsg;
        char buf[CMSG_SPACE(sizeof(fd))];

        memset(&msgh, 0, sizeof(msgh));

        memset(buf, 0, sizeof(buf));
        msgh.msg_control = buf;
        msgh.msg_controllen = sizeof(buf);

        cmsg = CMSG_FIRSTHDR(&msgh);
        cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;

        msgh.msg_controllen = cmsg->cmsg_len;

        memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
        return sendmsg(unix_fd, &msgh, 0);
}

int main(int argc, char *argv[])
{
	int fd[2];
	int i;

	for (int n = 0; n < 100; ++n) {
		if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fd) == -1)
			goto out_error;

		for (i = 0; i < 100; ++i) {
			if (send_fd(fd[0], fd[0]) == -1)
				goto out_error;

			if (send_fd(fd[1], fd[1]) == -1)
				goto out_error;
		}
	}

	return 0;

out_error:
	fprintf(stderr, "error: %s\n", strerror(errno));
}

Signed-off-by: Nam Cao <namcao@linutronix.de>
---
 include/net/af_unix.h | 5 +++++
 kernel/exit.c         | 2 ++
 net/unix/af_unix.h    | 1 -
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 34f53dde65ce..686f6f1d1c21 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -14,11 +14,16 @@
 
 #if IS_ENABLED(CONFIG_UNIX)
 struct unix_sock *unix_get_socket(struct file *filp);
+void unix_schedule_gc(struct user_struct *user);
 #else
 static inline struct unix_sock *unix_get_socket(struct file *filp)
 {
 	return NULL;
 }
+
+static inline void unix_schedule_gc(struct user_struct *user)
+{
+}
 #endif
 
 struct unix_address {
diff --git a/kernel/exit.c b/kernel/exit.c
index 2c0b1c02920f..ba9404844523 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -71,6 +71,7 @@
 #include <linux/unwind_deferred.h>
 #include <linux/uaccess.h>
 #include <linux/pidfs.h>
+#include <net/af_unix.h>
 
 #include <uapi/linux/wait.h>
 
@@ -1002,6 +1003,7 @@ void __noreturn do_exit(long code)
 	exit_sem(tsk);
 	exit_shm(tsk);
 	exit_files(tsk);
+	unix_schedule_gc(NULL); /* Must be after exit_files() */
 	exit_fs(tsk);
 	if (group_dead)
 		disassociate_ctty(1);
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3a3..fc4c59893124 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -30,7 +30,6 @@ void unix_update_edges(struct unix_sock *receiver);
 int unix_prepare_fpl(struct scm_fp_list *fpl);
 void unix_destroy_fpl(struct scm_fp_list *fpl);
 void unix_peek_fpl(struct scm_fp_list *fpl);
-void unix_schedule_gc(struct user_struct *user);
 
 /* SOCK_DIAG */
 long unix_inq_len(struct sock *sk);
-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg()
From: Nam Cao @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
  Cc: Nam Cao
In-Reply-To: <cover.1784268931.git.namcao@linutronix.de>

AF_UNIX sockets' sendmsg() schedules and blocks on the garbage collector if
user has too many inflight unix sockets and there is cyclic reference in
the system. This causes real-time issues, as cyclic reference can be
created by any task in the system, and high priority tasks who do need to
send lots of AF_UNIX sockets get blocked by the garbage collector which
runs as workqueue, causing a priority inversion scenario.

The reason for blocking on garbage collector goes back to 2008, when
it was reported that "Local/unprivileged users can cause soft lockups
and take out system processes by triggering the OOM killer":
https://bugzilla.redhat.com/show_bug.cgi?id=470201

The soft lockup was because a process can keep queueing AF_UNIX sockets to
another process that is exiting. Back in 2008, the garbage collector was
run synchronously by the exiting process, therefore keep queueing AF_UNIX
sockets blocks that process from exiting.

The solution to that issue was forcing sendmsg() to wait for ongoing
garbage collector.

The OOM killer issue was brought up again in 2010:
https://lore.kernel.org/lkml/AANLkTi=Q967xpX0KLMwX-=_4_1AKO5wjHEuJ1TrNjCj9@mail.gmail.com/

To resolve that report, beside blocking on the garbage collector, sendmsg()
also schedules the garbage collector if the number of inflight AF_UNIX
sockets in the system is too high.

Then in 2015, once again, the OOM killer problem was brought up:
https://lore.kernel.org/lkml/20151228141435.GA13351@1wt.eu/

That time, the issue was resolved by disallowing a user from having more
inflight AF_UNIX sockets than their RLIMIT_NOFILE. That was done by commit
712f4aad406b ("unix: properly account for FDs passed over unix sockets")
and commit 415e3d3e90ce ("unix: correctly track in-flight fds in sending
process user_struct").

Now, sendmsg() does not have to block on the garbage collector anymore,
because:

  - The OOM killer issue has already been addressed by checking
    RLIMIT_NOFILE.

  - The soft lockup issue is no longer relevant, because the garbage
    collector now runs asynchronously since commit d9f21b361333 ("af_unix:
    Try to run GC async.")

Therefore, remove that to prevent priority inversion. Running all the
reproducers from the mentioned bug reports after this patch, no problem is
observed.

Signed-off-by: Nam Cao <namcao@linutronix.de>
---
 net/unix/garbage.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 0783555e2526..f180c59b3da9 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -300,8 +300,6 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)
 	if (!fpl->edges)
 		goto err;
 
-	unix_schedule_gc(fpl->user);
-
 	return 0;
 
 err:
-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc()
From: Nam Cao @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
  Cc: Nam Cao
In-Reply-To: <cover.1784268931.git.namcao@linutronix.de>

unix_schedule_gc() is only called with NULL as an argument.

Simplify by deleting the parameter.

Signed-off-by: Nam Cao <namcao@linutronix.de>
---
 include/net/af_unix.h |  4 ++--
 kernel/exit.c         |  2 +-
 net/unix/af_unix.c    |  2 +-
 net/unix/garbage.c    | 14 +-------------
 4 files changed, 5 insertions(+), 17 deletions(-)

diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 686f6f1d1c21..1dfdc0720052 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -14,14 +14,14 @@
 
 #if IS_ENABLED(CONFIG_UNIX)
 struct unix_sock *unix_get_socket(struct file *filp);
-void unix_schedule_gc(struct user_struct *user);
+void unix_schedule_gc(void);
 #else
 static inline struct unix_sock *unix_get_socket(struct file *filp)
 {
 	return NULL;
 }
 
-static inline void unix_schedule_gc(struct user_struct *user)
+static inline void unix_schedule_gc(void)
 {
 }
 #endif
diff --git a/kernel/exit.c b/kernel/exit.c
index ba9404844523..764a911f111e 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1003,7 +1003,7 @@ void __noreturn do_exit(long code)
 	exit_sem(tsk);
 	exit_shm(tsk);
 	exit_files(tsk);
-	unix_schedule_gc(NULL); /* Must be after exit_files() */
+	unix_schedule_gc(); /* Must be after exit_files() */
 	exit_fs(tsk);
 	if (group_dead)
 		disassociate_ctty(1);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..759db734a866 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -733,7 +733,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
 
 	/* ---- Socket is dead now and most probably destroyed ---- */
 
-	unix_schedule_gc(NULL);
+	unix_schedule_gc();
 }
 
 struct unix_peercred {
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index f180c59b3da9..d46aeb9d2051 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -635,23 +635,11 @@ static void unix_gc(struct work_struct *work)
 
 static DECLARE_WORK(unix_gc_work, unix_gc);
 
-#define UNIX_INFLIGHT_SANE_USER		(SCM_MAX_FD * 8)
-
-void unix_schedule_gc(struct user_struct *user)
+void unix_schedule_gc(void)
 {
 	if (READ_ONCE(unix_graph_state) == UNIX_GRAPH_NOT_CYCLIC)
 		return;
 
-	/* Penalise users who want to send AF_UNIX sockets
-	 * but whose sockets have not been received yet.
-	 */
-	if (user &&
-	    READ_ONCE(user->unix_inflight) < UNIX_INFLIGHT_SANE_USER)
-		return;
-
 	if (!READ_ONCE(gc_in_progress))
 		queue_work(system_dfl_wq, &unix_gc_work);
-
-	if (user && READ_ONCE(unix_graph_cyclic_sccs))
-		flush_work(&unix_gc_work);
 }
-- 
2.47.3


^ permalink raw reply related

* RE: [External Mail] Re: [PATCH v4 2/7] net: wwan: t9xx: Add control plane transaction layer
From: Wu. JackBB (GSM) @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Simon Horman
  Cc: loic.poulain@oss.qualcomm.com, ryazanov.s.a@gmail.com,
	johannes@sipsolutions.net, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, wen-zhi.huang@mediatek.com,
	shi-wei.yeh@mediatek.com, Minano.tseng@mediatek.com,
	matthias.bgg@gmail.com, angelogioacchino.delregno@collabora.com,
	corbet@lwn.net, skhan@linuxfoundation.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260716093355.231047-1-horms@kernel.org>

Hi Simon,

> > +int mtk_ctrl_init(struct mtk_md_dev *mdev)
> > +{
> > +	struct mtk_ctrl_blk *ctrl_blk;
> > +	ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
>
> [Severity: Medium]
> Does this patch actually implement the TX and RX services described
> in the commit message?

We will rewrite the commit message to accurately describe
what this patch does — it introduces the control plane framework
and data structures, not the TX/RX service implementations.

> > +	return LE32_TO_U32(cpu_to_le32(hw_bits));
> > +}
>
> [Severity: High]
> Does this code apply a double byte-swap on big-endian architectures?

T9XX currently only targets x86 (little-endian) platforms where
cpu_to_le32() is a no-op. The Kconfig enforces "depends on PCI &&
ACPI" which restricts this to x86 platforms. On little-endian, no
byte-swap occurs at any point, so ffs() operates on the correct
value.

Thanks.

Jack Wu
    

^ permalink raw reply

* Re: [RFC PATCH 06/10] net: pcs: xpcs: improve SGMII AN state handling for Rockchip RK3568
From: Coia Prant @ 2026-07-17  6:28 UTC (permalink / raw)
  To: Jiawen Wu
  Cc: Andrew Lunn, kuba, davem, edumazet, pabeni, andrew+netdev, robh,
	krzk+dt, heiko, netdev, linux-rockchip, devicetree,
	linux-arm-kernel, linux-phy, Mengyuan Lou
In-Reply-To: <000c01dd1593$2ac0b0f0$804212d0$@trustnetic.com>

Jiawen Wu <jiawenwu@trustnetic.com> 于2026年7月17日周五 10:23写道:
>
> On Wed, Jul 15, 2026 7:05 AM, Coia Prant wrote:
> > Andrew Lunn <andrew@lunn.ch> 于2026年7月15日周三 06:44写道:
> > >
> > > On Wed, Jul 15, 2026 at 03:08:34AM +0800, Coia Prant wrote:
> > > > Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > >
> > > You do not appear to Cc: the Wangxun NIC people. It would be good to
> > > have there comments on this change.
> >
> > I apologize; the output from get_maintainer.pl is very long. I’ve
> > heard that having too many recipients can cause the PATCH to be
> > rejected by the LKML mail server.
> >
> > I have added Wangxun maintainer (Jiawen Wu <jiawenwu@trustnetic.com>
> > and Mengyuan Lou <mengyuanlou@net-swift.com>) to the CC list.
> >
> > > > Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> > > > Signed-off-by: Coia Prant <coiaprant@gmail.com>
> > >
> > > Please don't mix fixed and new code. Is this a real fix? Should it be
> > > back ported to stable?
> >
> > I am not sure if this is a specific characteristic of Wangxun NICs, as
> > I do not have any available for testing.
> >
> > The behavior of the Rockchip DW XPCS IP core matches what is described
> > in the commit message (even though phylink brings the link to down
> > based on the phydev link status).
> >
> > This appears to be a bug (at least on Rockchip platforms) or Wangxun
> > NICs features.
> >
> > However, I cannot confirm whether Wangxun NICs behave the same way.
> > Therefore, I have kept their code as is for now.
> >
> > Could a Wangxun NICs maintainer provide some feedback based on testing?
> >
> > If we can confirm that this is indeed a bug, I can submit a separate fix.
> >
> > I would greatly appreciate it.
> >
> > Thanks.
>
> Hi Coia,
>
> I have tested this patch and it works on Wangxun NICs, thanks.
>
> The commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode")
> is too long ago for me to recall exactly what that thought was.
>
> The log shows:
>
> "On this device, CL37_ANSGM_STS (bit[4:1] of VR_MII_AN_INTR_STS) indicates
> the status received from remote link during the auto-negotiation, and
> self-clear after the auto-negotiation is complete.
> Meanwhile, CL37_ANCMPLT_INTR will be set to 1, to indicate CL37 AN is
> complete. So add another way to get the state for CL37 SGMII."
>
> I tried to reproduce this issue, but it didn't seem to exist.
> In the current logic:
>
> link up -> read status from CL37_ANSGM_STS -> CL37_ANCMPLT_INTR not clear
> link down -> read status from BMCR -> CL37_ANCMPLT_INTR clear
>
> It also works, although CL37_ANCMPLT_INTR is not cleared every time, and AN
> restart is absent. But BMCR looks like it only wants to be return as 0, it is
> weird.
>
> So I think Wangxun NICs also can be applied to general code as well, for
> getting state in C37 SGMII mode.

Hi Jiawen,

Thanks so much for testing and confirming that the patch works on Wangxun
NICs. That's a huge relief.

You're right to ask about the CL37_ANCMPLT_INTR clear and ANRESTART. Let
me explain what I observed on RK3568.

The key difference is that on RK3568's XPCS, we're working in the MAC
side (TX_CONFIG = 0), whereas on Wangxun it seems you're in the PHY side
(TX_CONFIG = 1). This is based on the commit 2a22b7ae2fa3 where you set
TX_CONFIG = 1.

> However, I am wondering whether CL37_ANCMPLT_INTR clear and ANRESTART are
> necessary.

On the RK3568 (MAC side), according to the DesignWare XPCS TRM, the
hardware behavior is as follows:

- After a software reset (BMCR_RESET), ANENABLE is automatically set to 1
  (the hardware reset value). So auto-negotiation starts automatically.

- When the link is up, CL37_ANSGM_STS contains the negotiated speed/duplex
  and the link status.

- When the link goes down, CL37_ANSGM_STS becomes all zero, but
  CL37_ANCMPLT_INTR remains set to 1.

- Crucially, the PCS does **not** automatically restart AN when the link
  comes back up. Without an explicit ANRESTART, the link stays down
  forever.

So on RK3568, the ANRESTART is mandatory to recover from a link-down
event. Without it, the link never comes back up after a cable disconnect.

In my current logic:

1. If CL37_ANSGM_STS has valid speed/duplex bits, report the link as up
   and return.

2. If CL37_ANSGM_STS is zero but CL37_ANCMPLT_INTR is set, the link is
   down. We clear the interrupt and issue an ANRESTART to start a new
   negotiation.

This works reliably on RK3568 in SGMII MAC side mode.

Since you confirmed that the general code path also works on Wangxun, I
wonder: does the ANRESTART also work on your side? If yes, perhaps we can
make this the common path for all hardware, not just RK3568.

If you can test the ANRESTART logic on Wangxun and it works, I'd be happy
to submit a standalone fix that makes this the common code path.

Otherwise, the current approach (skipping the ANRESTART trigger on
Wangxun NICs in c37_sgmii_get_state) remains safe.

If you're comfortable with the change, I'd appreciate it if you could add
your Tested-by tag to the commit. I'll also make sure to include you on
the CC list for any future revisions or related fixes.

Thanks again for testing!

^ permalink raw reply

* [PATCH iproute2-next v2] ipmaddr: use RTM_GETMULTICAST to list multicast addresses
From: Yuyang Huang @ 2026-07-17  6:34 UTC (permalink / raw)
  To: Yuyang Huang; +Cc: David Ahern, netdev

Replace /proc/net/igmp and /proc/net/igmp6 parsing in "ip maddr show"
with RTM_GETMULTICAST dumps. The kernel dumps IPv6 multicast addresses
via netlink since the beginning, IPv4 since v6.15 (eb4e17a1d915), and
reports the group users count via IFA_MC_USERS since kernel commits
7cb8198761e6 and e1d0f3f08391.

The netlink result is only used when it carries the same information
as procfs: if the dump fails (e.g. no IPv4 dump support) or any entry
lacks IFA_MC_USERS, the result is discarded and the procfs parsers
run as before, so output is unchanged on older kernels.

Link-layer multicast addresses are still read from
/proc/net/dev_mcast as there is no netlink API for them.

When a device is given, its ifindex is passed in the dump request so
strict-check kernels filter the dump server side; received entries
are checked against the ifindex again for kernels that ignore the
request field. An unknown device keeps printing an empty list.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
change in v2:
- accept_maddr(): only accept RTM_NEWMULTICAST; the kernel never sends
  RTM_GETMULTICAST in dump replies

 include/libnetlink.h |   3 ++
 ip/ipmaddr.c         | 109 +++++++++++++++++++++++++++++++++++++++++--
 lib/libnetlink.c     |  26 +++++++++++
 3 files changed, 134 insertions(+), 4 deletions(-)

diff --git a/include/libnetlink.h b/include/libnetlink.h
index e91505d9..518b8714 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -62,6 +62,9 @@ typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen);
 int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
 		      req_filter_fn_t filter_fn)
 	__attribute__((warn_unused_result));
+int rtnl_mcaddrdump_req(struct rtnl_handle *rth, int family,
+			req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
 int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
 	__attribute__((warn_unused_result));
 int rtnl_routedump_req(struct rtnl_handle *rth, int family,
diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c
index 462b409e..16470655 100644
--- a/ip/ipmaddr.c
+++ b/ip/ipmaddr.c
@@ -27,6 +27,7 @@

 static struct {
 	char *dev;
+	int  index;
 	int  family;
 } filter;

@@ -207,6 +208,93 @@ static void read_igmp6(struct ma_info **result_p)
 	fclose(fp);
 }

+struct maddr_dump_ctx {
+	struct ma_info *list;
+	bool mc_users_missing;
+};
+
+static int maddr_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+
+	ifm->ifa_index = filter.index;
+
+	return 0;
+}
+
+static int accept_maddr(struct nlmsghdr *n, void *arg)
+{
+	struct maddr_dump_ctx *ctx = arg;
+	struct ifaddrmsg *ifm = NLMSG_DATA(n);
+	int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifm));
+	struct rtattr *tb[IFA_MAX + 1];
+	struct ma_info *ma;
+
+	if (n->nlmsg_type != RTM_NEWMULTICAST)
+		return 0;
+
+	if (len < 0)
+		return -1;
+
+	if (filter.index && filter.index != ifm->ifa_index)
+		return 0;
+
+	parse_rtattr(tb, IFA_MAX, IFA_RTA(ifm), len);
+
+	if (!tb[IFA_MULTICAST] ||
+	    RTA_PAYLOAD(tb[IFA_MULTICAST]) > sizeof(ma->addr.data))
+		return 0;
+
+	if (!tb[IFA_MC_USERS]) {
+		ctx->mc_users_missing = true;
+		return 0;
+	}
+
+	ma = calloc(1, sizeof(*ma));
+	if (ma == NULL)
+		return -1;
+
+	ma->index = ifm->ifa_index;
+	strlcpy(ma->name, ll_index_to_name(ifm->ifa_index), sizeof(ma->name));
+	ma->addr.family = ifm->ifa_family;
+	ma->addr.bytelen = RTA_PAYLOAD(tb[IFA_MULTICAST]);
+	ma->addr.bitlen = ma->addr.bytelen << 3;
+	memcpy(ma->addr.data, RTA_DATA(tb[IFA_MULTICAST]), ma->addr.bytelen);
+	ma->users = rta_getattr_u32(tb[IFA_MC_USERS]);
+	maddr_ins(&ctx->list, ma);
+
+	return 0;
+}
+
+static int read_maddr_netlink(int family, struct ma_info **result_p)
+{
+	struct maddr_dump_ctx ctx = {};
+	struct ma_info *ma;
+	int err;
+
+	rth.flags |= RTNL_HANDLE_F_SUPPRESS_NLERR;
+	err = rtnl_mcaddrdump_req(&rth, family,
+				  filter.index ? maddr_dump_filter : NULL);
+	if (err >= 0)
+		err = rtnl_dump_filter(&rth, accept_maddr, &ctx);
+	rth.flags &= ~RTNL_HANDLE_F_SUPPRESS_NLERR;
+
+	/* Kernels that dump multicast addresses but do not report the
+	 * users count via IFA_MC_USERS cannot replace procfs.
+	 */
+	if (err < 0 || ctx.mc_users_missing) {
+		maddr_clear(ctx.list);
+		return -1;
+	}
+
+	while ((ma = ctx.list) != NULL) {
+		ctx.list = ma->next;
+		maddr_ins(result_p, ma);
+	}
+
+	return 0;
+}
+
 static void print_maddr(FILE *fp, struct ma_info *list)
 {
 	print_string(PRINT_FP, NULL, "\t", NULL);
@@ -291,12 +379,25 @@ static int multiaddr_list(int argc, char **argv)
 		argv++; argc--;
 	}

+	if (filter.dev) {
+		filter.index = ll_name_to_index(filter.dev);
+		/* an unknown device has no multicast addresses */
+		if (!filter.index) {
+			print_mlist(stdout, NULL);
+			return 0;
+		}
+	}
+
 	if (!filter.family || filter.family == AF_PACKET)
 		read_dev_mcast(&list);
-	if (!filter.family || filter.family == AF_INET)
-		read_igmp(&list);
-	if (!filter.family || filter.family == AF_INET6)
-		read_igmp6(&list);
+	if (!filter.family || filter.family == AF_INET) {
+		if (read_maddr_netlink(AF_INET, &list) < 0)
+			read_igmp(&list);
+	}
+	if (!filter.family || filter.family == AF_INET6) {
+		if (read_maddr_netlink(AF_INET6, &list) < 0)
+			read_igmp6(&list);
+	}
 	print_mlist(stdout, list);
 	maddr_clear(list);
 	return 0;
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 8905e297..edf3a8ba 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -336,6 +336,32 @@ int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
 	return send(rth->fd, &req, sizeof(req), 0);
 }

+int rtnl_mcaddrdump_req(struct rtnl_handle *rth, int family,
+			req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifaddrmsg ifm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+		.nlh.nlmsg_type = RTM_GETMULTICAST,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifm.ifa_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
 int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
 {
 	struct {
--
2.43.0


^ permalink raw reply related

* RE: [External Mail] Re: [PATCH v4 3/7] net: wwan: t9xx: Add control DMA interface
From: Wu. JackBB (GSM) @ 2026-07-17  6:37 UTC (permalink / raw)
  To: Simon Horman
  Cc: loic.poulain@oss.qualcomm.com, ryazanov.s.a@gmail.com,
	johannes@sipsolutions.net, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, wen-zhi.huang@mediatek.com,
	shi-wei.yeh@mediatek.com, Minano.tseng@mediatek.com,
	matthias.bgg@gmail.com, angelogioacchino.delregno@collabora.com,
	corbet@lwn.net, skhan@linuxfoundation.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260716093406.231071-1-horms@kernel.org>

Hi Simon,

> > +static void mtk_cldma_tx_done_work(...)
> > +	if (!req->data_vm_addr || (req->gpd->tx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO))
>
> [Severity: High]
> Is a dma_rmb() needed after checking the hardware ownership flag?

The GPD descriptors are allocated from a coherent DMA pool
(dma_pool_zalloc), so CPU cache coherency is guaranteed by the DMA
mapping. A dma_rmb() is not needed because coherent memory ensures
the CPU always observes the latest values written by the device.

Additionally, there is an rmb() before this check that ensures the
HWO flag is read before any subsequent field accesses.

> > +static void mtk_cldma_rx_done_work(...)
> > +	if (req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO)
> > +		break;
> > +	mtk_cldma_rx_skb_adjust(mdev, rxq, req);
>
> [Severity: High]
> Similarly, would we need a dma_rmb() here before attempting to read the
> receive length out of the descriptor in mtk_cldma_rx_skb_adjust?

Same reasoning as above. GPD descriptors use coherent DMA memory
(dma_pool_zalloc), so no additional memory barrier is needed.

> > +	ret = mtk_cldma_reload_rx_skb(mdev, rxq, req);
> > +	if (ret)
> > +		goto out;
>
> [Severity: High]
> If mtk_cldma_reload_rx_skb fails due to memory pressure, it jumps to out
> without advancing rxq->free_idx. Will this cause the worker to permanently
> stall the queue?

If mtk_cldma_reload_rx_skb() fails, the code jumps to "out" which
unmasks the interrupt. On the next hardware interrupt, rx_done_work
runs again and retries from the same free_idx. This creates a retry
loop that depends on memory becoming available. Under sustained
memory pressure the RX queue stalls but recovers once memory is
freed. This is acceptable for a control plane interface with small,
infrequent messages.

> > +static void mtk_cldma_txq_free(...)
> > +	kfree(req->bd_dsc_pool);
> > +	kfree(txq->req_pool);
> > +	kfree(txq);
>
> [Severity: Critical]
> Does this incorrectly free memory managed by devres?

We will convert all four from devm_kcalloc to kcalloc in v5. These
are runtime-managed resources (allocated on queue open, freed on
queue close/teardown), so they should use kcalloc/kfree rather
than devres.

> > +static int mtk_cldma_reload_rx_skb(...)
> > +	dev_kfree_skb_any(bd_dsc->skb);
>
> [Severity: High]
> Can this lead to a double free?

We will add bd_dsc->skb = NULL after dev_kfree_skb_any()
in the error path to prevent double free during driver teardown.

> > +static int mtk_cldma_txbuf_set(...)
> > +	req->data_dma_addr = dma_map_single(mdev->dev, skb->data,
> > +					skb->len, DMA_TO_DEVICE);
>
> [Severity: High]
> Does this code safely handle non-linear SKBs?

When nr_bds == 0, the SKB is always linear — it is allocated
internally via __dev_alloc_skb() with data copied via skb_put().
This is a control plane interface where SKBs are constructed
internally (AT commands, MBIM control messages), not received
from the network stack. Paged fragments are never used.

When nr_bds > 0, fragmentation uses skb_shinfo(skb)->frag_list
(linked SKBs), not paged fragments (skb_shinfo(skb)->frags).

> > +static int mtk_cldma_submit_tx(...)
> > +	req->data_vm_addr = skb->data;
> > +	wmb();
> > +	req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
>
> [Severity: High]
> Could there be a race condition here with mtk_cldma_tx_done_work?

No race exists. tx_done_work checks:
  if (!req->data_vm_addr || (gpd_flags & CLDMA_GPD_FLAG_HWO))
      break;

It only proceeds when data_vm_addr is non-NULL AND HWO is cleared.
In submit_tx, data_vm_addr is set before HWO via wmb(). After
data_vm_addr is set but before HWO, tx_done_work sees HWO still
set (hardware hasn't completed DMA) and breaks. The rmb() in
tx_done_work ensures HWO is read first. tx_done_work only
processes this req after hardware clears HWO upon DMA completion.

> > +static void mtk_pci_remove(struct pci_dev *pdev)
> > +	pci_clear_master(pdev);
> > +	mtk_pci_dev_exit(mdev);
>
> [Severity: High]
> Is it intended to call pci_clear_master before mtk_pci_dev_exit?

Before this point, mtk_pci_pldr() has already power-cycled the
modem firmware via ACPI PXP._OFF/_ON. After PLDR, the modem is
in a fresh boot state with no active DMA. The CLDMA queues are
idle — there are no pending hardware DMA transactions.

pci_clear_master() disables bus mastering as a safety measure
before teardown. Since the hardware is already reset and idle,
this is the intended ordering.

> > +	err = mtk_cldma_submit_tx(trans->dev, skb);
> > +	if (err) {
> > +		if (err == -EAGAIN)
> > +			return;
>
> [Severity: High]
> If mtk_cldma_submit_tx returns -EAGAIN ... could this cause the
> mtk_ctrl_trb_thread loop to spin continuously without sleeping?

The -EAGAIN path returns from mtk_ctrl_trb_handler() to
mtk_ctrl_trb_thread(), which calls wait_event_interruptible().
After -EAGAIN, the skb remains on the list (non-empty), but the
CLDMA ring is full. The ring drains as hardware completes DMA,
triggering tx_done_work which calls wake_up on the trb_waitq.
The kthread sleeps until hardware completion wakes it — no
busy-spin occurs.

> > +	wait_event_interruptible(srv->trb_waitq,
> > +				!mtk_ctrl_chs_is_busy_or_empty(srv) ||
>
> [Severity: High]
> Can this cause an infinite loop if a signal is delivered to the thread?

Kernel threads in this driver are not targeted by user signals.
The kthread_stop and kthread_should_park mechanisms are the
primary lifecycle management, and both are checked in the wait
condition. This pattern is consistent with other WWAN drivers
(e.g., t7xx) and widely used across drivers/net/ where
wait_event_interruptible is the standard choice for kthread
wait loops.

> > +	radix_tree_for_each_slot(slot, &trans->queue_tbl, &iter, 0) {
> > +		queue = radix_tree_deref_slot(slot);
> > +		radix_tree_delete(&trans->queue_tbl, iter.index);
>
> [Severity: High]
> Is it safe to iterate over the radix tree and call radix_tree_delete
> without holding rcu_read_lock?

This function is only called during teardown after all users have
been stopped (FSM shutdown, kthreads stopped, queues drained).
There is no concurrent access to the radix tree at this point.

The tree was initialized with INIT_RADIX_TREE (not RCU-tagged),
and we are the sole accessor during teardown. The cursor-based
iteration is safe for single-threaded deletion.

Thanks.

Jack Wu

^ permalink raw reply

* RE: [External Mail] Re: [PATCH v4 1/7] net: wwan: t9xx: Add PCIe core
From: Wu. JackBB (GSM) @ 2026-07-17  6:25 UTC (permalink / raw)
  To: Simon Horman
  Cc: loic.poulain@oss.qualcomm.com, ryazanov.s.a@gmail.com,
	johannes@sipsolutions.net, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, wen-zhi.huang@mediatek.com,
	shi-wei.yeh@mediatek.com, Minano.tseng@mediatek.com,
	matthias.bgg@gmail.com, angelogioacchino.delregno@collabora.com,
	corbet@lwn.net, skhan@linuxfoundation.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260716093324.230974-1-horms@kernel.org>

Hi Simon,

> > +static irqreturn_t mtk_pci_irq_msix(int irq, void *data)
> > +{
> > +	if (unlikely(!irq_state) ||
> > +	    unlikely(!((irq_state & GENMASK(priv->irq_cnt - 1, 0)) &
> > +		      irq_desc->msix_bits)))
> > +		return IRQ_NONE;
>
> [Severity: High]
> Does this logic silently drop hardware interrupts when fewer than 32
> vectors are allocated?

No interrupts are dropped. This is the MSI-X merged mode design.
When fewer vectors are allocated, mtk_pci_set_msix_merged() configures
the hardware to merge all interrupt sources into the available vectors.

For example, with irq_cnt=1, the hardware maps ALL sources (including
MHCCIF at hardware bit 28) to vector 0. The MSI-X status register
shows BIT(0), not BIT(28). So irq_desc[0].msix_bits = BIT(0), and
GENMASK(0,0) & BIT(0) & BIT(0) = 1 — the check passes correctly.

The GENMASK filters to allocated vector bits only, then checks if the
current handler's vector is set. This works for both full allocation
(32 vectors, 1:1 mapping) and merged mode (fewer vectors, N:1 mapping).

> > +		ret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,
> > +				      &irq_desc[i], irq_desc[i].name);
>
> [Severity: Medium]
> Could this trigger a -Wformat-security compiler warning?

We will change to pci_request_irq(..., "%s",
irq_desc[i].name) to avoid format string issues.

> > +	hw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);
> > +	mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM,
> > +			ffs(hw_bits) - 1);
>
> [Severity: Medium]
> Will this break channel index calculation on big-endian systems?

This driver targets MediaTek T9xx PCIe WWAN modems on x86/ARM64-LE
platforms only. On little-endian, cpu_to_le32() is a no-op and
LE32_TO_U32() is a simple cast — no byte-swap occurs. The Kconfig
enforces depends on PCI && ACPI, effectively restricting to LE
platforms where this hardware exists.

> [Severity: Medium]
> What happens if the driver attempts to send an unmapped channel?

All callers of mtk_pci_send_ext_evt() pass known-valid channel values
that are mapped in SET_HW_BITS. The is_power_of_2(ch) check at the
top of the function rejects invalid input. The FSM single-thread
design ensures all ext_evt sends go through a controlled path, so
an unmapped channel cannot reach ffs().

> > +	priv->ext_reg_base = priv->bar23_addr - ATR_PCIE_REG_TRSL_ADDR;
>
> [Severity: Medium]
> Is it safe to store an out-of-bounds pointer?

This is a standard pattern in PCIe MMIO drivers using Address
Translation Registers (ATR). The modem's internal register space
starts at address 0x0 in its own view, but the host accesses it
through BAR2/3 mapped at offset ATR_PCIE_REG_TRSL_ADDR (0x10000000).

Pre-computing ext_reg_base = bar23_addr - 0x10000000 allows using
modem register addresses directly: ioread32(ext_reg_base + reg_addr).
ext_reg_base is typed as void __iomem *, an opaque cookie for MMIO
access — never dereferenced as a regular pointer. The ioread32/
iowrite32 functions perform platform-specific MMIO access on it.
UBSAN does not flag __iomem arithmetic. This same pattern is used
by other kernel PCIe drivers (e.g., mtk_t7xx).

> > +static void mtk_pci_remove(struct pci_dev *pdev)
> > +{
> > +	if (mtk_pci_pldr(mdev)) { ... }
> > +	pci_clear_master(pdev);
> > +	mtk_pci_free_irq(mdev);
> > +	mtk_mhccif_exit(mdev);
>
> [Severity: High]
> Does this sequence result in the device being powered off before
> the OS interrupt handlers and workqueues are synchronized?

The device is not left powered off. mtk_pci_pldr() calls ACPI
PXP._OFF then PXP._ON — the _ON call restores power and brings
the PCIe link back up. After _ON returns, the device is powered
on and BARs are accessible.

The PLDR sequence is: (1) _OFF: power-cycle modem firmware,
(2) msleep for power drain, (3) _ON: restore power and link.
After step 3, the device is live with a fresh firmware state.

Additionally, before PLDR, mtk_pci_mask_irq() disables the MHCCIF
interrupt at hardware level, preventing new interrupts. Between
pci_clear_master and mtk_pci_free_irq, mtk_pci_dev_exit() tears
down FSM and trans_ctrl, ensuring no workqueues remain scheduled.

> > +	/* Request a slot reset. */
> > +	return PCI_ERS_RESULT_CAN_RECOVER;
>
> [Severity: Medium]
> Will returning PCI_ERS_RESULT_CAN_RECOVER actually abort the slot
> reset?

We will change to PCI_ERS_RESULT_DISCONNECT and update
the comment, since the driver does not currently support AER
recovery.

Thanks.

Jack Wu
    

^ permalink raw reply

* Re: [PATCH v4 3/3] drm/xe/xe_ras: Add error-event support for CRI
From: Raag Jadav @ 2026-07-17  6:40 UTC (permalink / raw)
  To: Tauro, Riana
  Cc: intel-xe, dri-devel, netdev, aravind.iddamsetty, anshuman.gupta,
	rodrigo.vivi, joonas.lahtinen, kuba, simona.vetter, airlied,
	pratik.bari, joshua.santosh.ranjan, ashwin.kumar.kulkarni,
	shubham.kumar, ravi.kishore.koppuravuri, maarten.lankhorst,
	mallesh.koujalagi, soham.purkait, Michal Wajdeczko
In-Reply-To: <918f2fc4-2aec-43a4-b52a-028011aa91b4@intel.com>

On Thu, Jul 16, 2026 at 03:27:59PM +0530, Tauro, Riana wrote:
> On 09-07-2026 15:34, Raag Jadav wrote:
> > On Wed, Jul 01, 2026 at 03:14:13PM +0530, Riana Tauro wrote:
> > > Add error-event support for Correctable errors in CRI. Report an error
> > > event to userspace for every component that has crossed the threshold on
> > > receiving an interrupt.
> > ...
> > 
> > > +static void ras_send_error_event(struct xe_device *xe, u8 severity, u8 component)
> > > +{
> > > +	u8 drm_severity, drm_component;
> > > +	u32 value;
> > > +	int ret;
> > > +
> > > +	drm_severity = xe_to_drm_ras_severity(severity);
> > > +	if (drm_severity == DRM_XE_RAS_ERR_SEV_MAX) {
> > > +		xe_warn(xe, "sysctrl: unexpected severity %u\n", severity);
> > This is uapi and not coming from sysctrl, so the message is a bit
> > misleading. But if at all it needs validation, it should be done in
> > drm_ras layer.
> 
> You mean in the ras_event function?  The parameters to this function are
> coming from sysctrl.
> So added sysctrl flag

We shouldn't be at this point without valid severity and component, and
this also contradicts the if condition which is for uapi.

> > > +		return;
> > > +	}
> > > +
> > > +	drm_component = xe_to_drm_ras_component(component);
> > > +	if (drm_component == DRM_XE_RAS_ERR_COMP_MAX) {
> > > +		xe_warn(xe, "sysctrl: unexpected component %u\n", component);
> > Ditto.
> > 
> > > +		return;
> > > +	}
> > > +
> > > +	ret = xe_ras_get_counter(xe, drm_severity, drm_component, &value);
> > No, instead of converting back and forth just do get_counter() using
> > sysctrl values and send_event() afterwards.
> 
> The reason for using this is to avoid unnecessary churn of moving the get
> counter above or use forward declaration.
> Yeah i can use that too directly.

You already get a counter as part of threshold crossed event, which you
can directly pass to get_counter() instead of dealing with severity and
component individually.

Raag

^ permalink raw reply

* Re: [BUG] vlan: skb_under_panic when toggling NETIF_F_HW_VLAN_CTAG_TX on lower device
From: xietangxin @ 2026-07-17  6:42 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
	netdev, linux-kernel, John Fastabend, Jesse Gross, gaoxingwang1,
	huyizhen
In-Reply-To: <CANn89iKOb0003aqdxJw65F0_xaLOkM-45MZv+CUmQ13-NGoGOA@mail.gmail.com>



On 7/16/2026 9:06 PM, Eric Dumazet wrote:
> On Thu, Jul 16, 2026 at 2:53 PM Eric Dumazet <edumazet@google.com> wrote:
>>
>> On Thu, Jul 16, 2026 at 2:20 PM xietangxin <xietangxin@h-partners.com> wrote:
>>>
>>> [BUG] vlan: skb_under_panic when toggling NETIF_F_HW_VLAN_CTAG_TX on lower device
>>>
>>> Hi all,
>>>
>>> We encountered a skb_under_panic triggered by toggling
>>> NETIF_F_HW_VLAN_CTAG_TX on the lower device while a VLAN device is
>>> up and sending traffic.
>>>
>>> Call trace
>>> ==========
>>>
>>>  skbuff: skb_under_panic: text:ffffc0d2900283d8 len:74 put:14
>>>   head:ffff334820249c00 data:ffff334820249bfe tail:0x48 end:0xc0 dev:vlan4
>>>  ------------[ cut here ]------------
>>>  kernel BUG at net/core/skbuff.c:116!
>>>  Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
>>>  Call trace:
>>>   skb_panic+0xcc/0xd0
>>>   __skb_checksum+0x0/0x480
>>>   eth_header+0x48/0x1a0
>>>   vlan_dev_hard_header+0xd0/0x284
>>>   neigh_connected_output+0x16c/0x20c
>>>   ip6_finish_output2+0x4b4/0xd74
>>>   __ip6_finish_output.part.0+0x1ac/0x3b0
>>>   ip6_finish_output+0x160/0x200
>>>   ip6_output+0x13c/0x294
>>>   ndisc_send_skb+0x41c/0x6f0
>>>   ndisc_send_rs+0xac/0x3b0
>>>   addrconf_rs_timer+0x42c/0x660
>>>   call_timer_fn+0x54/0x290
>>>   expire_timers+0x26c/0x420
>>>
>>> Reproducer
>>> ==========
>>>
>>>  # Create veth pair (NETIF_F_HW_VLAN_CTAG_TX is ON by default)
>>>  ip link add veth0 type veth peer name veth1
>>>  ip link set veth0 up
>>>  ip link set veth1 up
>>>
>>>  # Turn off HW VLAN TX offload on lower device
>>>  ethtool -K veth0 tx-vlan-hw-insert off
>>>
>>>  # Create VLAN device on veth0
>>>  # At this point: header_ops = &vlan_header_ops, hard_header_len = 18
>>>  ip link add link veth0 name veth0.10 type vlan id 10 reorder_hdr off
>>>  ip addr add 192.168.10.1/24 dev veth0.10
>>>  ip link set veth0.10 up
>>>
>>>  # Turn HW VLAN TX offload back ON on lower device
>>>  # This triggers NETDEV_FEAT_CHANGE -> vlan_transfer_features()
>>>  # hard_header_len changes from 18 to 14, but header_ops is NOT updated
>>>  ethtool -K veth0 tx-vlan-hw-insert on
>>>
>>>  # When a packet is sent through veth0.10
>>>  # - skb is allocated based on hard_header_len=14 -> ~16 bytes
>>>  # - vlan_dev_hard_header() pushes VLAN_HLEN(4) + ETH_HLEN(14) = 18 bytes
>>>  # - skb_under_panic!
>>>
>>>
>>> Any feedback or guidance would be greatly appreciated.
>>
>> This rings a bell, I thought we already fixed this issue long ago :/
>>
>> I would suggest we always add VLAN_HLEN even if not (yet) needed.
>>
>> diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c
>> index 2b74ed56eb166d52c3351768d9dfedc7b2c8ec2d..d7f90b3b2bb3aadb2c466891720a7f407d0bc34b
>> 100644
>> --- a/net/8021q/vlan.c
>> +++ b/net/8021q/vlan.c
>> @@ -323,10 +323,7 @@ static void vlan_transfer_features(struct net_device *dev,
>>
>>         netif_inherit_tso_max(vlandev, dev);
>>
>> -       if (vlan_hw_offload_capable(dev->features, vlan->vlan_proto))
>> -               vlandev->hard_header_len = dev->hard_header_len;
>> -       else
>> -               vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN;
>> +       vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN;
>>
>>  #if IS_ENABLED(CONFIG_FCOE)
>>         vlandev->fcoe_ddp_xid = dev->fcoe_ddp_xid;
>>
>> A kind-of-similar change is needed in net/8021q/vlan_dev.c
> 
> The second part would be a nice cleanup, unless I am missing something.
> 
> diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c
> index 7aa3af8b10ead0384bbb2e767883b63a37be5fa2..dfd907e2ede2dcb0b6120e1fa5b6bb4d30bfb28a
> 100644
> --- a/net/8021q/vlan_dev.c
> +++ b/net/8021q/vlan_dev.c
> @@ -496,26 +496,6 @@ static const struct header_ops vlan_header_ops = {
>         .parse_protocol = vlan_parse_protocol,
>  };
> 
> -static int vlan_passthru_hard_header(struct sk_buff *skb, struct
> net_device *dev,
> -                                    unsigned short type,
> -                                    const void *daddr, const void *saddr,
> -                                    unsigned int len)
> -{
> -       struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
> -       struct net_device *real_dev = vlan->real_dev;
> -
> -       if (saddr == NULL)
> -               saddr = dev->dev_addr;
> -
> -       return dev_hard_header(skb, real_dev, type, daddr, saddr, len);
> -}
> -
> -static const struct header_ops vlan_passthru_header_ops = {
> -       .create  = vlan_passthru_hard_header,
> -       .parse   = eth_header_parse,
> -       .parse_protocol = vlan_parse_protocol,
> -};
> -
>  static const struct device_type vlan_type = {
>         .name   = "vlan",
>  };
> @@ -575,13 +555,8 @@ static int vlan_dev_init(struct net_device *dev)
>  #endif
> 
>         dev->needed_headroom = real_dev->needed_headroom;
> -       if (vlan_hw_offload_capable(real_dev->features, vlan->vlan_proto)) {
> -               dev->header_ops      = &vlan_passthru_header_ops;
> -               dev->hard_header_len = real_dev->hard_header_len;
> -       } else {
> -               dev->header_ops      = &vlan_header_ops;
> -               dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN;
> -       }
> +       dev->header_ops      = &vlan_header_ops;
> +       dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN;
> 
>         dev->netdev_ops = &vlan_netdev_ops;
Hi Eric,

Thanks for the suggestion. I tested both parts of your patch locally
and can confirm the skb_under_panic no longer reproduces.

your diff is against an older tree. After commit
cd1c188db109 ("vlan: defer real device state propagation to netdev_work"),
vlan_transfer_features() has been moved to vlan_dev.c.

Could you post a formal patch?
-- 
Best regards,
Tangxin Xie


^ permalink raw reply

* [PATCH net-next v2 1/3] net: nexthop: add NHA_FDB_PORT for fdb nexthops
From: Jack Ma @ 2026-07-17  6:45 UTC (permalink / raw)
  To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
  Cc: netdev, linux-kernel, linux-kselftest, Jack Ma
In-Reply-To: <20260717-b4-vxlan-fdb-port-v2-0-f4862e8fe867@gmail.com>

Commit 1274e1cc4226 ("vxlan: ecmp support for mac fdb entries") lets a
single inner MAC be reached through a group of remote VTEPs, with the
kernel flow-hashing across the group members.  Each member carries its
own remote IP, but the UDP destination port is always taken from the
VXLAN device (vxlan->cfg.dst_port) and cannot be set per member.

Some deployments pack several receivers behind one underlay IP and tell
them apart by UDP port, so they need a per-nexthop destination port to
spread flows across (IP, port) tuples rather than IP alone.

Add a netlink attribute NHA_FDB_PORT (__be16, mirroring NDA_PORT) that
carries an optional UDP destination port on an fdb nexthop.  It is only
accepted together with NHA_FDB and NHA_GATEWAY; it is stored in struct
nh_info and echoed back on dump.  This patch is control-plane plumbing
only; the VXLAN datapath is wired up in a follow-up patch, so behaviour
is unchanged for now.

Signed-off-by: Jack Ma <jack4it@gmail.com>
---
 include/net/nexthop.h        |  2 ++
 include/uapi/linux/nexthop.h |  3 +++
 net/ipv4/nexthop.c           | 20 +++++++++++++++++++-
 3 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/include/net/nexthop.h b/include/net/nexthop.h
index 572e69cda..9c8227996 100644
--- a/include/net/nexthop.h
+++ b/include/net/nexthop.h
@@ -28,6 +28,7 @@ struct nh_config {
 	u8		nh_protocol;
 	u8		nh_blackhole;
 	u8		nh_fdb;
+	__be16		nh_fdb_port;
 	u32		nh_flags;
 
 	int		nh_ifindex;
@@ -63,6 +64,7 @@ struct nh_info {
 	u8			family;
 	bool			reject_nh;
 	bool			fdb_nh;
+	__be16			fdb_port;
 
 	union {
 		struct fib_nh_common	fib_nhc;
diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h
index bc49baf4a..e587bbf3b 100644
--- a/include/uapi/linux/nexthop.h
+++ b/include/uapi/linux/nexthop.h
@@ -83,6 +83,9 @@ enum {
 	/* u32; read-only; whether any driver collects HW stats */
 	NHA_HW_STATS_USED,
 
+	/* be16; UDP destination port for an fdb nexthop (e.g. VXLAN) */
+	NHA_FDB_PORT,
+
 	__NHA_MAX,
 };
 
diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c
index 6205bd57a..5b27cc9a9 100644
--- a/net/ipv4/nexthop.c
+++ b/net/ipv4/nexthop.c
@@ -39,6 +39,7 @@ static const struct nla_policy rtm_nh_policy_new[] = {
 	[NHA_ENCAP_TYPE]	= { .type = NLA_U16 },
 	[NHA_ENCAP]		= { .type = NLA_NESTED },
 	[NHA_FDB]		= { .type = NLA_FLAG },
+	[NHA_FDB_PORT]		= { .type = NLA_U16 },
 	[NHA_RES_GROUP]		= { .type = NLA_NESTED },
 	[NHA_HW_STATS_ENABLE]	= NLA_POLICY_MAX(NLA_U32, true),
 };
@@ -956,6 +957,9 @@ static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
 	} else if (nhi->fdb_nh) {
 		if (nla_put_flag(skb, NHA_FDB))
 			goto nla_put_failure;
+		if (nhi->fdb_port &&
+		    nla_put_be16(skb, NHA_FDB_PORT, nhi->fdb_port))
+			goto nla_put_failure;
 	} else {
 		const struct net_device *dev;
 
@@ -1055,6 +1059,9 @@ static size_t nh_nlmsg_size_single(struct nexthop *nh)
 		break;
 	}
 
+	if (nhi->fdb_nh)
+		sz += nla_total_size(2);	/* NHA_FDB_PORT */
+
 	if (nhi->fib_nhc.nhc_lwtstate) {
 		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
 		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
@@ -2956,8 +2963,10 @@ static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
 	nhi->family = cfg->nh_family;
 	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
 
-	if (cfg->nh_fdb)
+	if (cfg->nh_fdb) {
 		nhi->fdb_nh = 1;
+		nhi->fdb_port = cfg->nh_fdb_port;
+	}
 
 	if (cfg->nh_blackhole) {
 		nhi->reject_nh = 1;
@@ -3147,6 +3156,15 @@ static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
 		cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
 	}
 
+	if (tb[NHA_FDB_PORT]) {
+		if (!tb[NHA_FDB] || !tb[NHA_GATEWAY]) {
+			NL_SET_ERR_MSG(extack,
+				       "FDB port can only be set on fdb nexthops that have a gateway");
+			goto out;
+		}
+		cfg->nh_fdb_port = nla_get_be16(tb[NHA_FDB_PORT]);
+	}
+
 	if (tb[NHA_GROUP]) {
 		if (nhm->nh_family != AF_UNSPEC) {
 			NL_SET_ERR_MSG(extack, "Invalid family for group");

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v2 0/3] net: nexthop: per-nexthop UDP dst port for fdb (VXLAN) nexthops
From: Jack Ma @ 2026-07-17  6:45 UTC (permalink / raw)
  To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
  Cc: netdev, linux-kernel, linux-kselftest, Jack Ma

FDB nexthops let a VXLAN fdb entry point at a group of remote VTEPs, with the
kernel flow-hashing across the group (commit 1274e1cc4226 ("vxlan: ecmp support
for mac fdb entries")).  Each leg carries its own remote IP, but the UDP
destination port is always taken from the VXLAN device (vxlan->cfg.dst_port)
and cannot be set per leg.

This series adds an optional per-nexthop UDP destination port for fdb nexthops,
so a group's legs can share a remote IP and differ only in UDP port.

Motivation

The deployment runs an overlay in which each tenant's traffic is terminated by
a "forwarder": a pod that hosts the VXLAN VTEP, decapsulates the tenant's
overlay, and relays it to and from that tenant's workload.  Forwarders for many
different tenants are packed onto the same receiver node behind one
mesh-routable underlay IP, and are demultiplexed purely by UDP destination
port.  The host does a stateless outer-UDP demux by port; it never terminates
the tunnel:

  receiver node -- one mesh-routable underlay IP (NodeIP_A)
  +----------------------------------------------------+
  |  host netns: stateless outer-UDP demux by dst port |
  |              (host does NOT terminate the tunnel)  |
  |                                                    |
  |     dst :40000        dst :40001        dst :40002 |
  |         |                 |                 |      |
  |   +-----v----+      +-----v----+      +-----v----+ |
  |   | pod0 ns  |      | pod1 ns  |      | pod2 ns  | |
  |   | vxlan    |      | vxlan    |      | vxlan    | |
  |   | VTEP     |      | VTEP     |      | VTEP     | |
  |   | decap    |      | decap    |      | decap    | |
  |   +----------+      +----------+      +----------+ |
  +----------------------------------------------------+
  (up to ~10 forwarder pods packed per node)

The packed pods are unrelated: each belongs to a different tenant on its own
VXLAN VNI, so the per-pod UDP port is node-level demux, not an HA construct.
The host, which only demuxes outer UDP, never has to reason about tenancy.

A single forwarder is made highly available by running replicas.  The replicas
of one forwarder share a single anycast overlay identity: one inner MAC and IP.
Clients address that one identity, and a sender spreads flows across the live
replicas with an fdb nexthop group.  Failover is transparent: a dead replica is
just dropped from the group, with no client re-resolution or route change.  The
single identity is deliberate; the endpoint is consumed one layer up as a
single stable address, so giving each replica its own address would push
multi-address handling and health-checking up into that consumer.

Anti-affinity keeps the two replicas of one HA set on different nodes, so a
group's legs land on distinct node IPs.  But each leg is still reachable only
at (node IP, that pod's UDP port), so within one group the legs differ in IP
*and* port.  A group can already carry a distinct IP per leg, but it takes the
UDP port from the device (a single value), so it cannot send each leg to its
own port.  That is the gap this series closes.

Zooming into one forwarder pod, there is nothing for the host to load-balance:
the tunnel terminates on a vxlan device inside the pod's own netns, and the pod
reaches its tenant through a separate NIC:

  one forwarder pod -- its own netns, tenant VNI X
  +-------------------------------------------------+
  |                                                 |
  |   on/off-ramp NIC   <--- customer data plane    |
  |   |   on-ramp (ingress) / off-ramp (egress)     |
  |   |   inner packet                              |
  |   vxlan (VTEP)   encap / decap for VNI X,       |
  |   |              listens on this pod's UDP port |
  |   |   outer VXLAN UDP                           |
  |   eth0 (underlay)   NodeIP:port                 |
  |   |   to peer VTEPs over the                    |
  |   v   mesh underlay                             |
  |                                                 |
  +-------------------------------------------------+

Existing mechanisms do not fit this shape:

  - L3 multipath in the overlay needs each leg to be a distinct routable
    nexthop with its own address.  Since an HA set is a single anycast address
    by design, there are no distinct per-leg addresses to route over; the fdb
    nexthop group bridging to that shared MAC is what load-balances.
  - Host-side fan-out (XDP / TC / SO_REUSEPORT) assumes a shared host datapath
    that is not there.  SO_REUSEPORT balances sockets within one netns, but the
    receivers are in different netns (in fact different tenants).  An XDP/TC
    fan-out would require the host to terminate the tunnel and re-dispatch
    inner traffic across netns and VNI boundaries, i.e. become a VTEP, which
    puts the host into the tenant datapath and largely duplicates what an fdb
    nexthop group already does.
  - Demuxing on VNI instead of port (one shared 4789 socket, multiple vxlan
    devices differing only in VNI, moved into each pod's netns) works when the
    co-located pods have different VNIs.  It does not help two same-VNI HA sets
    on one node: their outer headers are identical, so the host would again
    have to terminate the tunnel to tell them apart.  It also costs packing
    density: with one shared underlay IP, VNI demux allows at most one VTEP per
    (VNI, node), so N same-VNI HA sets of two replicas need 2N nodes, whereas a
    per-pod port fits them on two nodes with anti-affinity preserved.

This series adds the attribute:

  - Patch 1 adds a netlink attribute NHA_FDB_PORT (__be16, mirroring NDA_PORT),
    stored in struct nh_info and echoed back on dump.  It is only accepted
    together with NHA_FDB and NHA_GATEWAY.  Control-plane only; datapath
    behaviour is unchanged.
  - Patch 2 wires it into the VXLAN datapath: vxlan_fdb_nh_path_select() sets
    rdst->remote_port to the selected leg's port.  vxlan_xmit_one() already
    prefers rdst->remote_port when non-zero and otherwise falls back to the
    device port, so nexthops without a port are unaffected (backward
    compatible).
  - Patch 3 adds a selftest.

On the uAPI: this does not add a new datapath concept.  A single fdb entry
already carries a per-destination UDP port (NDA_PORT), and vxlan_xmit_one()
already prefers rdst->remote_port when set.  NHA_FDB_PORT is the nexthop analog
of that existing attribute: control-plane only, no datapath change, and
backward compatible (a leg with no port falls back to the device port as
today).  It sits at the nexthop level rather than under NHA_ENCAP because fdb
nexthops do not use the NHA_ENCAP / LWT infrastructure.

Example:

  ip nexthop add id 1  via 192.0.2.10 fdb port 4789
  ip nexthop add id 2  via 192.0.2.10 fdb port 5789
  ip nexthop add id 10 group 1/2 fdb
  bridge fdb add 00:11:22:33:44:55 dev vxlan0 nhid 10

Both legs share gateway 192.0.2.10 and differ only in UDP port; the kernel
hashes flows across them.

Testing: kernel and iproute2 built on net-next.  The control-plane selftest
passes (6/6) and a datapath test (two netns, tcpdump on the underlay) confirms
outer traffic is hashed across both UDP ports.  The series is bisectable:
patches 1 and 2 each build individually.

A matching iproute2 change (the `ip nexthop ... fdb port N` keyword) is posted
separately to the iproute2 list.

Changes in v2:
- Expand the cover letter with the deployment model and the
  addressing-vs-load-balancing distinction that motivates the attribute, as
  requested (Ido Schimmel).
- Reword the problem statement: the UDP port is underlay demux (addressing),
  not what distinguishes otherwise-interchangeable load-balancing targets.
- Drop the speculative NHA_FDB_VNI follow-up note; there is no use case for a
  per-leg VNI in an fdb nexthop group.
- No functional change to the patches.

v1: https://lore.kernel.org/netdev/20260712191218.236-1-jack4it@gmail.com/

Signed-off-by: Jack Ma <jack4it@gmail.com>
---
Jack Ma (3):
      net: nexthop: add NHA_FDB_PORT for fdb nexthops
      vxlan: honor per-nexthop fdb destination port
      selftests: net: add coverage for fdb nexthop dst port

 include/net/nexthop.h                              |  7 +-
 include/net/vxlan.h                                |  5 +-
 include/uapi/linux/nexthop.h                       |  3 +
 net/ipv4/nexthop.c                                 | 20 +++++-
 tools/testing/selftests/net/Makefile               |  1 +
 .../testing/selftests/net/fib_nexthops_fdb_port.sh | 78 ++++++++++++++++++++++
 6 files changed, 111 insertions(+), 3 deletions(-)
---
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
change-id: 20260712-b4-vxlan-fdb-port-486bb215eab6

Best regards,
--  
Jack Ma <jack4it@gmail.com>


^ permalink raw reply

* [PATCH net-next v2 3/3] selftests: net: add coverage for fdb nexthop dst port
From: Jack Ma @ 2026-07-17  6:45 UTC (permalink / raw)
  To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
  Cc: netdev, linux-kernel, linux-kselftest, Jack Ma
In-Reply-To: <20260717-b4-vxlan-fdb-port-v2-0-f4862e8fe867@gmail.com>

Add fib_nexthops_fdb_port.sh, which exercises the NHA_FDB_PORT rules:
accept a port on an fdb nexthop that has a gateway and echo it back on
dump, reject it on non-fdb or gateway-less nexthops, allow a group whose
legs differ only in UDP port, and confirm a portless fdb nexthop omits
the attribute.  The test SKIPs cleanly on kernels or iproute2 without
NHA_FDB_PORT support.

Signed-off-by: Jack Ma <jack4it@gmail.com>
---
 tools/testing/selftests/net/Makefile               |  1 +
 .../testing/selftests/net/fib_nexthops_fdb_port.sh | 78 ++++++++++++++++++++++
 2 files changed, 79 insertions(+)

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae..c06eb4927 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -36,6 +36,7 @@ TEST_PROGS := \
 	fib_nexthop_multiprefix.sh \
 	fib_nexthop_nongw.sh \
 	fib_nexthops.sh \
+	fib_nexthops_fdb_port.sh \
 	fib_rule_tests.sh \
 	fib_tests.sh \
 	fin_ack_lat.sh \
diff --git a/tools/testing/selftests/net/fib_nexthops_fdb_port.sh b/tools/testing/selftests/net/fib_nexthops_fdb_port.sh
new file mode 100755
index 000000000..8b401c6d2
--- /dev/null
+++ b/tools/testing/selftests/net/fib_nexthops_fdb_port.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Control-plane selftest for per-nexthop VXLAN fdb destination port
+# (NHA_FDB_PORT).  Verifies the accept/reject rules and the dump roundtrip.
+# No datapath traffic here -- see tests/integ/vxlan-fdb-port-integ.sh for the
+# real forwarding test.
+#
+# Requires: patched kernel (NHA_FDB_PORT) and patched iproute2 (the "port"
+# keyword on "ip nexthop ... fdb").  SKIPs cleanly otherwise.
+
+set -u
+
+ksft_skip=4
+NS="nhfdbport-$$"
+IP="ip -netns $NS"
+ret=0
+
+log_test() {	# $1 actual_rc  $2 expected_rc  $3 name
+	if [ "$1" = "$2" ]; then
+		printf "TEST: %-58s [ OK ]\n" "$3"
+	else
+		printf "TEST: %-58s [FAIL] (rc=$1 want=$2)\n" "$3"
+		ret=1
+	fi
+}
+
+# passes (returns 0) iff the command FAILS
+expect_fail() {
+	if "$@" >/dev/null 2>&1; then return 1; else return 0; fi
+}
+
+cleanup() { ip netns del "$NS" 2>/dev/null; }
+
+command -v ip >/dev/null 2>&1 || { echo "SKIP: iproute2 not found"; exit $ksft_skip; }
+ip nexthop help 2>&1 | grep -q fdb || { echo "SKIP: no fdb nexthop support"; exit $ksft_skip; }
+
+trap cleanup EXIT
+cleanup
+ip netns add "$NS" || { echo "SKIP: cannot create netns"; exit $ksft_skip; }
+$IP link set lo up
+
+# Probe for "port" keyword + kernel NHA_FDB_PORT support; SKIP if missing.
+if ! $IP nexthop add id 1 via 10.0.0.1 fdb port 4790 2>/dev/null; then
+	echo "SKIP: 'ip nexthop ... fdb port' unsupported (needs patched kernel + iproute2)"
+	exit $ksft_skip
+fi
+log_test 0 0 "add fdb nexthop with port"
+
+# Dump roundtrip must echo the port back.
+$IP nexthop show id 1 | grep -qw "port 4790"
+log_test $? 0 "dump shows fdb port 4790"
+
+# Reject: port on a routed (non-fdb) nexthop.
+expect_fail $IP nexthop add id 2 via 10.0.0.1 dev lo port 4790
+log_test $? 0 "reject port on non-fdb nexthop"
+
+# Reject: fdb port without a gateway.
+expect_fail $IP nexthop add id 3 fdb port 4790
+log_test $? 0 "reject fdb port without gateway"
+
+# The HA case: a group whose legs share the gateway but differ in port.
+$IP nexthop add id 10 via 10.0.0.1 fdb port 4789 && \
+$IP nexthop add id 11 via 10.0.0.1 fdb port 5789 && \
+$IP nexthop add id 100 group 10/11 fdb
+log_test $? 0 "add fdb nexthop group with differing ports"
+
+# A fdb nexthop without a port must NOT emit one (backward compat).
+$IP nexthop add id 20 via 10.0.0.1 fdb
+$IP nexthop show id 20 | grep -qw "port"
+log_test $? 1 "fdb nexthop without port omits NHA_FDB_PORT"
+
+if [ $ret -eq 0 ]; then
+	echo "PASS: all NHA_FDB_PORT control-plane checks"
+else
+	echo "FAIL: one or more NHA_FDB_PORT checks failed"
+fi
+exit $ret

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v2 2/3] vxlan: honor per-nexthop fdb destination port
From: Jack Ma @ 2026-07-17  6:45 UTC (permalink / raw)
  To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
  Cc: netdev, linux-kernel, linux-kselftest, Jack Ma
In-Reply-To: <20260717-b4-vxlan-fdb-port-v2-0-f4862e8fe867@gmail.com>

When an fdb entry points at a nexthop group, vxlan_fdb_nh_path_select()
resolves the selected leg's remote IP but leaves the UDP destination port
at the device default (vxlan->cfg.dst_port).

Extend nexthop_path_fdb_result() to also return the selected nexthop's
NHA_FDB_PORT (0 when unset) and have vxlan_fdb_nh_path_select() store it
in rdst->remote_port.  vxlan_xmit_one() already prefers rdst->remote_port
when non-zero and falls back to the device port otherwise, so nexthops
without a port are unaffected.

This lets one fdb nexthop group load-balance a flow across legs that
share an underlay IP but differ in UDP destination port.

Signed-off-by: Jack Ma <jack4it@gmail.com>
---
 include/net/nexthop.h | 5 ++++-
 include/net/vxlan.h   | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/include/net/nexthop.h b/include/net/nexthop.h
index 9c8227996..7fb612a73 100644
--- a/include/net/nexthop.h
+++ b/include/net/nexthop.h
@@ -576,7 +576,8 @@ struct fib_nh_common *nexthop_fdb_nhc(struct nexthop *nh)
 }
 
 static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh,
-							    int hash)
+						    int hash,
+						    __be16 *fdb_port)
 {
 	struct nh_info *nhi;
 	struct nexthop *nhp;
@@ -585,6 +586,8 @@ static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh,
 	if (unlikely(!nhp))
 		return NULL;
 	nhi = rcu_dereference(nhp->nh_info);
+	if (fdb_port)
+		*fdb_port = nhi->fdb_port;
 	return &nhi->fib_nhc;
 }
 #endif
diff --git a/include/net/vxlan.h b/include/net/vxlan.h
index dfba89695..de41b3746 100644
--- a/include/net/vxlan.h
+++ b/include/net/vxlan.h
@@ -567,8 +567,9 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh,
 					    struct vxlan_rdst *rdst)
 {
 	struct fib_nh_common *nhc;
+	__be16 fdb_port = 0;
 
-	nhc = nexthop_path_fdb_result(nh, hash >> 1);
+	nhc = nexthop_path_fdb_result(nh, hash >> 1, &fdb_port);
 	if (unlikely(!nhc))
 		return false;
 
@@ -583,6 +584,8 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh,
 		break;
 	}
 
+	rdst->remote_port = fdb_port;
+
 	return true;
 }
 

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit
From: Nam Cao @ 2026-07-17  6:45 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, netdev, linux-kernel, linux-rt-devel
In-Reply-To: <d500ff36af658ad412e5d6f8d5665070359ef68e.1784268931.git.namcao@linutronix.de>

Nam Cao <namcao@linutronix.de> writes:
> @@ -1002,6 +1003,7 @@ void __noreturn do_exit(long code)
>  	exit_sem(tsk);
>  	exit_shm(tsk);
>  	exit_files(tsk);
> +	unix_schedule_gc(NULL); /* Must be after exit_files() */

Argh, I just realized that exit_files() is deferred to task work, so
this must be after exit_task_work().

I will send v3 in a few days, if there is no other comment.

Nam

^ permalink raw reply

* Re: [PATCH net-next 4/7] phonet: pep: do not write beyond optlen in getsockopt
From: Rémi Denis-Courmont @ 2026-07-17  6:50 UTC (permalink / raw)
  To: Breno Leitao, sdf, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Alexander Aring, Stefan Schmidt,
	Miquel Raynal, Remi Denis-Courmont, Rémi Denis-Courmont,
	John Fastabend, Sabrina Dubroca, Shuah Khan
  Cc: netdev, linux-kernel, linux-wpan, linux-kselftest, kernel-team
In-Reply-To: <20260716-getsockopt_phase4-v1-4-4f45cb12dce7@debian.org>



Le 16 juillet 2026 16:00:02 GMT+03:00, Breno Leitao <leitao@debian.org> a écrit :
>pep_getsockopt() clamps the reported length to the caller's buffer with
>min_t(), but then stores the value with put_user(val, (int __user *)
>optval), which always writes sizeof(int) bytes. A getsockopt() call with
>an optlen smaller than sizeof(int) thus reports the clamped length yet
>writes a full int, one to three bytes past the user buffer.
>
>Write the value with copy_to_user() bounded by len, so at most optlen
>bytes are copied, matching the length reported back to userspace.
>
>Fixes: 02a47617cdce ("Phonet: implement GPRS virtual interface over PEP socket")
>Signed-off-by: Breno Leitao <leitao@debian.org>

Acked-by: Rémi Denis-Courmont <remi@remlab.net>

^ permalink raw reply

* Re: [PATCH net-next 5/7] phonet: pep: convert getsockopt to sockopt_t
From: Rémi Denis-Courmont @ 2026-07-17  6:52 UTC (permalink / raw)
  To: Breno Leitao, sdf, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Alexander Aring, Stefan Schmidt,
	Miquel Raynal, Remi Denis-Courmont, Rémi Denis-Courmont,
	John Fastabend, Sabrina Dubroca, Shuah Khan
  Cc: netdev, linux-kernel, linux-wpan, linux-kselftest, kernel-team
In-Reply-To: <20260716-getsockopt_phase4-v1-5-4f45cb12dce7@debian.org>



Le 16 juillet 2026 16:00:03 GMT+03:00, Breno Leitao <leitao@debian.org> a écrit :
>Continue converting the proto-layer getsockopt callbacks to the
>sockopt_t interface, splitting pep_getsockopt() into a
>do_pep_getsockopt() helper that takes a sockopt_t.
>
>The thin pep_getsockopt() wrapper keeps its __user signature for now:
>it builds a user-backed sockopt_t with sockopt_init_user(), calls the
>helper, and writes the returned length back to optlen. The helper uses
>copy_to_iter() instead of copy_to_user(). No functional change.
>
>Signed-off-by: Breno Leitao <leitao@debian.org>

Acked-by: Rémi Denis-Courmont <remi@remlab.net>

^ permalink raw reply


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