AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/09 06:04 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The modified code is confined to the Realtek RTL8365MB DSA Ethernet switch driver (drivers/net/dsa/realtek/), which targets vendor-specific discrete switch ASICs connected over SMI or MDIO with Device Tree bindings. This hardware is not present or emulated in standard virtualized fuzzing environments (QEMU/KVM on amd64), making the code structurally unreachable.",
  "WorthFuzzing": false
}

1/1 2026/10/09 06:04 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 44792155c6a50691b5d2f3d6b7667973657bac0b\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Oct 9 06:04:47 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h\nnew file mode 100644\nindex 0000000000000..315de4c93c446\n--- /dev/null\n+++ b/drivers/net/dsa/realtek/rtl8365mb.h\n@@ -0,0 +1,14 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef _RTL8365MB_H\n+#define _RTL8365MB_H\n+\n+#include \"realtek.h\"\n+\n+enum rtl8365mb_family {\n+\tRTL8365MB_FAMILY_C,\n+\tRTL8365MB_FAMILY_D,\n+};\n+\n+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv);\n+\n+#endif /* _RTL8365MB_H */\ndiff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c\nindex da06f952a053d..eb71c89945c16 100644\n--- a/drivers/net/dsa/realtek/rtl8365mb_main.c\n+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c\n@@ -81,6 +81,7 @@\n  *  - RTL8367RB-VB\n  *  - RTL8367SB\n  *  - RTL8367S\n+ *  - RTL8367S-VB\n  *  - RTL8370MB\n  *  - RTL8310SR\n  *\n@@ -97,6 +98,7 @@\n #include \u003clinux/irqdomain.h\u003e\n #include \u003clinux/mii.h\u003e\n #include \u003clinux/mutex.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n #include \u003clinux/of_irq.h\u003e\n #include \u003clinux/regmap.h\u003e\n #include \u003clinux/if_bridge.h\u003e\n@@ -110,12 +112,14 @@\n #include \"rtl83xx.h\"\n #include \"rtl8365mb_l2.h\"\n #include \"rtl8365mb_vlan.h\"\n+#include \"rtl8365mb.h\"\n \n /* Family-specific data and limits */\n #define RTL8365MB_PHYADDRMAX\t\t7\n #define RTL8365MB_NUM_PHYREGS\t\t32\n #define RTL8365MB_PHYREGMAX\t\t(RTL8365MB_NUM_PHYREGS - 1)\n #define RTL8365MB_MAX_NUM_PORTS\t\t11\n+#define RTL8365MB_D_MAX_NUM_PORTS\t8\n /* Valid for the whole family except RTL8370B, which has 4160 entries.\n  * RTL8370B is mentioned in vendor code but it might not even belong\n  * to the same RTL8367C family.\n@@ -283,6 +287,7 @@\n #define   RTL8365MB_SDS_INDACS_CMD_BUSY_MASK\tBIT(8)\n #define   RTL8365MB_SDS_INDACS_CMD_RUN_MASK\tBIT(7)\n #define   RTL8365MB_SDS_INDACS_CMD_WR_MASK\tBIT(6)\n+#define   RTL8365MB_SDS_INDACS_CMD_INDEX_MASK\tGENMASK(5, 0)\n #define RTL8365MB_SDS_INDACS_ADR_REG\t\t0x6601\n #define RTL8365MB_SDS_INDACS_DATA_REG\t\t0x6602\n \n@@ -296,6 +301,23 @@\n #define   RTL8365MB_SDS_MISC_SGMII_SPD_MASK\t\tGENMASK(8, 7)\n #define   RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK\tBIT(6)\n \n+/* Re-latch retry interval and attempt budget. Each attempt parks the\n+ * SerDes for 20 ms, so a far end that never comes up must not be retried\n+ * at this rate forever, and the interval must not be so tight that\n+ * back-to-back parks starve a link that is about to latch. The count is\n+ * reset to 1 by pcs_config() and to 0 whenever the receiver latches;\n+ * attempts start at pcs_config(), well before the far end MAC is up, so\n+ * with the interval below the budget is roughly RELATCH_MAX_TRIES seconds,\n+ * generous enough to outlast a slow conduit.\n+ */\n+#define RTL8365MB_D_SDS_RELATCH_INTERVAL\t(HZ)\n+#define RTL8365MB_D_SDS_RELATCH_MAX_TRIES\t15\n+\n+/* How often a latched (or out-of-budget) SerDes is checked, and re-latched\n+ * if still down. Much slower than the re-latch interval.\n+ */\n+#define RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL\t(10 * HZ)\n+\n /* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR\n  * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the\n  * vendor-specific low bits from phase 1 to phase 2, which triggers a data path\n@@ -331,6 +353,27 @@\n #define   RTL8365MB_SDS_OPTION_ARM_KEY\t\t0x0249\n #define RTL8365MB_SDS_OPTION_REG\t\t0x13C1\n \n+/* Family D uses the SDS13 indirect window for its MAC6 SerDes. */\n+#define RTL8365MB_D_SDS_EXT0_INDEX\t\t\t13\n+#define RTL8365MB_D_FIBER_CFG2_REG\t\t\t0x13E8\n+#define   RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK\tGENMASK(7, 6)\n+#define   RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0\tBIT(6)\n+#define RTL8365MB_D_SDS_MISC_PA33PC_EN\t\t\tBIT(11)\n+#define RTL8365MB_D_SDS_MISC_PA12PC_EN\t\t\tBIT(10)\n+#define RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0\t\tBIT(9)\n+#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK\t\tGENMASK(4, 0)\n+#define RTL8365MB_D_SDS_MISC_MODE_SGMII\t\t\t0x02\n+#define RTL8365MB_D_SDS_MISC_MODE_HSGMII\t\t0x12\n+#define RTL8365MB_D_SDS_MISC_CFG_MASK\t\t\t\\\n+\t(RTL8365MB_D_SDS_MISC_PA33PC_EN |\t\t\\\n+\t RTL8365MB_D_SDS_MISC_PA12PC_EN |\t\t\\\n+\t RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 |\t\t\\\n+\t RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK)\n+/* Shared \"disable\" encoding for both SDS_MISC's and SDS1_MISC0's\n+ * 5-bit mode fields.\n+ */\n+#define RTL8365MB_D_PORT_SDS_MODE_DISABLE\t\t0x1f\n+\n /* Embedded DW8051 microcontroller control registers. The microcontroller\n  * can run firmware to manage the SerDes link, but this driver keeps it in\n  * reset and disabled: phylink already performs the link management that\n@@ -343,6 +386,7 @@\n #define RTL8365MB_PORT_SPEED_10M\t0\n #define RTL8365MB_PORT_SPEED_100M\t1\n #define RTL8365MB_PORT_SPEED_1000M\t2\n+#define RTL8365MB_D_PORT_SPEED_2500M\t5\n \n /* External interface force configuration registers 0~2 */\n #define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0\t\t0x1310 /* EXT0 */\n@@ -360,6 +404,20 @@\n #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK\t\tBIT(4)\n #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK\t\tBIT(2)\n #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK\t\tGENMASK(1, 0)\n+#define   RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH\t\t2 /* bits[1:0] */\n+\n+/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */\n+#define   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK\tGENMASK(13, 12)\n+\n+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE\t\t0x12c0\n+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \\\n+\t\t(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port))\n+\n+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE\t\t0x12c8\n+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \\\n+\t\t(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port))\n+\n+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK\tGENMASK(15, 0)\n \n /* CPU port mask register - controls which ports are treated as CPU ports */\n #define RTL8365MB_CPU_PORT_MASK_REG\t0x1219\n@@ -441,6 +499,20 @@\n #define   RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK\t\tGENMASK(5, 4)\n #define   RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK\tGENMASK(3, 0)\n \n+/* EXT_TXC_DLY holds a 3-bit TX clock delay per external interface, for\n+ * RGMII ([2:0] EXT0, [5:3] EXT1, [8:6] EXT2) and separately for GMII.\n+ * Only the EXT1 RGMII field is used here, and it is cleared in RGMII\n+ * mode so that tx-internal-delay-ps is the only TX delay applied.\n+ */\n+#define RTL8365MB_D_REG_EXT_TXC_DLY\t\t\t\t0x13f9\n+#define   RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK\t\t\tGENMASK(5, 3)\n+\n+#define RTL8365MB_D_REG_TOP_CON0\t\t\t\t0x1d70\n+#define   RTL8365MB_D_MAC7_SEL_EXT1_MASK\t\t\tBIT(13)\n+#define   RTL8365MB_D_MAC4_SEL_EXT1_MASK\t\t\tBIT(12)\n+\n+#define RTL8365MB_D_REG_SDS1_MISC0\t\t\t\t0x1d78\n+\n /**\n  * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode\n  * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry\n@@ -676,6 +748,21 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {\n \t{ 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },\n };\n \n+/* Family D tuning tables from the Realtek vendor port API. */\n+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_sgmii[] = {\n+\t{ 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },\n+\t{ 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },\n+\t{ 0x0484, 0x011B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },\n+\t{ 0x0004, 0x074F },\n+};\n+\n+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_hsgmii[] = {\n+\t{ 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },\n+\t{ 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },\n+\t{ 0x0504, 0x051B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },\n+\t{ 0x0004, 0x074F },\n+};\n+\n enum rtl8365mb_phy_interface_mode {\n \tRTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,\n \tRTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),\n@@ -707,6 +794,7 @@ struct rtl8365mb_extint {\n  * @name: human-readable chip name\n  * @chip_id: chip identifier\n  * @chip_ver: chip silicon revision\n+ * @family: chip family\n  * @extints: available external interfaces\n  * @jam_table: chip-specific initialization jam table\n  * @jam_size: size of the chip's jam table\n@@ -719,6 +807,7 @@ struct rtl8365mb_chip_info {\n \tconst char *name;\n \tu32 chip_id;\n \tu32 chip_ver;\n+\tenum rtl8365mb_family family;\n \tconst struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS];\n \tconst struct rtl8365mb_jam_tbl_entry *jam_table;\n \tsize_t jam_size;\n@@ -731,6 +820,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {\n \t\t.name = \"RTL8365MB-VC\",\n \t\t.chip_id = 0x6367,\n \t\t.chip_ver = 0x0040,\n+\t\t.family = RTL8365MB_FAMILY_C,\n \t\t.extints = {\n \t\t\t{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |\n \t\t\t\tPHY_INTF(RMII) | PHY_INTF(RGMII) },\n@@ -742,6 +832,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {\n \t\t.name = \"RTL8367S\",\n \t\t.chip_id = 0x6367,\n \t\t.chip_ver = 0x00A0,\n+\t\t.family = RTL8365MB_FAMILY_C,\n \t\t.extints = {\n \t\t\t{ 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },\n \t\t\t{ 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) |\n@@ -754,6 +845,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {\n \t\t.name = \"RTL8367SB\",\n \t\t.chip_id = 0x6367,\n \t\t.chip_ver = 0x0010,\n+\t\t.family = RTL8365MB_FAMILY_C,\n \t\t.extints = {\n \t\t\t{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |\n \t\t\t\tPHY_INTF(RMII) | PHY_INTF(RGMII) |\n@@ -768,6 +860,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {\n \t\t.name = \"RTL8367RB-VB\",\n \t\t.chip_id = 0x6367,\n \t\t.chip_ver = 0x0020,\n+\t\t.family = RTL8365MB_FAMILY_C,\n \t\t.extints = {\n \t\t\t{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |\n \t\t\t\tPHY_INTF(RMII) | PHY_INTF(RGMII) },\n@@ -777,6 +870,19 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {\n \t\t.jam_table = rtl8365mb_init_jam_8365mb_vc,\n \t\t.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),\n \t},\n+\t{\n+\t\t.name = \"RTL8367S-VB\",\n+\t\t.chip_id = 0x6642,\n+\t\t.chip_ver = 0x0010,\n+\t\t.family = RTL8365MB_FAMILY_D,\n+\t\t.extints = {\n+\t\t\t{ 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },\n+\t\t\t{ 7, 1, PHY_INTF(MII) | PHY_INTF(TMII) |\n+\t\t\t\tPHY_INTF(RMII) | PHY_INTF(RGMII) },\n+\t\t},\n+\t\t.jam_table = rtl8365mb_init_jam_8365mb_vc,\n+\t\t.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),\n+\t},\n };\n \n enum rtl8365mb_stp_state {\n@@ -854,10 +960,16 @@ struct rtl8365mb_port {\n  * @chip_info: chip-specific info about the attached switch\n  * @cpu: CPU tagging and CPU port configuration for this chip\n  * @mib_lock: prevent concurrent reads of MIB counters\n+ * @sds_lock: serializes access to the shared SDS_INDACS ADR/CMD/DATA window\n+ *            and to RTL8365MB_SDS_MISC_REG, reachable both from phylink's\n+ *            PCS callbacks and from the family D SerDes re-latch work\n  * @ports: per-port data\n  * @pcs: PCS for the SerDes external interface\n  * @sds_supported: SerDes tuning parameters match the chip option, so the\n  *                 SerDes interface modes can be advertised\n+ * @sds_relatch: re-latch edges and link health check (family D)\n+ * @sds_relatch_count: next attempt of the re-latch episode, 0 once latched\n+ * @sds_misc_target_val: SDS_MISC_CFG_MASK fields the re-latch work restores\n  *\n  * Private data for this driver.\n  */\n@@ -867,13 +979,24 @@ struct rtl8365mb {\n \tconst struct rtl8365mb_chip_info *chip_info;\n \tstruct rtl8365mb_cpu cpu;\n \tstruct mutex mib_lock;\n+\tstruct mutex sds_lock;\n \tstruct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];\n \tstruct phylink_pcs pcs;\n \tbool sds_supported;\n+\tstruct delayed_work sds_relatch;\n+\tunsigned int sds_relatch_count;\n+\tu32 sds_misc_target_val;\n };\n \n #define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)\n \n+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv)\n+{\n+\tstruct rtl8365mb *mb = priv-\u003echip_data;\n+\n+\treturn mb-\u003echip_info-\u003efamily;\n+}\n+\n static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)\n {\n \tu32 val;\n@@ -1164,6 +1287,7 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,\n \tstruct dsa_port *dp;\n \tint tx_delay = 0;\n \tint rx_delay = 0;\n+\tu32 data;\n \tu32 val;\n \tint ret;\n \n@@ -1233,43 +1357,85 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,\n \tif (ret)\n \t\treturn ret;\n \n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D \u0026\u0026 extint-\u003eid == 1) {\n+\t\tret = regmap_update_bits(priv-\u003emap,\n+\t\t\t\t\t RTL8365MB_D_REG_EXT_TXC_DLY,\n+\t\t\t\t\t RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK, 0);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\t/* Configure RGMII/MII mux to port 7 if UTP_PORT4 is not RGMII mode */\n+\t\tret = regmap_read(priv-\u003emap, RTL8365MB_D_REG_TOP_CON0, \u0026data);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tif ((data \u0026 RTL8365MB_D_MAC4_SEL_EXT1_MASK) == 0) {\n+\t\t\tret = regmap_update_bits(priv-\u003emap,\n+\t\t\t\t\t\t RTL8365MB_D_REG_TOP_CON0,\n+\t\t\t\t\t\t RTL8365MB_D_MAC7_SEL_EXT1_MASK,\n+\t\t\t\t\t\t RTL8365MB_D_MAC7_SEL_EXT1_MASK);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t\tret = regmap_update_bits(priv-\u003emap,\n+\t\t\t\t\t RTL8365MB_D_REG_SDS1_MISC0,\n+\t\t\t\t\t RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK,\n+\t\t\t\t\t RTL8365MB_D_PORT_SDS_MODE_DISABLE);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n \treturn 0;\n }\n \n-static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)\n+static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index,\n+\t\t\t       u16 addr, u16 data)\n {\n+\tstruct rtl8365mb *mb = priv-\u003echip_data;\n \tint ret;\n \n+\tmutex_lock(\u0026mb-\u003esds_lock);\n+\n \tret = regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_DATA_REG, data);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \tret = regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_ADR_REG, addr);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \t/* The SerDes indirect access engine completes the command within the\n \t * register write transaction, so there is no need to wait or poll for\n \t * completion before the next access, matching the vendor driver.\n \t */\n-\treturn regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_CMD_REG,\n-\t\t\t    RTL8365MB_SDS_INDACS_CMD_RUN_MASK |\n-\t\t\t    RTL8365MB_SDS_INDACS_CMD_WR_MASK);\n+\tret = regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_CMD_REG,\n+\t\t\t   RTL8365MB_SDS_INDACS_CMD_RUN_MASK |\n+\t\t\t   RTL8365MB_SDS_INDACS_CMD_WR_MASK |\n+\t\t\t   FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,\n+\t\t\t\t      index));\n+\n+out_unlock:\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n+\treturn ret;\n }\n \n-static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)\n+static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,\n+\t\t\t      u16 addr, u16 *data)\n {\n+\tstruct rtl8365mb *mb = priv-\u003echip_data;\n \tu32 val;\n \tint ret;\n \n+\tmutex_lock(\u0026mb-\u003esds_lock);\n+\n \tret = regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_ADR_REG, addr);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \tret = regmap_write(priv-\u003emap, RTL8365MB_SDS_INDACS_CMD_REG,\n-\t\t\t   RTL8365MB_SDS_INDACS_CMD_RUN_MASK);\n+\t\t\t   RTL8365MB_SDS_INDACS_CMD_RUN_MASK |\n+\t\t\t   FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,\n+\t\t\t\t      index));\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \t/* Wait for the indirect read to complete: the engine clears the BUSY\n \t * bit once the data register holds the result.\n@@ -1279,15 +1445,19 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)\n \t\t\t\t       !(val \u0026 RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),\n \t\t\t\t       10, 1000);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \tret = regmap_read(priv-\u003emap, RTL8365MB_SDS_INDACS_DATA_REG, \u0026val);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_unlock;\n \n \t*data = val;\n \n-\treturn 0;\n+\tret = 0;\n+\n+out_unlock:\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n+\treturn ret;\n }\n \n /* The vendor driver selects between two sets of SerDes tuning parameters based\n@@ -1307,6 +1477,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)\n \tint ret;\n \tint i;\n \n+\t/* Family D has a fixed SDS13 programming model and does not use the\n+\t * family C option register to select its tuning table.\n+\t */\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {\n+\t\tmb-\u003esds_supported = true;\n+\t\treturn 0;\n+\t}\n+\n \t/* Nothing to probe if no external interface is wired to the SerDes */\n \tfor (i = 0; i \u003c RTL8365MB_MAX_NUM_EXTINTS; i++) {\n \t\textint = \u0026mb-\u003echip_info-\u003eextints[i];\n@@ -1385,28 +1563,54 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,\n \t\t\t\tconst unsigned long *advertising,\n \t\t\t\tbool permit_pause_to_mac)\n {\n-\tconst struct rtl8365mb_jam_tbl_entry *sds_jam;\n \tconst int id = RTL8365MB_SDS_EXT_INTERFACE_ID;\n+\tconst struct rtl8365mb_jam_tbl_entry *sds_jam;\n \tstruct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);\n-\tstruct realtek_priv *priv;\n+\tstruct realtek_priv *priv = mb-\u003epriv;\n \tsize_t sds_jam_size;\n-\tu32 mode;\n+\tu32 misc_mask;\n+\tu32 misc_val;\n+\tu32 sds_mode;\n+\tu8 sds_index;\n+\tbool is_d;\n \tu16 val;\n \tint ret;\n \tint i;\n \n-\tpriv = mb-\u003epriv;\n+\tis_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;\n \n+\t/* Cancel any in-flight re-latch edge before touching SDS_MISC: the\n+\t * work drops sds_lock across its sleep and could otherwise interleave\n+\t * with the reconfiguration below.\n+\t */\n+\tif (is_d)\n+\t\tcancel_delayed_work_sync(\u0026mb-\u003esds_relatch);\n+\n+\t/* Select the appropriate tuning table and SDS mode */\n \tif (interface == PHY_INTERFACE_MODE_2500BASEX) {\n-\t\tsds_jam = rtl8365mb_sds_jam_hsgmii;\n-\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);\n-\t\tmode = RTL8365MB_EXT_PORT_MODE_HSGMII;\n+\t\tif (is_d) {\n+\t\t\tsds_jam = rtl8365mb_d_sds_jam_hsgmii;\n+\t\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_hsgmii);\n+\t\t\tsds_mode = RTL8365MB_D_SDS_MISC_MODE_HSGMII;\n+\t\t} else {\n+\t\t\tsds_jam = rtl8365mb_sds_jam_hsgmii;\n+\t\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);\n+\t\t\tsds_mode = RTL8365MB_EXT_PORT_MODE_HSGMII;\n+\t\t}\n \t} else {\n-\t\tsds_jam = rtl8365mb_sds_jam_sgmii;\n-\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);\n-\t\tmode = RTL8365MB_EXT_PORT_MODE_SGMII;\n+\t\tif (is_d) {\n+\t\t\tsds_jam = rtl8365mb_d_sds_jam_sgmii;\n+\t\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_sgmii);\n+\t\t\tsds_mode = RTL8365MB_D_SDS_MISC_MODE_SGMII;\n+\t\t} else {\n+\t\t\tsds_jam = rtl8365mb_sds_jam_sgmii;\n+\t\t\tsds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);\n+\t\t\tsds_mode = RTL8365MB_EXT_PORT_MODE_SGMII;\n+\t\t}\n \t}\n \n+\tsds_index = is_d ? RTL8365MB_D_SDS_EXT0_INDEX : 0;\n+\n \t/* Hold the embedded DW8051 microcontroller in reset and keep it\n \t * disabled. The vendor driver loads firmware into it to manage the\n \t * SerDes link, but the firmware only duplicates work that phylink\n@@ -1435,34 +1639,57 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,\n \n \t/* Tune the SerDes with vendor-prescribed parameters */\n \tfor (i = 0; i \u003c sds_jam_size; i++) {\n-\t\tret = rtl8365mb_sds_write(priv, sds_jam[i].reg,\n-\t\t\t\t\t  sds_jam[i].val);\n+\t\tret = rtl8365mb_sds_write(priv, sds_index,\n+\t\t\t\t\t  sds_jam[i].reg, sds_jam[i].val);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/* Family-specific post-tuning configuration */\n+\tif (is_d) {\n+\t\tret = regmap_update_bits(priv-\u003emap, RTL8365MB_D_FIBER_CFG2_REG,\n+\t\t\t\t\t RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK,\n+\t\t\t\t\t RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0);\n \t\tif (ret)\n \t\t\treturn ret;\n+\n+\t\tmisc_mask = RTL8365MB_D_SDS_MISC_CFG_MASK;\n+\t\tmisc_val  = RTL8365MB_D_SDS_MISC_PA33PC_EN |\n+\t\t\t    RTL8365MB_D_SDS_MISC_PA12PC_EN |\n+\t\t\t    RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | sds_mode;\n+\t} else {\n+\t\t/* Mux the SerDes to MAC8 in the requested mode */\n+\t\tmisc_mask = RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |\n+\t\t\t    RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;\n+\t\tmisc_val  = (sds_mode == RTL8365MB_EXT_PORT_MODE_SGMII) ?\n+\t\t\t    RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :\n+\t\t\t    RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;\n \t}\n \n-\t/* Mux the SerDes to MAC8 in the requested mode */\n+\tmutex_lock(\u0026mb-\u003esds_lock);\n \tret = regmap_update_bits(priv-\u003emap, RTL8365MB_SDS_MISC_REG,\n-\t\t\t\t RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |\n-\t\t\t\t\t RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,\n-\t\t\t\t mode == RTL8365MB_EXT_PORT_MODE_SGMII ?\n-\t\t\t\t\t RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :\n-\t\t\t\t\t RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);\n+\t\t\t\t misc_mask, misc_val);\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n \tif (ret)\n \t\treturn ret;\n \n-\tval = mode \u003c\u003c RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);\n-\tret = regmap_update_bits(priv-\u003emap,\n-\t\t\t\t RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),\n-\t\t\t\t RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),\n-\t\t\t\t val);\n-\tif (ret)\n-\t\treturn ret;\n+\tif (is_d) {\n+\t\tWRITE_ONCE(mb-\u003esds_misc_target_val, misc_val);\n+\t} else {\n+\t\tval = sds_mode \u003c\u003c RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);\n+\t\tret = regmap_update_bits(priv-\u003emap,\n+\t\t\t\t\t RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),\n+\t\t\t\t\t RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),\n+\t\t\t\t\t val);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n \n \t/* Take the SerDes out of reset. The vendor driver does this only\n \t * after the SerDes mux and the interface mode are configured.\n \t */\n-\tret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET,\n+\tret = rtl8365mb_sds_write(priv, sds_index,\n+\t\t\t\t  RTL8365MB_SDS_REG_RESET,\n \t\t\t\t  RTL8365MB_SDS_RESET_DEASSERT);\n \tif (ret)\n \t\treturn ret;\n@@ -1472,12 +1699,14 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,\n \t * This flushes the FIFOs and ensures a clean state for the link,\n \t * preventing silent drops and CRC errors.\n \t */\n-\tret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,\n+\tret = rtl8365mb_sds_write(priv, sds_index,\n+\t\t\t\t  RTL8365MB_SDS_REG_BMCR,\n \t\t\t\t  RTL8365MB_SDS_BMCR_DPRST_PHASE1);\n \tif (ret)\n \t\treturn ret;\n \n-\tret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,\n+\tret = rtl8365mb_sds_write(priv, sds_index,\n+\t\t\t\t  RTL8365MB_SDS_REG_BMCR,\n \t\t\t\t  RTL8365MB_SDS_BMCR_DPRST_PHASE2);\n \tif (ret)\n \t\treturn ret;\n@@ -1485,14 +1714,148 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,\n \t/* Keep SGMII in-band autonegotiation disabled: the link parameters are\n \t * forced from rtl8365mb_pcs_link_up() instead.\n \t */\n-\tret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, \u0026val);\n+\tret = rtl8365mb_sds_read(priv, sds_index,\n+\t\t\t\t RTL8365MB_SDS_REG_NWAY, \u0026val);\n \tif (ret)\n \t\treturn ret;\n \n \tval \u0026= ~RTL8365MB_SDS_NWAY_EN_MASK;\n \tval |= RTL8365MB_SDS_NWAY_RESTART_MASK;\n \n-\treturn rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val);\n+\tret = rtl8365mb_sds_write(priv, sds_index,\n+\t\t\t\t  RTL8365MB_SDS_REG_NWAY, val);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (is_d) {\n+\t\t/* Start a new re-latch episode and kick off the first attempt;\n+\t\t * see rtl8365mb_sds_relatch_work() for why this cannot wait\n+\t\t * for phylink to ask again.\n+\t\t */\n+\t\tWRITE_ONCE(mb-\u003esds_relatch_count, 1);\n+\t\tschedule_delayed_work(\u0026mb-\u003esds_relatch, 0);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* The family D receiver latches on a DISABLE -\u003e mode edge in SDS_MISC\n+ * rather than on the value, and only once the far-end MAC has brought up\n+ * its half of the link. No local signal predicts that (bit 8 of the SDS\n+ * link-status word does not change ahead of it), so the work cannot wait\n+ * for it, only check afterwards whether an edge took. Nor does phylink\n+ * ever call rtl8365mb_pcs_get_state() for this fixed-link port\n+ * (mb-\u003epcs.poll only arms phylink's own poll for in-band links), so this\n+ * cannot rely on being polled at all. Instead, pcs_config() starts an\n+ * episode and this work re-arms itself after every edge, its next run\n+ * checking whether the edge took.\n+ *\n+ * An episode is capped at RTL8365MB_D_SDS_RELATCH_MAX_TRIES edges, spaced\n+ * RTL8365MB_D_SDS_RELATCH_INTERVAL apart, so that a far end which never\n+ * comes up costs a bounded burst of 20 ms parks. The run after the last\n+ * edge only checks whether it took and reports failure; from then on one\n+ * edge is driven per RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL, so a far end\n+ * that comes up late is recovered without a reconfiguration. Once the\n+ * receiver has latched the work just polls the link at the same slow\n+ * interval; finding it down then starts a new episode, e.g. after the far\n+ * end reset its PLL without a reconfiguration.\n+ *\n+ * sds_relatch_count is the number of the next attempt of the episode: 0\n+ * while latched, 1..MAX_TRIES in budget, MAX_TRIES + 1 for the run that\n+ * only reports, and above that once out of budget.\n+ */\n+static void rtl8365mb_sds_relatch_work(struct work_struct *work)\n+{\n+\tstruct rtl8365mb *mb = container_of(to_delayed_work(work),\n+\t\t\t\t\t    struct rtl8365mb, sds_relatch);\n+\tstruct realtek_priv *priv = mb-\u003epriv;\n+\tu32 park = (READ_ONCE(mb-\u003esds_misc_target_val) \u0026\n+\t\t    ~RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK) |\n+\t\t   RTL8365MB_D_PORT_SDS_MODE_DISABLE;\n+\tunsigned long delay = RTL8365MB_D_SDS_RELATCH_INTERVAL;\n+\tunsigned int attempts;\n+\tu16 status;\n+\tint ret;\n+\n+\t/* The condition that queued this work may already be stale by the\n+\t * time it runs (e.g. an earlier edge from a prior attempt just\n+\t * latched). Parking an already-live link would drop it for no\n+\t * reason.\n+\t */\n+\tret = rtl8365mb_sds_read(priv, RTL8365MB_D_SDS_EXT0_INDEX,\n+\t\t\t\t RTL8365MB_SDS_REG_LINK_STATUS, \u0026status);\n+\tif (ret) {\n+\t\tdev_err_ratelimited(priv-\u003edev,\n+\t\t\t\t    \"failed to read SerDes link status: %pe\\n\",\n+\t\t\t\t    ERR_PTR(ret));\n+\t\tgoto rearm;\n+\t}\n+\tif (status \u0026 RTL8365MB_SDS_LINK_STATUS_LINK_MASK) {\n+\t\t/* Latched: end the episode, keep watching for a later loss. */\n+\t\tWRITE_ONCE(mb-\u003esds_relatch_count, 0);\n+\t\tdelay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;\n+\t\tgoto rearm;\n+\t}\n+\n+\tattempts = READ_ONCE(mb-\u003esds_relatch_count);\n+\tif (!attempts) {\n+\t\tdev_warn(priv-\u003edev,\n+\t\t\t \"SerDes link lost outside a reconfiguration; re-latching\\n\");\n+\t\tattempts = 1;\n+\t} else if (attempts == RTL8365MB_D_SDS_RELATCH_MAX_TRIES + 1) {\n+\t\t/* This run only checked whether the last in-budget edge\n+\t\t * took. The first out-of-budget edge comes one healthcheck\n+\t\t * interval from now, not on this pass.\n+\t\t */\n+\t\tWRITE_ONCE(mb-\u003esds_relatch_count, attempts + 1);\n+\t\tdev_warn(priv-\u003edev,\n+\t\t\t \"SerDes did not latch after %u re-latch attempts; still retrying every %d s\\n\",\n+\t\t\t RTL8365MB_D_SDS_RELATCH_MAX_TRIES,\n+\t\t\t RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL / HZ);\n+\t\tdelay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;\n+\t\tgoto rearm;\n+\t}\n+\n+\tif (attempts \u003c= RTL8365MB_D_SDS_RELATCH_MAX_TRIES)\n+\t\tWRITE_ONCE(mb-\u003esds_relatch_count, attempts + 1);\n+\telse\n+\t\t/* Past the fast-retry budget. The receiver latches only on a\n+\t\t * DISABLE -\u003e mode edge and nothing re-runs pcs_config() for a\n+\t\t * fixed-link port, so keep driving one edge per interval.\n+\t\t */\n+\t\tdelay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;\n+\n+\tmutex_lock(\u0026mb-\u003esds_lock);\n+\tret = regmap_update_bits(priv-\u003emap, RTL8365MB_SDS_MISC_REG,\n+\t\t\t\t RTL8365MB_D_SDS_MISC_CFG_MASK, park);\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n+\tif (ret) {\n+\t\tdev_err_ratelimited(priv-\u003edev,\n+\t\t\t\t    \"failed to park SDS_MISC: %pe\\n\",\n+\t\t\t\t    ERR_PTR(ret));\n+\t\tgoto rearm;\n+\t}\n+\n+\tusleep_range(20000, 21000);\n+\n+\tmutex_lock(\u0026mb-\u003esds_lock);\n+\tret = regmap_update_bits(priv-\u003emap, RTL8365MB_SDS_MISC_REG,\n+\t\t\t\t RTL8365MB_D_SDS_MISC_CFG_MASK,\n+\t\t\t\t READ_ONCE(mb-\u003esds_misc_target_val));\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n+\tif (ret) {\n+\t\tdev_err_ratelimited(priv-\u003edev,\n+\t\t\t\t    \"failed to restore SDS_MISC: %pe\\n\",\n+\t\t\t\t    ERR_PTR(ret));\n+\t\tgoto rearm;\n+\t}\n+\n+\tdev_dbg(priv-\u003edev, \"SerDes re-latch edge driven (attempt %u)\\n\",\n+\t\tattempts);\n+\n+\t/* This edge may not have taken either; the next run checks. */\n+rearm:\n+\tschedule_delayed_work(\u0026mb-\u003esds_relatch, delay);\n }\n \n static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)\n@@ -1517,10 +1880,14 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,\n {\n \tstruct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);\n \tstruct realtek_priv *priv = mb-\u003epriv;\n+\tu8 sds_index = 0;\n \tu16 status;\n+\tbool is_d;\n \tu32 val;\n \tint ret;\n \n+\tis_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;\n+\n \t/* In-band autonegotiation is not implemented, so the link parameters are\n \t * forced from rtl8365mb_pcs_link_up(). The real link state must still be\n \t * read from the SerDes itself: the embedded DW8051 microcontroller that\n@@ -1528,7 +1895,11 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,\n \t * rtl8365mb_pcs_config()), so the link status register can be read\n \t * directly through the SDS_INDACS window without racing the auto-poll.\n \t */\n-\tret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, \u0026status);\n+\tif (is_d)\n+\t\tsds_index = RTL8365MB_D_SDS_EXT0_INDEX;\n+\n+\tret = rtl8365mb_sds_read(priv, sds_index,\n+\t\t\t\t RTL8365MB_SDS_REG_LINK_STATUS, \u0026status);\n \tif (ret) {\n \t\tstate-\u003elink = false;\n \t\treturn;\n@@ -1539,10 +1910,19 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,\n \tif (!state-\u003elink)\n \t\treturn;\n \n+\tif (is_d) {\n+\t\tstate-\u003eduplex = DUPLEX_FULL;\n+\t\tstate-\u003espeed = state-\u003einterface == PHY_INTERFACE_MODE_2500BASEX ?\n+\t\t\t\tSPEED_2500 : SPEED_1000;\n+\t\treturn;\n+\t}\n+\n \t/* The speed and duplex are forced; read them back from the values\n \t * programmed into the SerDes MISC register.\n \t */\n+\tmutex_lock(\u0026mb-\u003esds_lock);\n \tret = regmap_read(priv-\u003emap, RTL8365MB_SDS_MISC_REG, \u0026val);\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n \tif (ret) {\n \t\tstate-\u003elink = false;\n \t\treturn;\n@@ -1580,6 +1960,12 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,\n \tu32 r_speed;\n \tint ret;\n \n+\t/* Family D forces the external MAC ability from mac_link_up(); its\n+\t * SDS_MISC fields do not share the family C link-force layout.\n+\t */\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\treturn;\n+\n \t/* The speed field has no value for 2.5 Gbps: the rate is determined by\n \t * the HSGMII SerDes configuration, and the vendor driver programs the\n \t * 1 Gbps value here.\n@@ -1606,7 +1992,9 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,\n \t * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause\n \t * modes are known.\n \t */\n+\tmutex_lock(\u0026mb-\u003esds_lock);\n \tret = regmap_update_bits(priv-\u003emap, RTL8365MB_SDS_MISC_REG, mask, val);\n+\tmutex_unlock(\u0026mb-\u003esds_lock);\n \tif (ret) {\n \t\tdev_err(priv-\u003edev, \"failed to force SerDes link: %pe\\n\",\n \t\t\tERR_PTR(ret));\n@@ -1632,23 +2020,31 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,\n \tu32 r_duplex;\n \tu32 r_speed;\n \tu32 r_link;\n+\tbool is_d;\n \tint val;\n \tint ret;\n \n \tif (!extint)\n \t\treturn -ENODEV;\n \n+\tis_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;\n \tif (link) {\n \t\t/* Force the link up with the desired configuration */\n \t\tr_link = 1;\n \t\tr_rx_pause = rx_pause ? 1 : 0;\n \t\tr_tx_pause = tx_pause ? 1 : 0;\n \n-\t\t/* The speed field has no value for 2.5 Gbps: the rate is\n-\t\t * determined by the HSGMII SerDes configuration, and the\n-\t\t * vendor driver programs the 1 Gbps value here.\n-\t\t */\n-\t\tif (speed == SPEED_2500 || speed == SPEED_1000) {\n+\t\tif (speed == SPEED_2500) {\n+\t\t\tif (is_d) {\n+\t\t\t\tr_speed = RTL8365MB_D_PORT_SPEED_2500M;\n+\t\t\t} else {\n+\t\t\t\t/* The speed field has no value for 2.5 Gbps: the rate is\n+\t\t\t\t * determined by the HSGMII SerDes configuration, and the\n+\t\t\t\t * vendor driver programs the 1 Gbps value here.\n+\t\t\t\t */\n+\t\t\t\tr_speed = RTL8365MB_PORT_SPEED_1000M;\n+\t\t\t}\n+\t\t} else if (speed == SPEED_1000) {\n \t\t\tr_speed = RTL8365MB_PORT_SPEED_1000M;\n \t\t} else if (speed == SPEED_100) {\n \t\t\tr_speed = RTL8365MB_PORT_SPEED_100M;\n@@ -1678,20 +2074,42 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,\n \t\tr_duplex = 0;\n \t}\n \n-\tval = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) |\n-\t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,\n+\tval = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,\n \t\t\t r_tx_pause) |\n \t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK,\n \t\t\t r_rx_pause) |\n \t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) |\n \t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK,\n \t\t\t r_duplex) |\n-\t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed);\n-\tret = regmap_write(priv-\u003emap,\n-\t\t\t   RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint-\u003eid),\n-\t\t\t   val);\n-\tif (ret)\n-\t\treturn ret;\n+\t      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK,\n+\t\t\t r_speed \u0026 RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK);\n+\n+\tif (is_d) {\n+\t\t/* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED,\n+\t\t * bit[2] goes into FORCE_SPEED2 (bit 12); only 2500M sets it,\n+\t\t * bit 13 of the field is unused.\n+\t\t */\n+\t\tval |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK,\n+\t\t\t\t  r_speed \u003e\u003e RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH);\n+\t\tret = regmap_write(priv-\u003emap,\n+\t\t\t\t   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(port),\n+\t\t\t\t   val);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = regmap_write(priv-\u003emap,\n+\t\t\t\t   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(port),\n+\t\t\t\t   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t} else {\n+\t\tval |= FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1);\n+\t\tret = regmap_write(priv-\u003emap,\n+\t\t\t\t   RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint-\u003eid),\n+\t\t\t\t   val);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\n@@ -1876,7 +2294,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,\n \t\t * rtl8365mb_pcs_link_up() because pcs_link_up() carries no\n \t\t * pause information.\n \t\t */\n-\t\tif (rtl8365mb_interface_is_serdes(interface)) {\n+\t\tif (rtl8365mb_interface_is_serdes(interface) \u0026\u0026\n+\t\t    rtl8365mb_get_family(priv) != RTL8365MB_FAMILY_D) {\n \t\t\tu32 val = 0;\n \n \t\t\tif (tx_pause)\n@@ -1884,11 +2303,13 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,\n \t\t\tif (rx_pause)\n \t\t\t\tval |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;\n \n+\t\t\tmutex_lock(\u0026mb-\u003esds_lock);\n \t\t\tret = regmap_update_bits(priv-\u003emap,\n \t\t\t\t\t\t RTL8365MB_SDS_MISC_REG,\n \t\t\t\t\t\t RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |\n \t\t\t\t\t\t\t RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,\n \t\t\t\t\t\t val);\n+\t\t\tmutex_unlock(\u0026mb-\u003esds_lock);\n \t\t\tif (ret)\n \t\t\t\tdev_err(priv-\u003edev,\n \t\t\t\t\t\"failed to force SerDes pause modes on port %d: %pe\\n\",\n@@ -3100,6 +3521,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)\n \t */\n \tmb-\u003epcs.poll = true;\n \n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\tINIT_DELAYED_WORK(\u0026mb-\u003esds_relatch,\n+\t\t\t\t  rtl8365mb_sds_relatch_work);\n+\n \tret = rtl8365mb_reset_chip(priv);\n \tif (ret) {\n \t\tdev_err(priv-\u003edev, \"failed to reset chip: %pe\\n\",\n@@ -3284,6 +3709,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)\n static void rtl8365mb_teardown(struct dsa_switch *ds)\n {\n \tstruct realtek_priv *priv = ds-\u003epriv;\n+\tstruct rtl8365mb *mb = priv-\u003echip_data;\n+\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\tcancel_delayed_work_sync(\u0026mb-\u003esds_relatch);\n \n \trtl8365mb_stats_teardown(priv);\n \trtl8365mb_irq_teardown(priv);\n@@ -3349,7 +3778,15 @@ static int rtl8365mb_detect(struct realtek_priv *priv)\n \n \tdev_info(priv-\u003edev, \"found an %s switch\\n\", mb-\u003echip_info-\u003ename);\n \n-\tpriv-\u003enum_ports = RTL8365MB_MAX_NUM_PORTS;\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\tpriv-\u003enum_ports = RTL8365MB_D_MAX_NUM_PORTS;\n+\telse\n+\t\tpriv-\u003enum_ports = RTL8365MB_MAX_NUM_PORTS;\n+\n+\tret = devm_mutex_init(priv-\u003edev, \u0026mb-\u003esds_lock);\n+\tif (ret)\n+\t\treturn ret;\n+\n \tmb-\u003epriv = priv;\n \tmb-\u003ecpu.trap_port = RTL8365MB_MAX_NUM_PORTS;\n \tmb-\u003ecpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;\ndiff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c\nindex 8d49ffadce926..da517b1aae208 100644\n--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c\n+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c\n@@ -48,12 +48,14 @@\n \n #include \"rtl8365mb_vlan.h\"\n #include \"rtl8365mb_table.h\"\n+#include \"rtl8365mb.h\"\n #include \u003clinux/if_bridge.h\u003e\n #include \u003clinux/lockdep.h\u003e\n #include \u003clinux/regmap.h\u003e\n \n /* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */\n #define RTL8365MB_CVLAN_ENTRY_SIZE\t\t\t3 /* 48-bits */\n+#define RTL8365MB_D_CVLAN_ENTRY_SIZE\t\t\t2 /* 32-bits, no 3rd word */\n #define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK\t\tGENMASK(7, 0)\n #define   RTL8365MB_CVLAN_MBR_LO_MASK\t\t\tGENMASK(7, 0)\n #define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK\t\tGENMASK(15, 8)\n@@ -65,6 +67,10 @@\n #define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK\t\tGENMASK(13, 9)\n #define   RTL8365MB_CVLAN_METERIDX_LO_MASK\t\tGENMASK(4, 0)\n #define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK\t\tGENMASK(14, 14)\n+#define RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK \\\n+\t\t\t\t\t\t\tGENMASK(2, 2)\n+#define RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK\t\tGENMASK(3, 3)\n+#define RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK\t\tGENMASK(1, 0)\n /* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */\n #define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK\t\tGENMASK(2, 0)\n #define   RTL8365MB_CVLAN_MBR_HI_MASK\t\t\tGENMASK(10, 8)\n@@ -113,6 +119,11 @@\n #define   RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \\\n \t\t(0x1F \u003c\u003c RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p))\n \n+#define RTL8365MB_D_VLAN_PVID_CTRL_BASE\t\t\t0x0700\n+#define RTL8365MB_D_VLAN_PVID_CTRL_REG(port) \\\n+\t(RTL8365MB_D_VLAN_PVID_CTRL_BASE + (port))\n+#define RTL8365MB_D_VLAN_PVID_CTRL_MASK\t\t\tGENMASK(11, 0)\n+\n /* Frame type filtering registers */\n #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE\t0x07aa\n #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \\\n@@ -185,13 +196,16 @@ struct rtl8365mb_vlanmc {\n static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,\n \t\t\t\t  struct rtl8365mb_vlan4k *vlan4k)\n {\n+\tbool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;\n+\tsize_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :\n+\t\t\t\t   RTL8365MB_CVLAN_ENTRY_SIZE;\n \tu16 data[RTL8365MB_CVLAN_ENTRY_SIZE];\n \tint val;\n \tint ret;\n \n \tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,\n \t\t\t\t    RTL8365MB_TABLE_OP_READ, \u0026vid, 0, 0,\n-\t\t\t\t    data, ARRAY_SIZE(data));\n+\t\t\t\t    data, entry_size);\n \tif (ret)\n \t\treturn ret;\n \n@@ -199,33 +213,51 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,\n \tmemset(vlan4k, 0, sizeof(*vlan4k));\n \tvlan4k-\u003evid = vid;\n \n+\t/* member/untag: d0[7:0]/d0[15:8] on both families. Family C\n+\t * extends these into data[2] bits [2:0]/[5:3] for its 9th-11th\n+\t * ports; family D's die has only 8 ports and no third table\n+\t * word, so data[2] does not exist there and must not be read.\n+\t */\n \tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]);\n \tvlan4k-\u003emember = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val);\n-\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);\n-\tvlan4k-\u003emember |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);\n+\tif (!is_d) {\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);\n+\t\tvlan4k-\u003emember |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);\n+\t}\n \n \tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]);\n \tvlan4k-\u003euntag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);\n-\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);\n-\tvlan4k-\u003euntag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);\n-\n-\tvlan4k-\u003efid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);\n-\tvlan4k-\u003epriority_en =\n-\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);\n-\tvlan4k-\u003epriority =\n-\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);\n-\tvlan4k-\u003epolicing_en =\n-\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);\n-\n-\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);\n-\tval = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);\n-\tvlan4k-\u003emeteridx = val;\n-\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);\n-\tval = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);\n-\tvlan4k-\u003emeteridx |= val;\n-\n-\tvlan4k-\u003eivl_en =\n-\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);\n+\tif (!is_d) {\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);\n+\t\tvlan4k-\u003euntag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);\n+\t}\n+\n+\tif (is_d) {\n+\t\tvlan4k-\u003efid = FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, data[1]);\n+\t\t/* Family D has no priority/meter fields in this entry -\n+\t\t * left zeroed by the memset() above.\n+\t\t */\n+\t\tvlan4k-\u003eivl_en =\n+\t\t\tFIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, data[1]);\n+\t} else {\n+\t\tvlan4k-\u003efid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);\n+\t\tvlan4k-\u003epriority_en =\n+\t\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);\n+\t\tvlan4k-\u003epriority =\n+\t\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);\n+\t\tvlan4k-\u003epolicing_en =\n+\t\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);\n+\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);\n+\t\tval = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);\n+\t\tvlan4k-\u003emeteridx = val;\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);\n+\t\tval = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);\n+\t\tvlan4k-\u003emeteridx |= val;\n+\n+\t\tvlan4k-\u003eivl_en =\n+\t\t\tFIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);\n+\t}\n \n \treturn 0;\n }\n@@ -233,6 +265,9 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,\n static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,\n \t\t\t\t   const struct rtl8365mb_vlan4k *vlan4k)\n {\n+\tbool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;\n+\tsize_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :\n+\t\t\t\t   RTL8365MB_CVLAN_ENTRY_SIZE;\n \tu16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };\n \tu16 vid;\n \tint val;\n@@ -244,36 +279,52 @@ static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,\n \tval = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k-\u003euntag);\n \tdata[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val);\n \n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k-\u003efid);\n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,\n-\t\t\t      vlan4k-\u003epriority_en);\n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,\n-\t\t\t      vlan4k-\u003epriority);\n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,\n-\t\t\t      vlan4k-\u003epolicing_en);\n-\n-\t/* FIELD_* does not play nice with struct bitfield. */\n-\tval = vlan4k-\u003emeteridx;\n-\tval = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);\n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);\n-\n-\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,\n-\t\t\t      vlan4k-\u003eivl_en);\n-\n-\tval = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k-\u003emember);\n-\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);\n-\n-\tval = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k-\u003euntag);\n-\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);\n-\n-\tval = vlan4k-\u003emeteridx;\n-\tval = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);\n-\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);\n+\tif (is_d) {\n+\t\t/* The chip supports both IVL and SVL, but the caller (see\n+\t\t * rtl8365mb_vlan_4k_port_set()) never requests SVL, so both\n+\t\t * IVL/SVL selector bits are forced here rather than threaded\n+\t\t * through from vlan4k-\u003eivl_en, which family C does honor.\n+\t\t */\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, 1) |\n+\t\t\t   FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK, 1);\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, vlan4k-\u003efid);\n+\t\t/* No priority/meter/member-untag-extension fields exist in\n+\t\t * family D's 2-word entry - data[1] and data[0] above are\n+\t\t * the whole entry, and data[2] is not part of it at all.\n+\t\t */\n+\t} else {\n+\t\tval = vlan4k-\u003efid;\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, val);\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,\n+\t\t\t\t      vlan4k-\u003epriority_en);\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,\n+\t\t\t\t      vlan4k-\u003epriority);\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,\n+\t\t\t\t      vlan4k-\u003epolicing_en);\n+\n+\t\t/* FIELD_* does not play nice with struct bitfield. */\n+\t\tval = vlan4k-\u003emeteridx;\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);\n+\n+\t\tdata[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,\n+\t\t\t\t      vlan4k-\u003eivl_en);\n+\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k-\u003emember);\n+\t\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);\n+\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k-\u003euntag);\n+\t\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);\n+\n+\t\tval = vlan4k-\u003emeteridx;\n+\t\tval = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);\n+\t\tdata[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);\n+\t}\n \n \tvid = vlan4k-\u003evid;\n \treturn rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,\n \t\t\t\t     RTL8365MB_TABLE_OP_WRITE, \u0026vid, 0, 0,\n-\t\t\t\t     data, ARRAY_SIZE(data));\n+\t\t\t\t     data, entry_size);\n }\n \n static int\n@@ -679,11 +730,22 @@ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)\n \tu8 vlanmc_idx;\n \tint ret;\n \n-\tret = rtl8365mb_vlan_get_pvid_mc(priv, port, \u0026vlanmc_idx, \u0026vlanmc);\n-\tif (ret)\n-\t\treturn ret;\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {\n+\t\tu32 data;\n+\n+\t\tret = regmap_read(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), \u0026data);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t*pvid = data \u0026 RTL8365MB_D_VLAN_PVID_CTRL_MASK;\n+\t} else {\n+\t\tret = rtl8365mb_vlan_get_pvid_mc(priv, port, \u0026vlanmc_idx, \u0026vlanmc);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t*pvid = vlanmc.evid;\n+\t}\n \n-\t*pvid = vlanmc.evid;\n \treturn 0;\n }\n \n@@ -748,6 +810,64 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,\n \t\t\t\t  val);\n }\n \n+/*\n+ * rtl8365mb_vlan_pvid_port_set_direct() - Configure a port's PVID as a raw\n+ * VID written to its dedicated register, for chip families without a\n+ * working VLAN MC table (RTL8365MB_FAMILY_D)\n+ *\n+ * Reads back the previous PVID and frame filter first so both can be\n+ * restored if enabling the new PVID fails partway through, matching\n+ * the rollback behavior of the family-C implementation above.\n+ *\n+ * Context: Can sleep. Must be called with \u0026priv-\u003evlan_lock held.\n+ * Return: 0 on success, or a negative error code on failure.\n+ */\n+static int rtl8365mb_vlan_pvid_port_set_direct(struct realtek_priv *priv,\n+\t\t\t\t\t       int port, u16 vid)\n+{\n+\tenum rtl8365mb_frame_ingress prev_accepted_frame;\n+\tu32 prev_pvid;\n+\tint ret;\n+\n+\tret = regmap_read(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),\n+\t\t\t  \u0026prev_pvid);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to read current PVID\\n\");\n+\t\treturn ret;\n+\t}\n+\tprev_pvid \u0026= RTL8365MB_D_VLAN_PVID_CTRL_MASK;\n+\n+\tret = rtl8365mb_vlan_port_get_framefilter(priv, port, \u0026prev_accepted_frame);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to get current framefilter\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tret = regmap_update_bits(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),\n+\t\t\t\t RTL8365MB_D_VLAN_PVID_CTRL_MASK,\n+\t\t\t\t vid \u0026 RTL8365MB_D_VLAN_PVID_CTRL_MASK);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to set port PVID\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\t/* Changing accept frame is what enables PVID (if not enabled before) */\n+\tret = rtl8365mb_vlan_port_set_framefilter(priv, port,\n+\t\t\t\t\t\t  RTL8365MB_FRAME_TYPE_ANY_FRAME);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to set port frame filter\\n\");\n+\t\tgoto undo_pvid_write;\n+\t}\n+\n+\treturn 0;\n+\n+undo_pvid_write:\n+\t(void)regmap_update_bits(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),\n+\t\t\t\t RTL8365MB_D_VLAN_PVID_CTRL_MASK, prev_pvid);\n+\t(void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame);\n+\treturn ret;\n+}\n+\n /*\n  * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated\n  * VLANMC entry\n@@ -777,6 +897,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,\n \n \tlockdep_assert_held(\u0026priv-\u003evlan_lock);\n \n+\t/* This chip family has no VLAN MC table - PVID is a raw VID in a\n+\t * dedicated per-port register, and there is no separate membership\n+\t * table entry to allocate/track.\n+\t */\n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\treturn rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid);\n+\n \t/* Read the old PVID exclusively to undo in case of error */\n \tret = rtl8365mb_vlan_get_pvid_mc(priv, port, \u0026prev_vlanmc_idx,\n \t\t\t\t\t \u0026prev_vlanmc);\n@@ -856,6 +983,74 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,\n \treturn ret;\n }\n \n+/*\n+ * rtl8365mb_vlan_pvid_port_clear_direct() - Remove a port's raw-VID PVID\n+ * configuration, for chip families without a working VLAN MC table\n+ * (RTL8365MB_FAMILY_D)\n+ *\n+ * Reads back the previous frame filter first so it can be restored if\n+ * clearing the PVID register fails.\n+ *\n+ * Context: Can sleep. Must be called with \u0026priv-\u003evlan_lock held.\n+ * Return: 0 on success, or a negative error code on failure.\n+ */\n+static int rtl8365mb_vlan_pvid_port_clear_direct(struct dsa_switch *ds,\n+\t\t\t\t\t\t int port, u16 vid)\n+{\n+\tenum rtl8365mb_frame_ingress prev_accepted_frame;\n+\tstruct realtek_priv *priv = ds-\u003epriv;\n+\tbool filtering;\n+\tu32 cur_pvid;\n+\tint ret;\n+\n+\tret = regmap_read(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),\n+\t\t\t  \u0026cur_pvid);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to read current PVID\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\t/* Port is not using this VID as PVID. Nothing to remove. */\n+\tif ((cur_pvid \u0026 RTL8365MB_D_VLAN_PVID_CTRL_MASK) != vid)\n+\t\treturn 0;\n+\n+\tfiltering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));\n+\n+\t/* Changing accept frame is what really removes PVID. But only do\n+\t * that if VLAN filtering is enabled.\n+\t */\n+\tif (filtering) {\n+\t\tret = rtl8365mb_vlan_port_get_framefilter(priv, port,\n+\t\t\t\t\t\t\t  \u0026prev_accepted_frame);\n+\t\tif (ret) {\n+\t\t\tdev_err(priv-\u003edev, \"Failed to get current framefilter\\n\");\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = rtl8365mb_vlan_port_set_framefilter(\n+\t\t\tpriv, port, RTL8365MB_FRAME_TYPE_TAGGED_ONLY);\n+\t\tif (ret) {\n+\t\t\tdev_err(priv-\u003edev, \"Failed to set port frame filter\\n\");\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tret = regmap_update_bits(priv-\u003emap, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),\n+\t\t\t\t RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0);\n+\tif (ret) {\n+\t\tdev_err(priv-\u003edev, \"Failed to set port PVID to 0\\n\");\n+\t\tgoto undo_set_framefilter;\n+\t}\n+\n+\treturn 0;\n+\n+undo_set_framefilter:\n+\tif (filtering)\n+\t\t(void)rtl8365mb_vlan_port_set_framefilter(priv, port,\n+\t\t\t\t\t\t\t  prev_accepted_frame);\n+\treturn ret;\n+}\n+\n /*\n  * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration\n  * @ds: dsa switch instance\n@@ -879,6 +1074,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)\n \n \tlockdep_assert_held(\u0026priv-\u003evlan_lock);\n \n+\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n+\t\treturn rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid);\n+\n \tret = rtl8365mb_vlan_get_pvid_mc(priv, port, \u0026vlanmc_idx,\n \t\t\t\t\t \u0026vlanmc);\n \tif (ret) {\n"
}

2/1 2026/10/09 06:04 agent patch-evaluator
2m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The modified code is confined to the Realtek RTL8365MB DSA Ethernet switch driver (drivers/net/dsa/realtek/), which targets vendor-specific discrete switch ASICs connected over SMI or MDIO with Device Tree bindings. This hardware is not present or emulated in standard virtualized fuzzing environments (QEMU/KVM on amd64), making the code structurally unreachable.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 44792155c6a50691b5d2f3d6b7667973657bac0b
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Oct 9 06:04:47 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
new file mode 100644
index 0000000000000..315de4c93c446
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RTL8365MB_H
+#define _RTL8365MB_H
+
+#include "realtek.h"
+
+enum rtl8365mb_family {
+	RTL8365MB_FAMILY_C,
+	RTL8365MB_FAMILY_D,
+};
+
+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv);
+
+#endif /* _RTL8365MB_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index da06f952a053d..eb71c89945c16 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -81,6 +81,7 @@
  *  - RTL8367RB-VB
  *  - RTL8367SB
  *  - RTL8367S
+ *  - RTL8367S-VB
  *  - RTL8370MB
  *  - RTL8310SR
  *
@@ -97,6 +98,7 @@
 #include <linux/irqdomain.h>
 #include <linux/mii.h>
 #include <linux/mutex.h>
+#include <linux/workqueue.h>
 #include <linux/of_irq.h>
 #include <linux/regmap.h>
 #include <linux/if_bridge.h>
@@ -110,12 +112,14 @@
 #include "rtl83xx.h"
 #include "rtl8365mb_l2.h"
 #include "rtl8365mb_vlan.h"
+#include "rtl8365mb.h"
 
 /* Family-specific data and limits */
 #define RTL8365MB_PHYADDRMAX		7
 #define RTL8365MB_NUM_PHYREGS		32
 #define RTL8365MB_PHYREGMAX		(RTL8365MB_NUM_PHYREGS - 1)
 #define RTL8365MB_MAX_NUM_PORTS		11
+#define RTL8365MB_D_MAX_NUM_PORTS	8
 /* Valid for the whole family except RTL8370B, which has 4160 entries.
  * RTL8370B is mentioned in vendor code but it might not even belong
  * to the same RTL8367C family.
@@ -283,6 +287,7 @@
 #define   RTL8365MB_SDS_INDACS_CMD_BUSY_MASK	BIT(8)
 #define   RTL8365MB_SDS_INDACS_CMD_RUN_MASK	BIT(7)
 #define   RTL8365MB_SDS_INDACS_CMD_WR_MASK	BIT(6)
+#define   RTL8365MB_SDS_INDACS_CMD_INDEX_MASK	GENMASK(5, 0)
 #define RTL8365MB_SDS_INDACS_ADR_REG		0x6601
 #define RTL8365MB_SDS_INDACS_DATA_REG		0x6602
 
@@ -296,6 +301,23 @@
 #define   RTL8365MB_SDS_MISC_SGMII_SPD_MASK		GENMASK(8, 7)
 #define   RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK	BIT(6)
 
+/* Re-latch retry interval and attempt budget. Each attempt parks the
+ * SerDes for 20 ms, so a far end that never comes up must not be retried
+ * at this rate forever, and the interval must not be so tight that
+ * back-to-back parks starve a link that is about to latch. The count is
+ * reset to 1 by pcs_config() and to 0 whenever the receiver latches;
+ * attempts start at pcs_config(), well before the far end MAC is up, so
+ * with the interval below the budget is roughly RELATCH_MAX_TRIES seconds,
+ * generous enough to outlast a slow conduit.
+ */
+#define RTL8365MB_D_SDS_RELATCH_INTERVAL	(HZ)
+#define RTL8365MB_D_SDS_RELATCH_MAX_TRIES	15
+
+/* How often a latched (or out-of-budget) SerDes is checked, and re-latched
+ * if still down. Much slower than the re-latch interval.
+ */
+#define RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL	(10 * HZ)
+
 /* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR
  * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the
  * vendor-specific low bits from phase 1 to phase 2, which triggers a data path
@@ -331,6 +353,27 @@
 #define   RTL8365MB_SDS_OPTION_ARM_KEY		0x0249
 #define RTL8365MB_SDS_OPTION_REG		0x13C1
 
+/* Family D uses the SDS13 indirect window for its MAC6 SerDes. */
+#define RTL8365MB_D_SDS_EXT0_INDEX			13
+#define RTL8365MB_D_FIBER_CFG2_REG			0x13E8
+#define   RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK	GENMASK(7, 6)
+#define   RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0	BIT(6)
+#define RTL8365MB_D_SDS_MISC_PA33PC_EN			BIT(11)
+#define RTL8365MB_D_SDS_MISC_PA12PC_EN			BIT(10)
+#define RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0		BIT(9)
+#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK		GENMASK(4, 0)
+#define RTL8365MB_D_SDS_MISC_MODE_SGMII			0x02
+#define RTL8365MB_D_SDS_MISC_MODE_HSGMII		0x12
+#define RTL8365MB_D_SDS_MISC_CFG_MASK			\
+	(RTL8365MB_D_SDS_MISC_PA33PC_EN |		\
+	 RTL8365MB_D_SDS_MISC_PA12PC_EN |		\
+	 RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 |		\
+	 RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK)
+/* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's
+ * 5-bit mode fields.
+ */
+#define RTL8365MB_D_PORT_SDS_MODE_DISABLE		0x1f
+
 /* Embedded DW8051 microcontroller control registers. The microcontroller
  * can run firmware to manage the SerDes link, but this driver keeps it in
  * reset and disabled: phylink already performs the link management that
@@ -343,6 +386,7 @@
 #define RTL8365MB_PORT_SPEED_10M	0
 #define RTL8365MB_PORT_SPEED_100M	1
 #define RTL8365MB_PORT_SPEED_1000M	2
+#define RTL8365MB_D_PORT_SPEED_2500M	5
 
 /* External interface force configuration registers 0~2 */
 #define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0		0x1310 /* EXT0 */
@@ -360,6 +404,20 @@
 #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK		BIT(4)
 #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK		BIT(2)
 #define   RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK		GENMASK(1, 0)
+#define   RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH		2 /* bits[1:0] */
+
+/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */
+#define   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK	GENMASK(13, 12)
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE		0x12c0
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \
+		(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port))
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE		0x12c8
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \
+		(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port))
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK	GENMASK(15, 0)
 
 /* CPU port mask register - controls which ports are treated as CPU ports */
 #define RTL8365MB_CPU_PORT_MASK_REG	0x1219
@@ -441,6 +499,20 @@
 #define   RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK		GENMASK(5, 4)
 #define   RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK	GENMASK(3, 0)
 
+/* EXT_TXC_DLY holds a 3-bit TX clock delay per external interface, for
+ * RGMII ([2:0] EXT0, [5:3] EXT1, [8:6] EXT2) and separately for GMII.
+ * Only the EXT1 RGMII field is used here, and it is cleared in RGMII
+ * mode so that tx-internal-delay-ps is the only TX delay applied.
+ */
+#define RTL8365MB_D_REG_EXT_TXC_DLY				0x13f9
+#define   RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK			GENMASK(5, 3)
+
+#define RTL8365MB_D_REG_TOP_CON0				0x1d70
+#define   RTL8365MB_D_MAC7_SEL_EXT1_MASK			BIT(13)
+#define   RTL8365MB_D_MAC4_SEL_EXT1_MASK			BIT(12)
+
+#define RTL8365MB_D_REG_SDS1_MISC0				0x1d78
+
 /**
  * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode
  * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry
@@ -676,6 +748,21 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {
 	{ 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },
 };
 
+/* Family D tuning tables from the Realtek vendor port API. */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_sgmii[] = {
+	{ 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },
+	{ 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },
+	{ 0x0484, 0x011B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },
+	{ 0x0004, 0x074F },
+};
+
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_hsgmii[] = {
+	{ 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },
+	{ 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },
+	{ 0x0504, 0x051B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },
+	{ 0x0004, 0x074F },
+};
+
 enum rtl8365mb_phy_interface_mode {
 	RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
 	RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -707,6 +794,7 @@ struct rtl8365mb_extint {
  * @name: human-readable chip name
  * @chip_id: chip identifier
  * @chip_ver: chip silicon revision
+ * @family: chip family
  * @extints: available external interfaces
  * @jam_table: chip-specific initialization jam table
  * @jam_size: size of the chip's jam table
@@ -719,6 +807,7 @@ struct rtl8365mb_chip_info {
 	const char *name;
 	u32 chip_id;
 	u32 chip_ver;
+	enum rtl8365mb_family family;
 	const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS];
 	const struct rtl8365mb_jam_tbl_entry *jam_table;
 	size_t jam_size;
@@ -731,6 +820,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
 		.name = "RTL8365MB-VC",
 		.chip_id = 0x6367,
 		.chip_ver = 0x0040,
+		.family = RTL8365MB_FAMILY_C,
 		.extints = {
 			{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
 				PHY_INTF(RMII) | PHY_INTF(RGMII) },
@@ -742,6 +832,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
 		.name = "RTL8367S",
 		.chip_id = 0x6367,
 		.chip_ver = 0x00A0,
+		.family = RTL8365MB_FAMILY_C,
 		.extints = {
 			{ 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
 			{ 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) |
@@ -754,6 +845,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
 		.name = "RTL8367SB",
 		.chip_id = 0x6367,
 		.chip_ver = 0x0010,
+		.family = RTL8365MB_FAMILY_C,
 		.extints = {
 			{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
 				PHY_INTF(RMII) | PHY_INTF(RGMII) |
@@ -768,6 +860,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
 		.name = "RTL8367RB-VB",
 		.chip_id = 0x6367,
 		.chip_ver = 0x0020,
+		.family = RTL8365MB_FAMILY_C,
 		.extints = {
 			{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
 				PHY_INTF(RMII) | PHY_INTF(RGMII) },
@@ -777,6 +870,19 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
 		.jam_table = rtl8365mb_init_jam_8365mb_vc,
 		.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
 	},
+	{
+		.name = "RTL8367S-VB",
+		.chip_id = 0x6642,
+		.chip_ver = 0x0010,
+		.family = RTL8365MB_FAMILY_D,
+		.extints = {
+			{ 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
+			{ 7, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
+				PHY_INTF(RMII) | PHY_INTF(RGMII) },
+		},
+		.jam_table = rtl8365mb_init_jam_8365mb_vc,
+		.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+	},
 };
 
 enum rtl8365mb_stp_state {
@@ -854,10 +960,16 @@ struct rtl8365mb_port {
  * @chip_info: chip-specific info about the attached switch
  * @cpu: CPU tagging and CPU port configuration for this chip
  * @mib_lock: prevent concurrent reads of MIB counters
+ * @sds_lock: serializes access to the shared SDS_INDACS ADR/CMD/DATA window
+ *            and to RTL8365MB_SDS_MISC_REG, reachable both from phylink's
+ *            PCS callbacks and from the family D SerDes re-latch work
  * @ports: per-port data
  * @pcs: PCS for the SerDes external interface
  * @sds_supported: SerDes tuning parameters match the chip option, so the
  *                 SerDes interface modes can be advertised
+ * @sds_relatch: re-latch edges and link health check (family D)
+ * @sds_relatch_count: next attempt of the re-latch episode, 0 once latched
+ * @sds_misc_target_val: SDS_MISC_CFG_MASK fields the re-latch work restores
  *
  * Private data for this driver.
  */
@@ -867,13 +979,24 @@ struct rtl8365mb {
 	const struct rtl8365mb_chip_info *chip_info;
 	struct rtl8365mb_cpu cpu;
 	struct mutex mib_lock;
+	struct mutex sds_lock;
 	struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
 	struct phylink_pcs pcs;
 	bool sds_supported;
+	struct delayed_work sds_relatch;
+	unsigned int sds_relatch_count;
+	u32 sds_misc_target_val;
 };
 
 #define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
 
+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv)
+{
+	struct rtl8365mb *mb = priv->chip_data;
+
+	return mb->chip_info->family;
+}
+
 static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
 {
 	u32 val;
@@ -1164,6 +1287,7 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
 	struct dsa_port *dp;
 	int tx_delay = 0;
 	int rx_delay = 0;
+	u32 data;
 	u32 val;
 	int ret;
 
@@ -1233,43 +1357,85 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
 	if (ret)
 		return ret;
 
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D && extint->id == 1) {
+		ret = regmap_update_bits(priv->map,
+					 RTL8365MB_D_REG_EXT_TXC_DLY,
+					 RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK, 0);
+		if (ret)
+			return ret;
+		/* Configure RGMII/MII mux to port 7 if UTP_PORT4 is not RGMII mode */
+		ret = regmap_read(priv->map, RTL8365MB_D_REG_TOP_CON0, &data);
+		if (ret)
+			return ret;
+		if ((data & RTL8365MB_D_MAC4_SEL_EXT1_MASK) == 0) {
+			ret = regmap_update_bits(priv->map,
+						 RTL8365MB_D_REG_TOP_CON0,
+						 RTL8365MB_D_MAC7_SEL_EXT1_MASK,
+						 RTL8365MB_D_MAC7_SEL_EXT1_MASK);
+			if (ret)
+				return ret;
+		}
+		ret = regmap_update_bits(priv->map,
+					 RTL8365MB_D_REG_SDS1_MISC0,
+					 RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK,
+					 RTL8365MB_D_PORT_SDS_MODE_DISABLE);
+		if (ret)
+			return ret;
+	}
+
 	return 0;
 }
 
-static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index,
+			       u16 addr, u16 data)
 {
+	struct rtl8365mb *mb = priv->chip_data;
 	int ret;
 
+	mutex_lock(&mb->sds_lock);
+
 	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data);
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	/* The SerDes indirect access engine completes the command within the
 	 * register write transaction, so there is no need to wait or poll for
 	 * completion before the next access, matching the vendor driver.
 	 */
-	return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
-			    RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
-			    RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+			   RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+			   RTL8365MB_SDS_INDACS_CMD_WR_MASK |
+			   FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+				      index));
+
+out_unlock:
+	mutex_unlock(&mb->sds_lock);
+	return ret;
 }
 
-static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,
+			      u16 addr, u16 *data)
 {
+	struct rtl8365mb *mb = priv->chip_data;
 	u32 val;
 	int ret;
 
+	mutex_lock(&mb->sds_lock);
+
 	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
-			   RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+			   RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+			   FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+				      index));
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	/* Wait for the indirect read to complete: the engine clears the BUSY
 	 * bit once the data register holds the result.
@@ -1279,15 +1445,19 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
 				       !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),
 				       10, 1000);
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val);
 	if (ret)
-		return ret;
+		goto out_unlock;
 
 	*data = val;
 
-	return 0;
+	ret = 0;
+
+out_unlock:
+	mutex_unlock(&mb->sds_lock);
+	return ret;
 }
 
 /* The vendor driver selects between two sets of SerDes tuning parameters based
@@ -1307,6 +1477,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
 	int ret;
 	int i;
 
+	/* Family D has a fixed SDS13 programming model and does not use the
+	 * family C option register to select its tuning table.
+	 */
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
+		mb->sds_supported = true;
+		return 0;
+	}
+
 	/* Nothing to probe if no external interface is wired to the SerDes */
 	for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) {
 		extint = &mb->chip_info->extints[i];
@@ -1385,28 +1563,54 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 				const unsigned long *advertising,
 				bool permit_pause_to_mac)
 {
-	const struct rtl8365mb_jam_tbl_entry *sds_jam;
 	const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
+	const struct rtl8365mb_jam_tbl_entry *sds_jam;
 	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
-	struct realtek_priv *priv;
+	struct realtek_priv *priv = mb->priv;
 	size_t sds_jam_size;
-	u32 mode;
+	u32 misc_mask;
+	u32 misc_val;
+	u32 sds_mode;
+	u8 sds_index;
+	bool is_d;
 	u16 val;
 	int ret;
 	int i;
 
-	priv = mb->priv;
+	is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
 
+	/* Cancel any in-flight re-latch edge before touching SDS_MISC: the
+	 * work drops sds_lock across its sleep and could otherwise interleave
+	 * with the reconfiguration below.
+	 */
+	if (is_d)
+		cancel_delayed_work_sync(&mb->sds_relatch);
+
+	/* Select the appropriate tuning table and SDS mode */
 	if (interface == PHY_INTERFACE_MODE_2500BASEX) {
-		sds_jam = rtl8365mb_sds_jam_hsgmii;
-		sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
-		mode = RTL8365MB_EXT_PORT_MODE_HSGMII;
+		if (is_d) {
+			sds_jam = rtl8365mb_d_sds_jam_hsgmii;
+			sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_hsgmii);
+			sds_mode = RTL8365MB_D_SDS_MISC_MODE_HSGMII;
+		} else {
+			sds_jam = rtl8365mb_sds_jam_hsgmii;
+			sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
+			sds_mode = RTL8365MB_EXT_PORT_MODE_HSGMII;
+		}
 	} else {
-		sds_jam = rtl8365mb_sds_jam_sgmii;
-		sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
-		mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+		if (is_d) {
+			sds_jam = rtl8365mb_d_sds_jam_sgmii;
+			sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_sgmii);
+			sds_mode = RTL8365MB_D_SDS_MISC_MODE_SGMII;
+		} else {
+			sds_jam = rtl8365mb_sds_jam_sgmii;
+			sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
+			sds_mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+		}
 	}
 
+	sds_index = is_d ? RTL8365MB_D_SDS_EXT0_INDEX : 0;
+
 	/* Hold the embedded DW8051 microcontroller in reset and keep it
 	 * disabled. The vendor driver loads firmware into it to manage the
 	 * SerDes link, but the firmware only duplicates work that phylink
@@ -1435,34 +1639,57 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 
 	/* Tune the SerDes with vendor-prescribed parameters */
 	for (i = 0; i < sds_jam_size; i++) {
-		ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
-					  sds_jam[i].val);
+		ret = rtl8365mb_sds_write(priv, sds_index,
+					  sds_jam[i].reg, sds_jam[i].val);
+		if (ret)
+			return ret;
+	}
+
+	/* Family-specific post-tuning configuration */
+	if (is_d) {
+		ret = regmap_update_bits(priv->map, RTL8365MB_D_FIBER_CFG2_REG,
+					 RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK,
+					 RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0);
 		if (ret)
 			return ret;
+
+		misc_mask = RTL8365MB_D_SDS_MISC_CFG_MASK;
+		misc_val  = RTL8365MB_D_SDS_MISC_PA33PC_EN |
+			    RTL8365MB_D_SDS_MISC_PA12PC_EN |
+			    RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | sds_mode;
+	} else {
+		/* Mux the SerDes to MAC8 in the requested mode */
+		misc_mask = RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
+			    RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;
+		misc_val  = (sds_mode == RTL8365MB_EXT_PORT_MODE_SGMII) ?
+			    RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
+			    RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;
 	}
 
-	/* Mux the SerDes to MAC8 in the requested mode */
+	mutex_lock(&mb->sds_lock);
 	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
-				 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
-					 RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
-				 mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
-					 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
-					 RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
+				 misc_mask, misc_val);
+	mutex_unlock(&mb->sds_lock);
 	if (ret)
 		return ret;
 
-	val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
-	ret = regmap_update_bits(priv->map,
-				 RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
-				 RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
-				 val);
-	if (ret)
-		return ret;
+	if (is_d) {
+		WRITE_ONCE(mb->sds_misc_target_val, misc_val);
+	} else {
+		val = sds_mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+		ret = regmap_update_bits(priv->map,
+					 RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
+					 RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
+					 val);
+		if (ret)
+			return ret;
+	}
 
 	/* Take the SerDes out of reset. The vendor driver does this only
 	 * after the SerDes mux and the interface mode are configured.
 	 */
-	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET,
+	ret = rtl8365mb_sds_write(priv, sds_index,
+				  RTL8365MB_SDS_REG_RESET,
 				  RTL8365MB_SDS_RESET_DEASSERT);
 	if (ret)
 		return ret;
@@ -1472,12 +1699,14 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 	 * This flushes the FIFOs and ensures a clean state for the link,
 	 * preventing silent drops and CRC errors.
 	 */
-	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+	ret = rtl8365mb_sds_write(priv, sds_index,
+				  RTL8365MB_SDS_REG_BMCR,
 				  RTL8365MB_SDS_BMCR_DPRST_PHASE1);
 	if (ret)
 		return ret;
 
-	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+	ret = rtl8365mb_sds_write(priv, sds_index,
+				  RTL8365MB_SDS_REG_BMCR,
 				  RTL8365MB_SDS_BMCR_DPRST_PHASE2);
 	if (ret)
 		return ret;
@@ -1485,14 +1714,148 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 	/* Keep SGMII in-band autonegotiation disabled: the link parameters are
 	 * forced from rtl8365mb_pcs_link_up() instead.
 	 */
-	ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val);
+	ret = rtl8365mb_sds_read(priv, sds_index,
+				 RTL8365MB_SDS_REG_NWAY, &val);
 	if (ret)
 		return ret;
 
 	val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
 	val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
 
-	return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val);
+	ret = rtl8365mb_sds_write(priv, sds_index,
+				  RTL8365MB_SDS_REG_NWAY, val);
+	if (ret)
+		return ret;
+
+	if (is_d) {
+		/* Start a new re-latch episode and kick off the first attempt;
+		 * see rtl8365mb_sds_relatch_work() for why this cannot wait
+		 * for phylink to ask again.
+		 */
+		WRITE_ONCE(mb->sds_relatch_count, 1);
+		schedule_delayed_work(&mb->sds_relatch, 0);
+	}
+
+	return 0;
+}
+
+/* The family D receiver latches on a DISABLE -> mode edge in SDS_MISC
+ * rather than on the value, and only once the far-end MAC has brought up
+ * its half of the link. No local signal predicts that (bit 8 of the SDS
+ * link-status word does not change ahead of it), so the work cannot wait
+ * for it, only check afterwards whether an edge took. Nor does phylink
+ * ever call rtl8365mb_pcs_get_state() for this fixed-link port
+ * (mb->pcs.poll only arms phylink's own poll for in-band links), so this
+ * cannot rely on being polled at all. Instead, pcs_config() starts an
+ * episode and this work re-arms itself after every edge, its next run
+ * checking whether the edge took.
+ *
+ * An episode is capped at RTL8365MB_D_SDS_RELATCH_MAX_TRIES edges, spaced
+ * RTL8365MB_D_SDS_RELATCH_INTERVAL apart, so that a far end which never
+ * comes up costs a bounded burst of 20 ms parks. The run after the last
+ * edge only checks whether it took and reports failure; from then on one
+ * edge is driven per RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL, so a far end
+ * that comes up late is recovered without a reconfiguration. Once the
+ * receiver has latched the work just polls the link at the same slow
+ * interval; finding it down then starts a new episode, e.g. after the far
+ * end reset its PLL without a reconfiguration.
+ *
+ * sds_relatch_count is the number of the next attempt of the episode: 0
+ * while latched, 1..MAX_TRIES in budget, MAX_TRIES + 1 for the run that
+ * only reports, and above that once out of budget.
+ */
+static void rtl8365mb_sds_relatch_work(struct work_struct *work)
+{
+	struct rtl8365mb *mb = container_of(to_delayed_work(work),
+					    struct rtl8365mb, sds_relatch);
+	struct realtek_priv *priv = mb->priv;
+	u32 park = (READ_ONCE(mb->sds_misc_target_val) &
+		    ~RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK) |
+		   RTL8365MB_D_PORT_SDS_MODE_DISABLE;
+	unsigned long delay = RTL8365MB_D_SDS_RELATCH_INTERVAL;
+	unsigned int attempts;
+	u16 status;
+	int ret;
+
+	/* The condition that queued this work may already be stale by the
+	 * time it runs (e.g. an earlier edge from a prior attempt just
+	 * latched). Parking an already-live link would drop it for no
+	 * reason.
+	 */
+	ret = rtl8365mb_sds_read(priv, RTL8365MB_D_SDS_EXT0_INDEX,
+				 RTL8365MB_SDS_REG_LINK_STATUS, &status);
+	if (ret) {
+		dev_err_ratelimited(priv->dev,
+				    "failed to read SerDes link status: %pe\n",
+				    ERR_PTR(ret));
+		goto rearm;
+	}
+	if (status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK) {
+		/* Latched: end the episode, keep watching for a later loss. */
+		WRITE_ONCE(mb->sds_relatch_count, 0);
+		delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+		goto rearm;
+	}
+
+	attempts = READ_ONCE(mb->sds_relatch_count);
+	if (!attempts) {
+		dev_warn(priv->dev,
+			 "SerDes link lost outside a reconfiguration; re-latching\n");
+		attempts = 1;
+	} else if (attempts == RTL8365MB_D_SDS_RELATCH_MAX_TRIES + 1) {
+		/* This run only checked whether the last in-budget edge
+		 * took. The first out-of-budget edge comes one healthcheck
+		 * interval from now, not on this pass.
+		 */
+		WRITE_ONCE(mb->sds_relatch_count, attempts + 1);
+		dev_warn(priv->dev,
+			 "SerDes did not latch after %u re-latch attempts; still retrying every %d s\n",
+			 RTL8365MB_D_SDS_RELATCH_MAX_TRIES,
+			 RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL / HZ);
+		delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+		goto rearm;
+	}
+
+	if (attempts <= RTL8365MB_D_SDS_RELATCH_MAX_TRIES)
+		WRITE_ONCE(mb->sds_relatch_count, attempts + 1);
+	else
+		/* Past the fast-retry budget. The receiver latches only on a
+		 * DISABLE -> mode edge and nothing re-runs pcs_config() for a
+		 * fixed-link port, so keep driving one edge per interval.
+		 */
+		delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+
+	mutex_lock(&mb->sds_lock);
+	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+				 RTL8365MB_D_SDS_MISC_CFG_MASK, park);
+	mutex_unlock(&mb->sds_lock);
+	if (ret) {
+		dev_err_ratelimited(priv->dev,
+				    "failed to park SDS_MISC: %pe\n",
+				    ERR_PTR(ret));
+		goto rearm;
+	}
+
+	usleep_range(20000, 21000);
+
+	mutex_lock(&mb->sds_lock);
+	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+				 RTL8365MB_D_SDS_MISC_CFG_MASK,
+				 READ_ONCE(mb->sds_misc_target_val));
+	mutex_unlock(&mb->sds_lock);
+	if (ret) {
+		dev_err_ratelimited(priv->dev,
+				    "failed to restore SDS_MISC: %pe\n",
+				    ERR_PTR(ret));
+		goto rearm;
+	}
+
+	dev_dbg(priv->dev, "SerDes re-latch edge driven (attempt %u)\n",
+		attempts);
+
+	/* This edge may not have taken either; the next run checks. */
+rearm:
+	schedule_delayed_work(&mb->sds_relatch, delay);
 }
 
 static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
@@ -1517,10 +1880,14 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
 {
 	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
 	struct realtek_priv *priv = mb->priv;
+	u8 sds_index = 0;
 	u16 status;
+	bool is_d;
 	u32 val;
 	int ret;
 
+	is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+
 	/* In-band autonegotiation is not implemented, so the link parameters are
 	 * forced from rtl8365mb_pcs_link_up(). The real link state must still be
 	 * read from the SerDes itself: the embedded DW8051 microcontroller that
@@ -1528,7 +1895,11 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
 	 * rtl8365mb_pcs_config()), so the link status register can be read
 	 * directly through the SDS_INDACS window without racing the auto-poll.
 	 */
-	ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status);
+	if (is_d)
+		sds_index = RTL8365MB_D_SDS_EXT0_INDEX;
+
+	ret = rtl8365mb_sds_read(priv, sds_index,
+				 RTL8365MB_SDS_REG_LINK_STATUS, &status);
 	if (ret) {
 		state->link = false;
 		return;
@@ -1539,10 +1910,19 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
 	if (!state->link)
 		return;
 
+	if (is_d) {
+		state->duplex = DUPLEX_FULL;
+		state->speed = state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+				SPEED_2500 : SPEED_1000;
+		return;
+	}
+
 	/* The speed and duplex are forced; read them back from the values
 	 * programmed into the SerDes MISC register.
 	 */
+	mutex_lock(&mb->sds_lock);
 	ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val);
+	mutex_unlock(&mb->sds_lock);
 	if (ret) {
 		state->link = false;
 		return;
@@ -1580,6 +1960,12 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
 	u32 r_speed;
 	int ret;
 
+	/* Family D forces the external MAC ability from mac_link_up(); its
+	 * SDS_MISC fields do not share the family C link-force layout.
+	 */
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		return;
+
 	/* The speed field has no value for 2.5 Gbps: the rate is determined by
 	 * the HSGMII SerDes configuration, and the vendor driver programs the
 	 * 1 Gbps value here.
@@ -1606,7 +1992,9 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
 	 * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause
 	 * modes are known.
 	 */
+	mutex_lock(&mb->sds_lock);
 	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val);
+	mutex_unlock(&mb->sds_lock);
 	if (ret) {
 		dev_err(priv->dev, "failed to force SerDes link: %pe\n",
 			ERR_PTR(ret));
@@ -1632,23 +2020,31 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
 	u32 r_duplex;
 	u32 r_speed;
 	u32 r_link;
+	bool is_d;
 	int val;
 	int ret;
 
 	if (!extint)
 		return -ENODEV;
 
+	is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
 	if (link) {
 		/* Force the link up with the desired configuration */
 		r_link = 1;
 		r_rx_pause = rx_pause ? 1 : 0;
 		r_tx_pause = tx_pause ? 1 : 0;
 
-		/* The speed field has no value for 2.5 Gbps: the rate is
-		 * determined by the HSGMII SerDes configuration, and the
-		 * vendor driver programs the 1 Gbps value here.
-		 */
-		if (speed == SPEED_2500 || speed == SPEED_1000) {
+		if (speed == SPEED_2500) {
+			if (is_d) {
+				r_speed = RTL8365MB_D_PORT_SPEED_2500M;
+			} else {
+				/* The speed field has no value for 2.5 Gbps: the rate is
+				 * determined by the HSGMII SerDes configuration, and the
+				 * vendor driver programs the 1 Gbps value here.
+				 */
+				r_speed = RTL8365MB_PORT_SPEED_1000M;
+			}
+		} else if (speed == SPEED_1000) {
 			r_speed = RTL8365MB_PORT_SPEED_1000M;
 		} else if (speed == SPEED_100) {
 			r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1678,20 +2074,42 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
 		r_duplex = 0;
 	}
 
-	val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) |
-	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,
+	val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,
 			 r_tx_pause) |
 	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK,
 			 r_rx_pause) |
 	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) |
 	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK,
 			 r_duplex) |
-	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed);
-	ret = regmap_write(priv->map,
-			   RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
-			   val);
-	if (ret)
-		return ret;
+	      FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK,
+			 r_speed & RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK);
+
+	if (is_d) {
+		/* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED,
+		 * bit[2] goes into FORCE_SPEED2 (bit 12); only 2500M sets it,
+		 * bit 13 of the field is unused.
+		 */
+		val |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK,
+				  r_speed >> RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH);
+		ret = regmap_write(priv->map,
+				   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(port),
+				   val);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(priv->map,
+				   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(port),
+				   RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK);
+		if (ret)
+			return ret;
+	} else {
+		val |= FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1);
+		ret = regmap_write(priv->map,
+				   RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
+				   val);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
@@ -1876,7 +2294,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
 		 * rtl8365mb_pcs_link_up() because pcs_link_up() carries no
 		 * pause information.
 		 */
-		if (rtl8365mb_interface_is_serdes(interface)) {
+		if (rtl8365mb_interface_is_serdes(interface) &&
+		    rtl8365mb_get_family(priv) != RTL8365MB_FAMILY_D) {
 			u32 val = 0;
 
 			if (tx_pause)
@@ -1884,11 +2303,13 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
 			if (rx_pause)
 				val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;
 
+			mutex_lock(&mb->sds_lock);
 			ret = regmap_update_bits(priv->map,
 						 RTL8365MB_SDS_MISC_REG,
 						 RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |
 							 RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,
 						 val);
+			mutex_unlock(&mb->sds_lock);
 			if (ret)
 				dev_err(priv->dev,
 					"failed to force SerDes pause modes on port %d: %pe\n",
@@ -3100,6 +3521,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 	 */
 	mb->pcs.poll = true;
 
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		INIT_DELAYED_WORK(&mb->sds_relatch,
+				  rtl8365mb_sds_relatch_work);
+
 	ret = rtl8365mb_reset_chip(priv);
 	if (ret) {
 		dev_err(priv->dev, "failed to reset chip: %pe\n",
@@ -3284,6 +3709,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 static void rtl8365mb_teardown(struct dsa_switch *ds)
 {
 	struct realtek_priv *priv = ds->priv;
+	struct rtl8365mb *mb = priv->chip_data;
+
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		cancel_delayed_work_sync(&mb->sds_relatch);
 
 	rtl8365mb_stats_teardown(priv);
 	rtl8365mb_irq_teardown(priv);
@@ -3349,7 +3778,15 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
 
 	dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name);
 
-	priv->num_ports = RTL8365MB_MAX_NUM_PORTS;
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		priv->num_ports = RTL8365MB_D_MAX_NUM_PORTS;
+	else
+		priv->num_ports = RTL8365MB_MAX_NUM_PORTS;
+
+	ret = devm_mutex_init(priv->dev, &mb->sds_lock);
+	if (ret)
+		return ret;
+
 	mb->priv = priv;
 	mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS;
 	mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 8d49ffadce926..da517b1aae208 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -48,12 +48,14 @@
 
 #include "rtl8365mb_vlan.h"
 #include "rtl8365mb_table.h"
+#include "rtl8365mb.h"
 #include <linux/if_bridge.h>
 #include <linux/lockdep.h>
 #include <linux/regmap.h>
 
 /* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */
 #define RTL8365MB_CVLAN_ENTRY_SIZE			3 /* 48-bits */
+#define RTL8365MB_D_CVLAN_ENTRY_SIZE			2 /* 32-bits, no 3rd word */
 #define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK		GENMASK(7, 0)
 #define   RTL8365MB_CVLAN_MBR_LO_MASK			GENMASK(7, 0)
 #define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK		GENMASK(15, 8)
@@ -65,6 +67,10 @@
 #define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK		GENMASK(13, 9)
 #define   RTL8365MB_CVLAN_METERIDX_LO_MASK		GENMASK(4, 0)
 #define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK		GENMASK(14, 14)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK \
+							GENMASK(2, 2)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK		GENMASK(3, 3)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK		GENMASK(1, 0)
 /* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */
 #define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK		GENMASK(2, 0)
 #define   RTL8365MB_CVLAN_MBR_HI_MASK			GENMASK(10, 8)
@@ -113,6 +119,11 @@
 #define   RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \
 		(0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p))
 
+#define RTL8365MB_D_VLAN_PVID_CTRL_BASE			0x0700
+#define RTL8365MB_D_VLAN_PVID_CTRL_REG(port) \
+	(RTL8365MB_D_VLAN_PVID_CTRL_BASE + (port))
+#define RTL8365MB_D_VLAN_PVID_CTRL_MASK			GENMASK(11, 0)
+
 /* Frame type filtering registers */
 #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE	0x07aa
 #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \
@@ -185,13 +196,16 @@ struct rtl8365mb_vlanmc {
 static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
 				  struct rtl8365mb_vlan4k *vlan4k)
 {
+	bool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+	size_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :
+				   RTL8365MB_CVLAN_ENTRY_SIZE;
 	u16 data[RTL8365MB_CVLAN_ENTRY_SIZE];
 	int val;
 	int ret;
 
 	ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
 				    RTL8365MB_TABLE_OP_READ, &vid, 0, 0,
-				    data, ARRAY_SIZE(data));
+				    data, entry_size);
 	if (ret)
 		return ret;
 
@@ -199,33 +213,51 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
 	memset(vlan4k, 0, sizeof(*vlan4k));
 	vlan4k->vid = vid;
 
+	/* member/untag: d0[7:0]/d0[15:8] on both families. Family C
+	 * extends these into data[2] bits [2:0]/[5:3] for its 9th-11th
+	 * ports; family D's die has only 8 ports and no third table
+	 * word, so data[2] does not exist there and must not be read.
+	 */
 	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]);
 	vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val);
-	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
-	vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
+	if (!is_d) {
+		val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
+		vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
+	}
 
 	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]);
 	vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);
-	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
-	vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
-
-	vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
-	vlan4k->priority_en =
-		FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
-	vlan4k->priority =
-		FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
-	vlan4k->policing_en =
-		FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
-
-	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
-	val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
-	vlan4k->meteridx = val;
-	val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
-	val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
-	vlan4k->meteridx |= val;
-
-	vlan4k->ivl_en =
-		FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
+	if (!is_d) {
+		val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
+		vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
+	}
+
+	if (is_d) {
+		vlan4k->fid = FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, data[1]);
+		/* Family D has no priority/meter fields in this entry -
+		 * left zeroed by the memset() above.
+		 */
+		vlan4k->ivl_en =
+			FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, data[1]);
+	} else {
+		vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
+		vlan4k->priority_en =
+			FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
+		vlan4k->priority =
+			FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
+		vlan4k->policing_en =
+			FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
+
+		val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
+		val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+		vlan4k->meteridx = val;
+		val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
+		val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+		vlan4k->meteridx |= val;
+
+		vlan4k->ivl_en =
+			FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
+	}
 
 	return 0;
 }
@@ -233,6 +265,9 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
 static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
 				   const struct rtl8365mb_vlan4k *vlan4k)
 {
+	bool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+	size_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :
+				   RTL8365MB_CVLAN_ENTRY_SIZE;
 	u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };
 	u16 vid;
 	int val;
@@ -244,36 +279,52 @@ static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
 	val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag);
 	data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val);
 
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
-			      vlan4k->priority_en);
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
-			      vlan4k->priority);
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
-			      vlan4k->policing_en);
-
-	/* FIELD_* does not play nice with struct bitfield. */
-	val = vlan4k->meteridx;
-	val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
-
-	data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
-			      vlan4k->ivl_en);
-
-	val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
-	data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
-
-	val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
-	data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
-
-	val = vlan4k->meteridx;
-	val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
-	data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
+	if (is_d) {
+		/* The chip supports both IVL and SVL, but the caller (see
+		 * rtl8365mb_vlan_4k_port_set()) never requests SVL, so both
+		 * IVL/SVL selector bits are forced here rather than threaded
+		 * through from vlan4k->ivl_en, which family C does honor.
+		 */
+		data[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, 1) |
+			   FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK, 1);
+		data[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
+		/* No priority/meter/member-untag-extension fields exist in
+		 * family D's 2-word entry - data[1] and data[0] above are
+		 * the whole entry, and data[2] is not part of it at all.
+		 */
+	} else {
+		val = vlan4k->fid;
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, val);
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
+				      vlan4k->priority_en);
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
+				      vlan4k->priority);
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
+				      vlan4k->policing_en);
+
+		/* FIELD_* does not play nice with struct bitfield. */
+		val = vlan4k->meteridx;
+		val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
+
+		data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
+				      vlan4k->ivl_en);
+
+		val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
+		data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
+
+		val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
+		data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
+
+		val = vlan4k->meteridx;
+		val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+		data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
+	}
 
 	vid = vlan4k->vid;
 	return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
 				     RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0,
-				     data, ARRAY_SIZE(data));
+				     data, entry_size);
 }
 
 static int
@@ -679,11 +730,22 @@ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)
 	u8 vlanmc_idx;
 	int ret;
 
-	ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
-	if (ret)
-		return ret;
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
+		u32 data;
+
+		ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), &data);
+		if (ret)
+			return ret;
+
+		*pvid = data & RTL8365MB_D_VLAN_PVID_CTRL_MASK;
+	} else {
+		ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
+		if (ret)
+			return ret;
+
+		*pvid = vlanmc.evid;
+	}
 
-	*pvid = vlanmc.evid;
 	return 0;
 }
 
@@ -748,6 +810,64 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
 				  val);
 }
 
+/*
+ * rtl8365mb_vlan_pvid_port_set_direct() - Configure a port's PVID as a raw
+ * VID written to its dedicated register, for chip families without a
+ * working VLAN MC table (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous PVID and frame filter first so both can be
+ * restored if enabling the new PVID fails partway through, matching
+ * the rollback behavior of the family-C implementation above.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_set_direct(struct realtek_priv *priv,
+					       int port, u16 vid)
+{
+	enum rtl8365mb_frame_ingress prev_accepted_frame;
+	u32 prev_pvid;
+	int ret;
+
+	ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+			  &prev_pvid);
+	if (ret) {
+		dev_err(priv->dev, "Failed to read current PVID\n");
+		return ret;
+	}
+	prev_pvid &= RTL8365MB_D_VLAN_PVID_CTRL_MASK;
+
+	ret = rtl8365mb_vlan_port_get_framefilter(priv, port, &prev_accepted_frame);
+	if (ret) {
+		dev_err(priv->dev, "Failed to get current framefilter\n");
+		return ret;
+	}
+
+	ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+				 RTL8365MB_D_VLAN_PVID_CTRL_MASK,
+				 vid & RTL8365MB_D_VLAN_PVID_CTRL_MASK);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set port PVID\n");
+		return ret;
+	}
+
+	/* Changing accept frame is what enables PVID (if not enabled before) */
+	ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
+						  RTL8365MB_FRAME_TYPE_ANY_FRAME);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set port frame filter\n");
+		goto undo_pvid_write;
+	}
+
+	return 0;
+
+undo_pvid_write:
+	(void)regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+				 RTL8365MB_D_VLAN_PVID_CTRL_MASK, prev_pvid);
+	(void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame);
+	return ret;
+}
+
 /*
  * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
  * VLANMC entry
@@ -777,6 +897,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
 
 	lockdep_assert_held(&priv->vlan_lock);
 
+	/* This chip family has no VLAN MC table - PVID is a raw VID in a
+	 * dedicated per-port register, and there is no separate membership
+	 * table entry to allocate/track.
+	 */
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		return rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid);
+
 	/* Read the old PVID exclusively to undo in case of error */
 	ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
 					 &prev_vlanmc);
@@ -856,6 +983,74 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
 	return ret;
 }
 
+/*
+ * rtl8365mb_vlan_pvid_port_clear_direct() - Remove a port's raw-VID PVID
+ * configuration, for chip families without a working VLAN MC table
+ * (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous frame filter first so it can be restored if
+ * clearing the PVID register fails.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_clear_direct(struct dsa_switch *ds,
+						 int port, u16 vid)
+{
+	enum rtl8365mb_frame_ingress prev_accepted_frame;
+	struct realtek_priv *priv = ds->priv;
+	bool filtering;
+	u32 cur_pvid;
+	int ret;
+
+	ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+			  &cur_pvid);
+	if (ret) {
+		dev_err(priv->dev, "Failed to read current PVID\n");
+		return ret;
+	}
+
+	/* Port is not using this VID as PVID. Nothing to remove. */
+	if ((cur_pvid & RTL8365MB_D_VLAN_PVID_CTRL_MASK) != vid)
+		return 0;
+
+	filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));
+
+	/* Changing accept frame is what really removes PVID. But only do
+	 * that if VLAN filtering is enabled.
+	 */
+	if (filtering) {
+		ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+							  &prev_accepted_frame);
+		if (ret) {
+			dev_err(priv->dev, "Failed to get current framefilter\n");
+			return ret;
+		}
+
+		ret = rtl8365mb_vlan_port_set_framefilter(
+			priv, port, RTL8365MB_FRAME_TYPE_TAGGED_ONLY);
+		if (ret) {
+			dev_err(priv->dev, "Failed to set port frame filter\n");
+			return ret;
+		}
+	}
+
+	ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+				 RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set port PVID to 0\n");
+		goto undo_set_framefilter;
+	}
+
+	return 0;
+
+undo_set_framefilter:
+	if (filtering)
+		(void)rtl8365mb_vlan_port_set_framefilter(priv, port,
+							  prev_accepted_frame);
+	return ret;
+}
+
 /*
  * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration
  * @ds: dsa switch instance
@@ -879,6 +1074,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
 
 	lockdep_assert_held(&priv->vlan_lock);
 
+	if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+		return rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid);
+
 	ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
 					 &vlanmc);
 	if (ret) {

3/2 2026/10/09 06:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25841
							output: 36
							thoughts: 217
						

Thoughts:
**Analyzing a Potential Syzkaller Fuzzing Target**

Okay, let's take a look at this patch and decide if it's worth our time to throw syzkaller at it. First, I need to get a handle on what's actually being modified. Looks like it touches:

*   `drivers/net/dsa/realtek/rtl8365mb.h`
*   `drivers/net/dsa/realtek/rtl8365mb_main.c`
*   `drivers/net/dsa/realtek/rtl8365mb_vlan.c`

Right, so this is a Realtek switch driver, specifically within the `drivers/net/dsa/realtek/` directory. Aha, DSA switches! Now the key question is whether the `rtl8365mb` driver is accessible in a typical virtualized environment – the kind we'd use with QEMU and syzkaller.

I need to figure out how this driver probes. What bus is it using? Is it MDIO, SMI, SPI, I2C, or something platform-specific? Knowing this is absolutely crucial. Gotta either grep around for the probe function or dive into the source code directly. Time to do some digging.



4/2 2026/10/09 06:04 tool grepper
0m Args:
{
  "Expression": "rtl8365mb",
  "PathPrefixes": [
    "drivers/net/dsa/realtek"
  ]
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 1890 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (10 files in total):\ndrivers/net/dsa/realtek/Makefile:6\ndrivers/net/dsa/realtek/realtek.h:1\ndrivers/net/dsa/realtek/rtl8365mb.h:2\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:41\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:7\ndrivers/net/dsa/realtek/rtl8365mb_main.c:413\ndrivers/net/dsa/realtek/rtl8365mb_table.c:12\ndrivers/net/dsa/realtek/rtl8365mb_table.h:15\ndrivers/net/dsa/realtek/rtl8365mb_vlan.c:101\ndrivers/net/dsa/realtek/rtl8365mb_vlan.h:11\n\ndrivers/net/dsa/realtek/Makefile=17=endif\ndrivers/net/dsa/realtek/Makefile:18:obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o\ndrivers/net/dsa/realtek/Makefile:19:rtl8365mb-objs := rtl8365mb_main.o \\\ndrivers/net/dsa/realtek/Makefile:20:\t\t  rtl8365mb_table.o \\\ndrivers/net/dsa/realtek/Makefile:21:\t\t  rtl8365mb_vlan.o \\\ndrivers/net/dsa/realtek/Makefile:22:\t\t  rtl8365mb_l2.o \\\ndrivers/net/dsa/realtek/Makefile:23:# end of rtl8365mb-objs\n--\ndrivers/net/dsa/realtek/realtek.h=197=extern const struct realtek_variant rtl8366rb_variant;\ndrivers/net/dsa/realtek/realtek.h:198:extern const struct realtek_variant rtl8365mb_variant;\ndrivers/net/dsa/realtek/realtek.h-199-\n--\ndrivers/net/dsa/realtek/rtl8365mb.h-6-\ndrivers/net/dsa/realtek/rtl8365mb.h:7:enum rtl8365mb_family {\ndrivers/net/dsa/realtek/rtl8365mb.h-8-\tRTL8365MB_FAMILY_C,\n--\ndrivers/net/dsa/realtek/rtl8365mb.h-11-\ndrivers/net/dsa/realtek/rtl8365mb.h:12:enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv);\ndrivers/net/dsa/realtek/rtl8365mb.h-13-\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-1-// SPDX-License-Identifier: GPL-2.0\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:2:/* Forwarding and multicast database interface for the rtl8365mb switch family\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-3- *\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-8-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:9:#include \"rtl8365mb_l2.h\"\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:10:#include \"rtl8365mb_table.h\"\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-11-#include \u003clinux/regmap.h\u003e\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-91-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:92:struct rtl8365mb_l2_uc_key {\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-93-\tu8 mac_addr[ETH_ALEN];\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-99-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:100:struct rtl8365mb_l2_uc {\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:101:\tstruct rtl8365mb_l2_uc_key key;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-102-\tu8 port;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-113-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:114:struct rtl8365mb_l2_mc_key {\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-115-\tu8 mac_addr[ETH_ALEN];\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-122-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:123:struct rtl8365mb_l2_mc {\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:124:\tstruct rtl8365mb_l2_mc_key key;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-125-\tu16 member;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-133-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:134:static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-135-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-163-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:164:static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-165-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-202-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:203:static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-204-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-230-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:231:static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data)\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-232-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-261-/*\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:262: * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-263- * @priv: realtek_priv pointer\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-281- **/\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:282:int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-283-\t\t\t     struct realtek_fdb_entry *entry)\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-285-\tu16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:286:\tstruct rtl8365mb_l2_uc uc;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-287-\tint ret;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-288-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:289:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-290-\t\t\t\t    RTL8365MB_TABLE_OP_READ, addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-295-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:296:\trtl8365mb_l2_data_to_uc(data, \u0026uc);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-297-\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-304-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:305:int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-306-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-309-\tu16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:310:\tstruct rtl8365mb_l2_uc uc = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-311-\tu16 addr;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-328-\tuc.age = 1;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:329:\trtl8365mb_l2_uc_to_data(\u0026uc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-330-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-331-\t/* add the new entry or update an existing one */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:332:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-333-\t\t\t\t    RTL8365MB_TABLE_OP_WRITE, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-344-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:345:int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-346-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-349-\tu16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:350:\tstruct rtl8365mb_l2_uc uc = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-351-\tu16 addr;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-360-\tuc.age = 0;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:361:\trtl8365mb_l2_uc_to_data(\u0026uc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-362-\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-367-\t */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:368:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-369-\t\t\t\t    RTL8365MB_TABLE_OP_WRITE, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-383-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:384:int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid)\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-385-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-448-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:449:int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-450-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-453-\tu16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:454:\tstruct rtl8365mb_l2_mc mc = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-455-\tu16 addr;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-465-\tmc.is_static = 1;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:466:\trtl8365mb_l2_mc_to_data(\u0026mc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-467-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-468-\t/* First look for an existing entry (to get existing port members) */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:469:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-470-\t\t\t\t    RTL8365MB_TABLE_OP_READ, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-474-\t\t/* There is already an entry... */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:475:\t\trtl8365mb_l2_data_to_mc(data, \u0026mc);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-476-\t\tdev_dbg(priv-\u003edev,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-489-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:490:\t\trtl8365mb_l2_mc_to_data(\u0026mc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-491-\t} else if (ret == -ENOENT) {\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-507-\t/* add the new entry or update an existing one */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:508:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-509-\t\t\t\t    RTL8365MB_TABLE_OP_WRITE, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-519-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:520:int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-521-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-524-\tu16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:525:\tstruct rtl8365mb_l2_mc mc = { 0 };\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-526-\tu16 addr;\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-531-\tmc.key.ivl = true;\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:532:\trtl8365mb_l2_mc_to_data(\u0026mc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-533-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-534-\t/* First look for an existing entry (to get existing port members) */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:535:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-536-\t\t\t\t    RTL8365MB_TABLE_OP_READ, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-549-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:550:\trtl8365mb_l2_data_to_mc(data, \u0026mc);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-551-\tdev_dbg(priv-\u003edev,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-567-\t}\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:568:\trtl8365mb_l2_mc_to_data(\u0026mc, data);\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-569-\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-570-\t/* update the existing entry. */\ndrivers/net/dsa/realtek/rtl8365mb_l2.c:571:\tret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,\ndrivers/net/dsa/realtek/rtl8365mb_l2.c-572-\t\t\t\t    RTL8365MB_TABLE_OP_WRITE, \u0026addr,\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-1-/* SPDX-License-Identifier: GPL-2.0 */\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:2:/* Forwarding and multicast database interface for the rtl8365mb switch family\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-3- *\n--\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-14-\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:15:int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-16-\t\t\t     struct realtek_fdb_entry *entry);\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:17:int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-18-\t\t\tconst unsigned char addr[static ETH_ALEN],\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-19-\t\t\tu16 efid, u16 vid);\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:20:int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-21-\t\t\tconst unsigned char addr[static ETH_ALEN],\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-22-\t\t\tu16 efid, u16 vid);\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:23:int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid);\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-24-\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:25:int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-26-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-27-\t\t\tu16 vid);\ndrivers/net/dsa/realtek/rtl8365mb_l2.h:28:int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,\ndrivers/net/dsa/realtek/rtl8365mb_l2.h-29-\t\t\tconst unsigned char mac_addr[static ETH_ALEN],\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-64- * require the rtl8367d vendor driver. With all this uncertainty, the driver has\ndrivers/net/dsa/realtek/rtl8365mb_main.c:65: * been modestly named rtl8365mb. Future implementors may wish to rename things\ndrivers/net/dsa/realtek/rtl8365mb_main.c-66- * accordingly.\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-112-#include \"rtl83xx.h\"\ndrivers/net/dsa/realtek/rtl8365mb_main.c:113:#include \"rtl8365mb_l2.h\"\ndrivers/net/dsa/realtek/rtl8365mb_main.c:114:#include \"rtl8365mb_vlan.h\"\ndrivers/net/dsa/realtek/rtl8365mb_main.c:115:#include \"rtl8365mb.h\"\ndrivers/net/dsa/realtek/rtl8365mb_main.c-116-\n\n"
}

5/2 2026/10/09 06:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32983
							output: 38
							thoughts: 30
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Diving into the rtl8365mb_variant and Realtek DSA Driver Landscape**

Alright, time to get my hands dirty. I need to understand the lifecycle of the `rtl8365mb_variant` and how these Realtek DSA (Distributed Switch Architecture) switch drivers are hooked into the system. My first thought is to trace the actual usage of that particular variant. I need to see where it's referenced, where it's initialized, and, crucially, how it interacts with the broader DSA framework. I'm especially interested in the registration and probing stages. How are the drivers for this specific hardware platform declared and made available to the kernel? What's the triggering mechanism that gets them probed? Are there specific board-level configurations or device tree entries that tie it all together? I'll probably be looking for calls to registration functions, probe functions, and any related initialization sequences. Understanding the overall control flow is key to grasping the architecture and pinpointing any potential areas for optimization or modification. I need to ensure that I know how the switch driver interacts with the hardware itself. The realtek DSA switch drivers should be well documented, but it always helps to have that contextual understanding of the overall approach to hardware implementation.



6/2 2026/10/09 06:05 tool grepper
0m Args:
{
  "Expression": "rtl8365mb_variant",
  "PathPrefixes": [
    "drivers/net/dsa/realtek"
  ]
}

Results:
{
  "Output": "drivers/net/dsa/realtek/realtek.h=197=extern const struct realtek_variant rtl8366rb_variant;\ndrivers/net/dsa/realtek/realtek.h:198:extern const struct realtek_variant rtl8365mb_variant;\ndrivers/net/dsa/realtek/realtek.h-199-\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=3909=static const char *const rtl8365mb_supplies[] = {\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3912-\ndrivers/net/dsa/realtek/rtl8365mb_main.c:3913:const struct realtek_variant rtl8365mb_variant = {\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3914-\t.ds_ops = \u0026rtl8365mb_switch_ops,\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=3925=static const struct of_device_id rtl8365mb_of_match[] = {\ndrivers/net/dsa/realtek/rtl8365mb_main.c:3926:\t{ .compatible = \"realtek,rtl8365mb\", .data = \u0026rtl8365mb_variant, },\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3927-\t{ /* sentinel */ }\n"
}

7/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33730
							output: 27
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering the contents of that directory:

**Examining the Realtek DSA Driver Implementation**

Okay, let's dive into the `drivers/net/dsa/realtek/` directory. This tells me we're looking at the Realtek implementation for Distributed Switch Architecture (DSA) within the Linux kernel. Given my understanding of DSA, I anticipate finding the core driver source files, likely named something like `rtek_dsa.c` or similar, that handle the interaction with Realtek's specific switch hardware. I'd be looking for the DSA registration and unregistration functions, the probe and remove routines to initialize and clean up the hardware, and the netdevice operations that bridge between the standard Linux networking stack and the switch's internal workings. I expect to see definitions for the Realtek-specific registers and their associated meanings. I'll also be searching for any interrupt handling routines, and possibly DMA setup if the Realtek hardware utilizes that. Further, I anticipate finding configurations for various Realtek switch chips, perhaps through device tree bindings or platform data structures, or potentially some of both. I am also aware that this is a hardware specific implementation and will follow the conventions for the hardware driver in order to ensure the correct functionality.



8/2 2026/10/09 06:05 tool codesearch-dir-index
0m Args:
{
  "Dir": "drivers/net/dsa/realtek"
}

Results:
{
  "Files": [
    "realtek-mdio.c",
    "realtek-mdio.h",
    "realtek-smi.c",
    "realtek-smi.h",
    "realtek.h",
    "rtl8365mb.h",
    "rtl8365mb_l2.c",
    "rtl8365mb_l2.h",
    "rtl8365mb_main.c",
    "rtl8365mb_table.c",
    "rtl8365mb_table.h",
    "rtl8365mb_vlan.c",
    "rtl8365mb_vlan.h",
    "rtl8366-core.c",
    "rtl8366rb-leds.c",
    "rtl8366rb.c",
    "rtl8366rb.h",
    "rtl83xx.c",
    "rtl83xx.h"
  ],
  "Subdirs": null
}

9/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34277
							output: 66
							thoughts: 63
						

Thoughts:
Here's my analysis of those thoughts, tailored for an expert audience:

**Investigating rtl8365mb Driver Dependencies**

Okay, so the prompt wants me to figure out what buses the rtl8365mb driver utilizes. My initial thought is to immediately dive into the code. My hunch is that the `realtek-mdio.c` and `realtek-smi.c` files are the likely suspects, given the common Realtek naming convention and the "MDIO" and "SMI" references, which strongly suggest communication interfaces. To confirm this and gain a more complete picture, I need to thoroughly examine both `realtek-mdio.c` and `realtek-smi.c` source files and then, just to be sure, I'll also glance at the corresponding `Kconfig` file. This should help me determine the driver's dependencies, configuration options, and how it's integrated into the kernel build process. From `Kconfig` I'll confirm that these are the only relevant drivers, and check which options are enabled.



10/2 2026/10/09 06:05 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "rtl8365mb_variant",
  "PathPrefixes": [
    "drivers/net/dsa/realtek/realtek-mdio.c",
    "drivers/net/dsa/realtek/realtek-smi.c"
  ]
}

Results:
{
  "error": "no matches"
}

11/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34648
							output: 64
							thoughts: 32
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating RTL8365MB Usage and Realtek MDIO/SMI Probe**

Alright, let's get down to brass tacks. I need to understand the connection points here. My immediate focus is on how the `rtl8365mb_of_match` is being leveraged. I need to trace its usage within the codebase. Is it simply a device tree match, or does it do more? Are there any clever tricks Realtek is using here, maybe some board-specific overrides, or something to bypass the standard probing mechanisms? I should probably start by grepping for that specific symbol.

Concurrently, I'm thinking about the realtek-mdio and realtek-smi probe functions. I know these are the likely candidates for the initial device detection and setup. I have to grasp the control flow. How does the driver decide whether to use MDIO or SMI? Is it based on the device tree, or is there some other form of board-specific configuration playing a role? Are there any quirks or workarounds in these probe functions that I need to be aware of? Are there any potential issues that could be solved by manipulating the device tree properties?

I'm thinking about the interaction between the device tree and the probe functions. I want to build a better understanding of the overall initialization sequence. So, tracing and analyzing the call graph seems to be the best approach. I have to quickly build a mental map of what happens after the driver is bound and the relationship between the MDIO/SMI probing and the `rtl8365mb_of_match`.



12/2 2026/10/09 06:05 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "rtl8365mb",
  "PathPrefixes": [
    "drivers/net/dsa/realtek/realtek-mdio.c",
    "drivers/net/dsa/realtek/realtek-smi.c"
  ]
}

Results:
{
  "error": "no matches"
}

13/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35112
							output: 49
							thoughts: 47
						

Thoughts:
Here's my thought process, summarized as you requested:

**Investigating the `rtl8365mb_of_match` Function**

Okay, hold on a second. I need to figure out where the `rtl8365mb_of_match` function is actually being *used*. A quick glance at the code isn't enough; I need to pinpoint the calls. My instinct says to start with `rtl8365mb_main.c`, since that's often where the core logic resides. Let me navigate to that file and, based on previous experience with similar driver structures, I'll start by checking around lines 3910 through 3940. That region feels like a likely spot for the function calls, given the naming convention and my understanding of the typical module interaction patterns. Let's see what we find there.



14/2 2026/10/09 06:05 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/rtl8365mb_main.c",
  "FirstLine": 3910,
  "LineCount": 40
}

Results:
{
  "Contents": "3910:\t\t\"avddh\", \"avddl\", \"dvddio\", \"dvddio1\", \"dvddl\", \"pllvddl\",\n3911:\t};\n3912:\t\n3913:\tconst struct realtek_variant rtl8365mb_variant = {\n3914:\t\t.ds_ops = \u0026rtl8365mb_switch_ops,\n3915:\t\t.ops = \u0026rtl8365mb_ops,\n3916:\t\t.phylink_mac_ops = \u0026rtl8365mb_phylink_mac_ops,\n3917:\t\t.clk_delay = 10,\n3918:\t\t.cmd_read = 0xb9,\n3919:\t\t.cmd_write = 0xb8,\n3920:\t\t.chip_data_sz = sizeof(struct rtl8365mb),\n3921:\t\t.supplies = rtl8365mb_supplies,\n3922:\t\t.num_supplies = ARRAY_SIZE(rtl8365mb_supplies),\n3923:\t};\n3924:\t\n3925:\tstatic const struct of_device_id rtl8365mb_of_match[] = {\n3926:\t\t{ .compatible = \"realtek,rtl8365mb\", .data = \u0026rtl8365mb_variant, },\n3927:\t\t{ /* sentinel */ }\n3928:\t};\n3929:\tMODULE_DEVICE_TABLE(of, rtl8365mb_of_match);\n3930:\t\n3931:\tstatic struct platform_driver rtl8365mb_smi_driver = {\n3932:\t\t.driver = {\n3933:\t\t\t.name = \"rtl8365mb-smi\",\n3934:\t\t\t.of_match_table = rtl8365mb_of_match,\n3935:\t\t},\n3936:\t\t.probe  = realtek_smi_probe,\n3937:\t\t.remove = realtek_smi_remove,\n3938:\t\t.shutdown = realtek_smi_shutdown,\n3939:\t};\n3940:\t\n3941:\tstatic struct mdio_driver rtl8365mb_mdio_driver = {\n3942:\t\t.mdiodrv.driver = {\n3943:\t\t\t.name = \"rtl8365mb-mdio\",\n3944:\t\t\t.of_match_table = rtl8365mb_of_match,\n3945:\t\t},\n3946:\t\t.probe  = realtek_mdio_probe,\n3947:\t\t.remove = realtek_mdio_remove,\n3948:\t\t.shutdown = realtek_mdio_shutdown,\n3949:\t};\n"
}

15/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36129
							output: 45
							thoughts: 489
						

Thoughts:
**Analyzing the Realtek RTL8365MB Driver**

Okay, so I'm looking at this `rtl8365mb_smi_driver`, and it's a `platform_driver`, with an OF match table that's specifically looking for "realtek,rtl8365mb". And there's also an `rtl8365mb_mdio_driver` that's an `mdio_driver`, also matching "realtek,rtl8365mb". Hmm, interesting.

The critical question here is whether this can even be instantiated or probed in a standard virtualized environment like QEMU or using syzkaller. Gotta keep the Core Triage Philosophy in mind; "REACHABILITY IS THE PRIMARY GATE". If it's not reachable in a standard environment, it's not worth fuzzing. The philosophy is clear: If it's structurally unreachable, don't fuzz it. That means checking Section 2.

Alright, Section 2 lays out the "NEGATIVE CRITERIA". We return `WorthFuzzing=false` if the code falls into categories like vendor-specific PCIe switches, or drivers that require physical ASICs or PCIe cards not emulated in standard QEMU. Also, any code only executed during driver teardown related to physical hot-unplug, or paths specific to non-target architectures.

Right. What kind of hardware is this driver for? Ah, Realtek RTL8365MB, RTL8367S, RTL8367S-VB, etc. Those are physical Ethernet switch ICs – chips used in routers and embedded boards. Okay, so that's a red flag. Can this driver even *probe* without the physical hardware in QEMU? That's the key question.

Now I need to understand how `realtek_smi_probe` or `realtek_mdio_probe` works. Gotta dive into `realtek-smi.c` and `realtek-mdio.c` to see how they're implemented. This is going to be the deciding factor.



16/2 2026/10/09 06:05 tool grepper
0m Args:
{
  "Expression": "realtek_smi_probe|realtek_mdio_probe",
  "PathPrefixes": [
    "drivers/net/dsa/realtek"
  ]
}

Results:
{
  "Output": "drivers/net/dsa/realtek/realtek-mdio.c=104=static const struct realtek_interface_info realtek_mdio_info = {\n--\ndrivers/net/dsa/realtek/realtek-mdio.c-109-/**\ndrivers/net/dsa/realtek/realtek-mdio.c:110: * realtek_mdio_probe() - Probe a platform device for an MDIO-connected switch\ndrivers/net/dsa/realtek/realtek-mdio.c-111- * @mdiodev: mdio_device to probe on.\n--\ndrivers/net/dsa/realtek/realtek-mdio.c-120- */\ndrivers/net/dsa/realtek/realtek-mdio.c:121:int realtek_mdio_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/realtek/realtek-mdio.c-122-{\n--\ndrivers/net/dsa/realtek/realtek-mdio.c-142-}\ndrivers/net/dsa/realtek/realtek-mdio.c:143:EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe, \"REALTEK_DSA\");\ndrivers/net/dsa/realtek/realtek-mdio.c-144-\n--\ndrivers/net/dsa/realtek/realtek-mdio.h=13=static inline void realtek_mdio_driver_unregister(struct mdio_driver *drv)\n--\ndrivers/net/dsa/realtek/realtek-mdio.h-17-\ndrivers/net/dsa/realtek/realtek-mdio.h:18:int realtek_mdio_probe(struct mdio_device *mdiodev);\ndrivers/net/dsa/realtek/realtek-mdio.h-19-void realtek_mdio_remove(struct mdio_device *mdiodev);\n--\ndrivers/net/dsa/realtek/realtek-mdio.h=29=static inline void realtek_mdio_driver_unregister(struct mdio_driver *drv)\n--\ndrivers/net/dsa/realtek/realtek-mdio.h-32-\ndrivers/net/dsa/realtek/realtek-mdio.h:33:static inline int realtek_mdio_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/realtek/realtek-mdio.h-34-{\n--\ndrivers/net/dsa/realtek/realtek-smi.c=314=static const struct realtek_interface_info realtek_smi_info = {\n--\ndrivers/net/dsa/realtek/realtek-smi.c-319-/**\ndrivers/net/dsa/realtek/realtek-smi.c:320: * realtek_smi_probe() - Probe a platform device for an SMI-connected switch\ndrivers/net/dsa/realtek/realtek-smi.c-321- * @pdev: platform_device to probe on.\n--\ndrivers/net/dsa/realtek/realtek-smi.c-330- */\ndrivers/net/dsa/realtek/realtek-smi.c:331:int realtek_smi_probe(struct platform_device *pdev)\ndrivers/net/dsa/realtek/realtek-smi.c-332-{\n--\ndrivers/net/dsa/realtek/realtek-smi.c-363-}\ndrivers/net/dsa/realtek/realtek-smi.c:364:EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, \"REALTEK_DSA\");\ndrivers/net/dsa/realtek/realtek-smi.c-365-\n--\ndrivers/net/dsa/realtek/realtek-smi.h=13=static inline void realtek_smi_driver_unregister(struct platform_driver *drv)\n--\ndrivers/net/dsa/realtek/realtek-smi.h-17-\ndrivers/net/dsa/realtek/realtek-smi.h:18:int realtek_smi_probe(struct platform_device *pdev);\ndrivers/net/dsa/realtek/realtek-smi.h-19-void realtek_smi_remove(struct platform_device *pdev);\n--\ndrivers/net/dsa/realtek/realtek-smi.h=29=static inline void realtek_smi_driver_unregister(struct platform_driver *drv)\n--\ndrivers/net/dsa/realtek/realtek-smi.h-32-\ndrivers/net/dsa/realtek/realtek-smi.h:33:static inline int realtek_smi_probe(struct platform_device *pdev)\ndrivers/net/dsa/realtek/realtek-smi.h-34-{\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=3931=static struct platform_driver rtl8365mb_smi_driver = {\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3935-\t},\ndrivers/net/dsa/realtek/rtl8365mb_main.c:3936:\t.probe  = realtek_smi_probe,\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3937-\t.remove = realtek_smi_remove,\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=3941=static struct mdio_driver rtl8365mb_mdio_driver = {\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3945-\t},\ndrivers/net/dsa/realtek/rtl8365mb_main.c:3946:\t.probe  = realtek_mdio_probe,\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3947-\t.remove = realtek_mdio_remove,\n--\ndrivers/net/dsa/realtek/rtl8366rb.c=1846=static struct platform_driver rtl8366rb_smi_driver = {\n--\ndrivers/net/dsa/realtek/rtl8366rb.c-1850-\t},\ndrivers/net/dsa/realtek/rtl8366rb.c:1851:\t.probe  = realtek_smi_probe,\ndrivers/net/dsa/realtek/rtl8366rb.c-1852-\t.remove = realtek_smi_remove,\n--\ndrivers/net/dsa/realtek/rtl8366rb.c=1856=static struct mdio_driver rtl8366rb_mdio_driver = {\n--\ndrivers/net/dsa/realtek/rtl8366rb.c-1860-\t},\ndrivers/net/dsa/realtek/rtl8366rb.c:1861:\t.probe  = realtek_mdio_probe,\ndrivers/net/dsa/realtek/rtl8366rb.c-1862-\t.remove = realtek_mdio_remove,\n"
}

17/2 2026/10/09 06:05 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38965
							output: 45
							thoughts: 23
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Analyzing Realtek Driver Probes**

Alright, let's dive into the core of these Realtek driver probes: `realtek_smi_probe` and `realtek_mdio_probe`. My immediate thought is, "Okay, we're likely dealing with the device discovery and initialization phase." Given the names, I suspect `realtek_smi_probe` is handling the System Management Interface (SMI), and `realtek_mdio_probe` is geared towards the Management Data Input/Output (MDIO) interface.

I'm probably looking at how these functions are registered, their argument structure (likely a `platform_device` or similar), and how they interact with the Realtek chipset's specific registers or hardware access mechanisms. The crucial part here is likely the resource allocation: are they using `ioremap` effectively? How's the error handling – are they gracefully falling back if they can't acquire the necessary resources?

Beyond just getting the devices attached, I'll be looking for how the probe functions are handling the specifics of the Realtek silicon itself. What version of the driver is it for, and what Realtek chipset(s) are they targeting? How are the different features (e.g. PHY configuration, link management, interrupt handling) initialized and activated? Are they following the standard device tree bindings, or doing something custom?

My primary concern, of course, is potential issues: What are the locking mechanisms in place to avoid race conditions during concurrent access? Are there potential buffer overflows or other security vulnerabilities hiding in the register access routines or data transfers?

I'll be paying close attention to any interaction with other drivers or hardware components on the system. Does the Realtek driver play nicely with other network interfaces or the power management subsystem?

Let's get started.



18/2 2026/10/09 06:06 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/realtek-smi.c",
  "FirstLine": 330,
  "LineCount": 40
}

Results:
{
  "Contents": " 330:\t */\n 331:\tint realtek_smi_probe(struct platform_device *pdev)\n 332:\t{\n 333:\t\tstruct device *dev = \u0026pdev-\u003edev;\n 334:\t\tstruct realtek_priv *priv;\n 335:\t\tint ret;\n 336:\t\n 337:\t\tpriv = rtl83xx_probe(dev, \u0026realtek_smi_info);\n 338:\t\tif (IS_ERR(priv))\n 339:\t\t\treturn PTR_ERR(priv);\n 340:\t\n 341:\t\t/* Fetch MDIO pins */\n 342:\t\tpriv-\u003emdc = devm_gpiod_get_optional(dev, \"mdc\", GPIOD_OUT_LOW);\n 343:\t\tif (IS_ERR(priv-\u003emdc)) {\n 344:\t\t\trtl83xx_remove(priv);\n 345:\t\t\treturn PTR_ERR(priv-\u003emdc);\n 346:\t\t}\n 347:\t\n 348:\t\tpriv-\u003emdio = devm_gpiod_get_optional(dev, \"mdio\", GPIOD_OUT_LOW);\n 349:\t\tif (IS_ERR(priv-\u003emdio)) {\n 350:\t\t\trtl83xx_remove(priv);\n 351:\t\t\treturn PTR_ERR(priv-\u003emdio);\n 352:\t\t}\n 353:\t\n 354:\t\tpriv-\u003ewrite_reg_noack = realtek_smi_write_reg_noack;\n 355:\t\n 356:\t\tret = rtl83xx_register_switch(priv);\n 357:\t\tif (ret) {\n 358:\t\t\trtl83xx_remove(priv);\n 359:\t\t\treturn ret;\n 360:\t\t}\n 361:\t\n 362:\t\treturn 0;\n 363:\t}\n 364:\tEXPORT_SYMBOL_NS_GPL(realtek_smi_probe, \"REALTEK_DSA\");\n 365:\t\n 366:\t/**\n 367:\t * realtek_smi_remove() - Remove the driver of a SMI-connected switch\n 368:\t * @pdev: platform_device to be removed.\n 369:\t *\n"
}

19/2 2026/10/09 06:06 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40049
							output: 45
							thoughts: 34
						

Thoughts:
Here's my analysis of what I'm looking at in the `rtl83xx` driver, focusing on the core initialization steps:

**Diving into the RTL83xx Initialization**

Okay, so I'm digging into the heart of the RTL83xx switch driver. My immediate goal is to understand how the device gets probed and registered within the kernel. I need to get a clear picture of how this switch is actually brought online. I'm starting with `rtl83xx_probe` and `rtl83xx_register_switch` in `rtl83xx.c`. I'm expecting `rtl83xx_probe` to be the initial entry point after the device has been detected, and from there, `rtl83xx_register_switch` will likely handle the higher-level registration with the switch subsystem. I'm keen to see the specific details of hardware initialization within the probe routine and what information it passes along for the device registration. I want to look closely at things like resource allocation, I/O mapping, and interrupt setup. I'll need to observe the flow of data structures and function calls to see the sequence of steps and how this specific switch is recognized. I'll be comparing and contrasting this against other switch drivers I've examined to identify common patterns and any rtl83xx-specific quirks.



20/2 2026/10/09 06:06 tool grepper
0m Args:
{
  "Expression": "rtl83xx_probe|rtl83xx_register_switch",
  "PathPrefixes": [
    "drivers/net/dsa/realtek"
  ]
}

Results:
{
  "Output": "drivers/net/dsa/realtek/realtek-mdio.c=121=int realtek_mdio_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/realtek/realtek-mdio.c-126-\ndrivers/net/dsa/realtek/realtek-mdio.c:127:\tpriv = rtl83xx_probe(dev, \u0026realtek_mdio_info);\ndrivers/net/dsa/realtek/realtek-mdio.c-128-\tif (IS_ERR(priv))\n--\ndrivers/net/dsa/realtek/realtek-mdio.c-134-\ndrivers/net/dsa/realtek/realtek-mdio.c:135:\tret = rtl83xx_register_switch(priv);\ndrivers/net/dsa/realtek/realtek-mdio.c-136-\tif (ret) {\n--\ndrivers/net/dsa/realtek/realtek-smi.c=331=int realtek_smi_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/realtek/realtek-smi.c-336-\ndrivers/net/dsa/realtek/realtek-smi.c:337:\tpriv = rtl83xx_probe(dev, \u0026realtek_smi_info);\ndrivers/net/dsa/realtek/realtek-smi.c-338-\tif (IS_ERR(priv))\n--\ndrivers/net/dsa/realtek/realtek-smi.c-355-\ndrivers/net/dsa/realtek/realtek-smi.c:356:\tret = rtl83xx_register_switch(priv);\ndrivers/net/dsa/realtek/realtek-smi.c-357-\tif (ret) {\n--\ndrivers/net/dsa/realtek/rtl83xx.c=135=EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_user_mdio, \"REALTEK_DSA\");\n--\ndrivers/net/dsa/realtek/rtl83xx.c-137-/**\ndrivers/net/dsa/realtek/rtl83xx.c:138: * rtl83xx_probe() - probe a Realtek switch\ndrivers/net/dsa/realtek/rtl83xx.c-139- * @dev: the device being probed\n--\ndrivers/net/dsa/realtek/rtl83xx.c=151=struct realtek_priv *\ndrivers/net/dsa/realtek/rtl83xx.c:152:rtl83xx_probe(struct device *dev,\ndrivers/net/dsa/realtek/rtl83xx.c-153-\t      const struct realtek_interface_info *interface_info)\n--\ndrivers/net/dsa/realtek/rtl83xx.c-255-}\ndrivers/net/dsa/realtek/rtl83xx.c:256:EXPORT_SYMBOL_NS_GPL(rtl83xx_probe, \"REALTEK_DSA\");\ndrivers/net/dsa/realtek/rtl83xx.c-257-\ndrivers/net/dsa/realtek/rtl83xx.c-258-/**\ndrivers/net/dsa/realtek/rtl83xx.c:259: * rtl83xx_register_switch() - detects and register a switch\ndrivers/net/dsa/realtek/rtl83xx.c-260- * @priv: realtek_priv pointer\n--\ndrivers/net/dsa/realtek/rtl83xx.c-267- */\ndrivers/net/dsa/realtek/rtl83xx.c:268:int rtl83xx_register_switch(struct realtek_priv *priv)\ndrivers/net/dsa/realtek/rtl83xx.c-269-{\n--\ndrivers/net/dsa/realtek/rtl83xx.c-292-}\ndrivers/net/dsa/realtek/rtl83xx.c:293:EXPORT_SYMBOL_NS_GPL(rtl83xx_register_switch, \"REALTEK_DSA\");\ndrivers/net/dsa/realtek/rtl83xx.c-294-\n--\ndrivers/net/dsa/realtek/rtl83xx.h=14=struct realtek_priv *\ndrivers/net/dsa/realtek/rtl83xx.h:15:rtl83xx_probe(struct device *dev,\ndrivers/net/dsa/realtek/rtl83xx.h-16-\t      const struct realtek_interface_info *interface_info);\ndrivers/net/dsa/realtek/rtl83xx.h:17:int rtl83xx_register_switch(struct realtek_priv *priv);\ndrivers/net/dsa/realtek/rtl83xx.h-18-void rtl83xx_unregister_switch(struct realtek_priv *priv);\n"
}

21/2 2026/10/09 06:06 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41676
							output: 44
							thoughts: 31
						

Thoughts:
Okay, let's dive into the `rtl83xx.c` file and pinpoint lines 150 through 295. My goal here is to understand the crucial interplay between the `probe` function and the `register_switch` function within the context of this specific RTL chip driver. I need to grasp how the hardware is initially detected, identified, and brought into the system. More specifically, I want to trace the flow from the moment the device is recognized to its formal registration within the network subsystem. This section likely handles the core initialization sequence, resource allocation, and device-specific configuration that are essential for the switch to function correctly. I'm looking for the specific mechanisms used to interact with the RTL83xx hardware, any crucial checks for hardware variants or capabilities, and, ultimately, how the system knows the switch is available. Understanding this section will give me a strong foundation for dissecting other parts of the driver and ultimately optimizing its performance.



22/2 2026/10/09 06:06 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/rtl83xx.c",
  "FirstLine": 150,
  "LineCount": 80
}

Results:
{
  "Contents": " 150:\t */\n 151:\tstruct realtek_priv *\n 152:\trtl83xx_probe(struct device *dev,\n 153:\t\t      const struct realtek_interface_info *interface_info)\n 154:\t{\n 155:\t\tconst struct realtek_variant *var;\n 156:\t\tstruct realtek_priv *priv;\n 157:\t\tstruct regmap_config rc = {\n 158:\t\t\t.reg_bits = 10, /* A4..A0 R4..R0 */\n 159:\t\t\t.val_bits = 16,\n 160:\t\t\t.reg_stride = 1,\n 161:\t\t\t.max_register = 0xffff,\n 162:\t\t\t.reg_format_endian = REGMAP_ENDIAN_BIG,\n 163:\t\t\t.reg_read = interface_info-\u003ereg_read,\n 164:\t\t\t.reg_write = interface_info-\u003ereg_write,\n 165:\t\t\t.cache_type = REGCACHE_NONE,\n 166:\t\t\t.lock = rtl83xx_lock,\n 167:\t\t\t.unlock = rtl83xx_unlock,\n 168:\t\t};\n 169:\t\tint ret;\n 170:\t\n 171:\t\tvar = of_device_get_match_data(dev);\n 172:\t\tif (!var)\n 173:\t\t\treturn ERR_PTR(-EINVAL);\n 174:\t\n 175:\t\tpriv = devm_kzalloc(dev, size_add(sizeof(*priv), var-\u003echip_data_sz),\n 176:\t\t\t\t    GFP_KERNEL);\n 177:\t\tif (!priv)\n 178:\t\t\treturn ERR_PTR(-ENOMEM);\n 179:\t\n 180:\t\tret = devm_mutex_init(dev, \u0026priv-\u003emap_lock);\n 181:\t\tif (ret)\n 182:\t\t\treturn ERR_PTR(ret);\n 183:\t\n 184:\t\tret = devm_mutex_init(dev, \u0026priv-\u003evlan_lock);\n 185:\t\tif (ret)\n 186:\t\t\treturn ERR_PTR(ret);\n 187:\t\n 188:\t\tret = devm_mutex_init(dev, \u0026priv-\u003el2_lock);\n 189:\t\tif (ret)\n 190:\t\t\treturn ERR_PTR(ret);\n 191:\t\n 192:\t\trc.lock_arg = priv;\n 193:\t\tpriv-\u003emap = devm_regmap_init(dev, NULL, priv, \u0026rc);\n 194:\t\tif (IS_ERR(priv-\u003emap)) {\n 195:\t\t\tret = PTR_ERR(priv-\u003emap);\n 196:\t\t\tdev_err(dev, \"regmap init failed: %d\\n\", ret);\n 197:\t\t\treturn ERR_PTR(ret);\n 198:\t\t}\n 199:\t\n 200:\t\trc.disable_locking = true;\n 201:\t\tpriv-\u003emap_nolock = devm_regmap_init(dev, NULL, priv, \u0026rc);\n 202:\t\tif (IS_ERR(priv-\u003emap_nolock)) {\n 203:\t\t\tret = PTR_ERR(priv-\u003emap_nolock);\n 204:\t\t\tdev_err(dev, \"regmap init failed: %d\\n\", ret);\n 205:\t\t\treturn ERR_PTR(ret);\n 206:\t\t}\n 207:\t\n 208:\t\t/* Link forward and backward */\n 209:\t\tpriv-\u003edev = dev;\n 210:\t\tpriv-\u003evariant = var;\n 211:\t\tpriv-\u003eops = var-\u003eops;\n 212:\t\tpriv-\u003echip_data = (void *)priv + sizeof(*priv);\n 213:\t\n 214:\t\tspin_lock_init(\u0026priv-\u003elock);\n 215:\t\n 216:\t\tpriv-\u003eleds_disabled = of_property_read_bool(dev-\u003eof_node,\n 217:\t\t\t\t\t\t\t    \"realtek,disable-leds\");\n 218:\t\n 219:\t\t/* Enable the supplies before the reset line is requested and driven,\n 220:\t\t * so the chip is powered before its pins are driven.\n 221:\t\t */\n 222:\t\tif (var-\u003enum_supplies) {\n 223:\t\t\tret = devm_regulator_bulk_get_enable(dev, var-\u003enum_supplies,\n 224:\t\t\t\t\t\t\t     var-\u003esupplies);\n 225:\t\t\tif (ret)\n 226:\t\t\t\treturn dev_err_ptr_probe(dev, ret, \"failed to enable supplies\\n\");\n 227:\t\t}\n 228:\t\n 229:\t\tpriv-\u003ereset_ctl = devm_reset_control_get_optional(dev, NULL);\n"
}

23/2 2026/10/09 06:06 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43221
							output: 44
							thoughts: 14
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Diving into rtl83xx.c**

Alright, the next step is clear – let's dig deeper into the `rtl83xx.c` file. I'm going to need to get a handle on the specifics of this situation, so I'm going to start by analyzing the code in that file. I'll be looking for the crucial functions or structures that are in play. I'm especially interested in any potential areas where the reported issue might be stemming from. Let's see what gems we can find in there.



24/2 2026/10/09 06:06 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/rtl83xx.c",
  "FirstLine": 230,
  "LineCount": 65
}

Results:
{
  "Contents": " 230:\t\tif (IS_ERR(priv-\u003ereset_ctl))\n 231:\t\t\treturn dev_err_cast_probe(dev, priv-\u003ereset_ctl,\n 232:\t\t\t\t\t\t  \"failed to get reset control\\n\");\n 233:\t\n 234:\t\tpriv-\u003ereset = devm_gpiod_get_optional(dev, \"reset\", GPIOD_OUT_LOW);\n 235:\t\tif (IS_ERR(priv-\u003ereset))\n 236:\t\t\treturn dev_err_cast_probe(dev, priv-\u003ereset, \"failed to get RESET GPIO\\n\");\n 237:\t\n 238:\t\tdev_set_drvdata(dev, priv);\n 239:\t\n 240:\t\tif (priv-\u003ereset_ctl || priv-\u003ereset) {\n 241:\t\t\trtl83xx_reset_assert(priv);\n 242:\t\t\tdev_dbg(dev, \"asserted RESET\\n\");\n 243:\t\t\tmsleep(REALTEK_HW_STOP_DELAY);\n 244:\t\t\trtl83xx_reset_deassert(priv);\n 245:\t\t\tmsleep(REALTEK_HW_START_DELAY);\n 246:\t\t\tdev_dbg(dev, \"deasserted RESET\\n\");\n 247:\t\t} else if (var-\u003enum_supplies) {\n 248:\t\t\t/* Powered but no reset line: still wait for the chip to boot\n 249:\t\t\t * before the first register access.\n 250:\t\t\t */\n 251:\t\t\tmsleep(REALTEK_HW_START_DELAY);\n 252:\t\t}\n 253:\t\n 254:\t\treturn priv;\n 255:\t}\n 256:\tEXPORT_SYMBOL_NS_GPL(rtl83xx_probe, \"REALTEK_DSA\");\n 257:\t\n 258:\t/**\n 259:\t * rtl83xx_register_switch() - detects and register a switch\n 260:\t * @priv: realtek_priv pointer\n 261:\t *\n 262:\t * This function first checks the switch chip ID and register a DSA\n 263:\t * switch.\n 264:\t *\n 265:\t * Context: Can sleep. Takes and releases priv-\u003emap_lock.\n 266:\t * Return: 0 on success, negative value for failure.\n 267:\t */\n 268:\tint rtl83xx_register_switch(struct realtek_priv *priv)\n 269:\t{\n 270:\t\tstruct dsa_switch *ds = \u0026priv-\u003eds;\n 271:\t\tint ret;\n 272:\t\n 273:\t\tret = priv-\u003eops-\u003edetect(priv);\n 274:\t\tif (ret) {\n 275:\t\t\tdev_err_probe(priv-\u003edev, ret, \"unable to detect switch\\n\");\n 276:\t\t\treturn ret;\n 277:\t\t}\n 278:\t\n 279:\t\tds-\u003epriv = priv;\n 280:\t\tds-\u003edev = priv-\u003edev;\n 281:\t\tds-\u003eops = priv-\u003evariant-\u003eds_ops;\n 282:\t\tds-\u003ephylink_mac_ops = priv-\u003evariant-\u003ephylink_mac_ops;\n 283:\t\tds-\u003enum_ports = priv-\u003enum_ports;\n 284:\t\n 285:\t\tret = dsa_register_switch(ds);\n 286:\t\tif (ret) {\n 287:\t\t\tdev_err_probe(priv-\u003edev, ret, \"unable to register switch\\n\");\n 288:\t\t\treturn ret;\n 289:\t\t}\n 290:\t\n 291:\t\treturn 0;\n 292:\t}\n 293:\tEXPORT_SYMBOL_NS_GPL(rtl83xx_register_switch, \"REALTEK_DSA\");\n 294:\t\n"
}

25/2 2026/10/09 06:06 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 44439
							output: 49
							thoughts: 322
						

Thoughts:
**Analyzing the RTL8365MB Driver's Syzkaller Vulnerability**

Okay, let's break this down. First, I see that we're dealing with two distinct drivers here: `rtl8365mb_smi_driver`, a platform driver, and `rtl8365mb_mdio_driver`, which is an MDIO driver. My initial instinct is to look for potential interaction points, but based on the code, things are likely not going to be easy.

Specifically, the `rtl83xx_probe()` function is where things get interesting. It's calling `of_device_get_match_data(dev)`. That immediately screams Device Tree dependency. And here's the catch: we're talking about an amd64 target. On x86_64, Device Tree isn't the standard. So, how would a device with `compatible = "realtek,rtl8365mb"` even *be* created in the first place, especially if `of_device_get_match_data()` is a requirement? This is starting to look unlikely.

Aha, that's it! Wait, can syzkaller even instantiate platform devices or mdio devices? I need to think about syzkaller's capabilities here. Does it have some clever way to create platform or MDIO devices, or even communicate with this DSA driver? Syzkaller *doesn't* create Device Tree nodes, and even if there *was* some obscure interface to create platform devices, this driver explicitly checks for DT via `of_device_get_match_data(dev)`. If that check fails (`!var`), the driver is going to return `ERR_PTR(-EINVAL)`. So, that's a dead end.

Unless... what about `detect`? I need to check out the `rtl83xx_register_switch` function. It's calling `priv->ops->detect(priv)`. Let me see what `rtl8365mb_detect()` is doing. That's where the interesting stuff is likely hiding.



26/2 2026/10/09 06:06 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/rtl8365mb_main.c",
  "FirstLine": 3330,
  "LineCount": 55
}

Results:
{
  "Contents": "3330:\t\n3331:\t\tret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT,\n3332:\t\t\t\t\t   \"rtl8365mb\", priv);\n3333:\t\tif (ret) {\n3334:\t\t\tdev_err(priv-\u003edev, \"failed to request irq: %pe\\n\",\n3335:\t\t\t\tERR_PTR(ret));\n3336:\t\t\tgoto out_remove_irqdomain;\n3337:\t\t}\n3338:\t\n3339:\t\tret = rtl8365mb_irq_enable(priv);\n3340:\t\tif (ret)\n3341:\t\t\tgoto out_free_irq;\n3342:\t\n3343:\t\tof_node_put(intc);\n3344:\t\n3345:\t\treturn 0;\n3346:\t\n3347:\tout_free_irq:\n3348:\t\tfree_irq(mb-\u003eirq, priv);\n3349:\t\n3350:\tout_remove_irqdomain:\n3351:\t\tdsa_switch_for_each_port(dp, ds) {\n3352:\t\t\tvirq = irq_find_mapping(priv-\u003eirqdomain, dp-\u003eindex);\n3353:\t\n3354:\t\t\tif (virq)\n3355:\t\t\t\tirq_dispose_mapping(virq);\n3356:\t\t}\n3357:\t\n3358:\t\tirq_domain_remove(priv-\u003eirqdomain);\n3359:\t\tpriv-\u003eirqdomain = NULL;\n3360:\t\n3361:\tout_put_node:\n3362:\t\tmb-\u003eirq = 0;\n3363:\t\tof_node_put(intc);\n3364:\t\n3365:\t\treturn ret;\n3366:\t}\n3367:\t\n3368:\tstatic void rtl8365mb_irq_teardown(struct realtek_priv *priv)\n3369:\t{\n3370:\t\tstruct rtl8365mb *mb = priv-\u003echip_data;\n3371:\t\tstruct dsa_switch *ds = \u0026priv-\u003eds;\n3372:\t\tstruct dsa_port *dp;\n3373:\t\tint virq;\n3374:\t\n3375:\t\tif (mb-\u003eirq) {\n3376:\t\t\tfree_irq(mb-\u003eirq, priv);\n3377:\t\t\tmb-\u003eirq = 0;\n3378:\t\t}\n3379:\t\n3380:\t\tif (priv-\u003eirqdomain) {\n3381:\t\t\t/* Unused ports with a linked PHY still have an active IRQ\n3382:\t\t\t * mapping that must be disposed of during teardown. Loop\n3383:\t\t\t * through all ports.\n3384:\t\t\t */\n"
}

27/2 2026/10/09 06:06 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 46045
							output: 49
							thoughts: 52
						

Thoughts:
Here's my take on the thought process:

**Diving into the rtl8365mb_detect Function**

Okay, time to get specific. I need to refresh my memory on the `rtl8365mb_detect` function. Let's head straight to the source, and focus on the `drivers/net/dsa/realtek/rtl8365mb_main.c` file. I'm going to jump to lines 3750 through 3800. That should give me a good chunk of code to work with, right where `rtl8365mb_detect` is defined. I'll quickly see the surrounding context, and remind myself how the Realtek switch detection works in that specific implementation. Let's see what we've got!



28/2 2026/10/09 06:07 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/rtl8365mb_main.c",
  "FirstLine": 3730,
  "LineCount": 70
}

Results:
{
  "Contents": "3730:\t\t\treturn ret;\n3731:\t\n3732:\t\tret = regmap_read(map, RTL8365MB_CHIP_ID_REG, id);\n3733:\t\tif (ret)\n3734:\t\t\treturn ret;\n3735:\t\n3736:\t\tret = regmap_read(map, RTL8365MB_CHIP_VER_REG, ver);\n3737:\t\tif (ret)\n3738:\t\t\treturn ret;\n3739:\t\n3740:\t\t/* Reset magic register */\n3741:\t\tret = regmap_write(map, RTL8365MB_MAGIC_REG, 0);\n3742:\t\tif (ret)\n3743:\t\t\treturn ret;\n3744:\t\n3745:\t\treturn 0;\n3746:\t}\n3747:\t\n3748:\tstatic int rtl8365mb_detect(struct realtek_priv *priv)\n3749:\t{\n3750:\t\tstruct rtl8365mb *mb = priv-\u003echip_data;\n3751:\t\tu32 chip_id;\n3752:\t\tu32 chip_ver;\n3753:\t\tint ret;\n3754:\t\tint i;\n3755:\t\n3756:\t\tret = rtl8365mb_get_chip_id_and_ver(priv-\u003emap, \u0026chip_id, \u0026chip_ver);\n3757:\t\tif (ret) {\n3758:\t\t\tdev_err(priv-\u003edev, \"failed to read chip id and version: %pe\\n\",\n3759:\t\t\t\tERR_PTR(ret));\n3760:\t\t\treturn ret;\n3761:\t\t}\n3762:\t\n3763:\t\tfor (i = 0; i \u003c ARRAY_SIZE(rtl8365mb_chip_infos); i++) {\n3764:\t\t\tconst struct rtl8365mb_chip_info *ci = \u0026rtl8365mb_chip_infos[i];\n3765:\t\n3766:\t\t\tif (ci-\u003echip_id == chip_id \u0026\u0026 ci-\u003echip_ver == chip_ver) {\n3767:\t\t\t\tmb-\u003echip_info = ci;\n3768:\t\t\t\tbreak;\n3769:\t\t\t}\n3770:\t\t}\n3771:\t\n3772:\t\tif (!mb-\u003echip_info) {\n3773:\t\t\tdev_err(priv-\u003edev,\n3774:\t\t\t\t\"unrecognized switch (id=0x%04x, ver=0x%04x)\", chip_id,\n3775:\t\t\t\tchip_ver);\n3776:\t\t\treturn -ENODEV;\n3777:\t\t}\n3778:\t\n3779:\t\tdev_info(priv-\u003edev, \"found an %s switch\\n\", mb-\u003echip_info-\u003ename);\n3780:\t\n3781:\t\tif (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)\n3782:\t\t\tpriv-\u003enum_ports = RTL8365MB_D_MAX_NUM_PORTS;\n3783:\t\telse\n3784:\t\t\tpriv-\u003enum_ports = RTL8365MB_MAX_NUM_PORTS;\n3785:\t\n3786:\t\tret = devm_mutex_init(priv-\u003edev, \u0026mb-\u003esds_lock);\n3787:\t\tif (ret)\n3788:\t\t\treturn ret;\n3789:\t\n3790:\t\tmb-\u003epriv = priv;\n3791:\t\tmb-\u003ecpu.trap_port = RTL8365MB_MAX_NUM_PORTS;\n3792:\t\tmb-\u003ecpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;\n3793:\t\tmb-\u003ecpu.position = RTL8365MB_CPU_POS_AFTER_SA;\n3794:\t\tmb-\u003ecpu.rx_length = RTL8365MB_CPU_RXLEN_64BYTES;\n3795:\t\tmb-\u003ecpu.format = RTL8365MB_CPU_FORMAT_8BYTES;\n3796:\t\n3797:\t\treturn 0;\n3798:\t}\n3799:\t\n"
}

29/2 2026/10/09 06:07 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 47442
							output: 46
							thoughts: 774
						

Thoughts:
**Analyzing the Feasibility of Fuzzing RTL8365MB Driver Code**

Okay, so I'm digging into this `rtl8365mb` driver, specifically `rtl8365mb_get_chip_id_and_ver` and `rtl8365mb_detect`, and it's immediately clear this code is talking directly to hardware. It's reading registers on a physical switch chip via MDIO or SMI bus. The first thing I'm doing is checking for any kind of emulation, like in QEMU or some kind of mock kernel driver. Nope, nothing. QEMU doesn't have any emulation for Realtek RTL8365MB or anything similar. The hardware registers through an MDIO bus, or even GPIOs in the case of SMI, and then registers via Device Tree using `realtek,rtl8365mb`.

Given that I'm on amd64, there's no physical RTL8365MB switch in this environment, and certainly no QEMU emulation.

Now, let's carefully consider the implications from Sections 1 and 2 of this documentation. The core point is that reachability is everything. If the code is not accessible in a standard virtualized environment (QEMU/GCE), it's not worth fuzzing, no matter what it does. Section 2 confirms this: if code is structurally unreachable, we return `WorthFuzzing=false`. The specific examples they give are vendor-specific PCIe switches and drivers that require physical cards not emulated in QEMU, or things like driver teardown callbacks that only run during hot-unplug.

The question then arises, could we fuzz DSA switches at all using syzkaller? I'll look into syzkaller's source or known drivers to see if it supports DSA or MDIO directly. Syzkaller's pseudo-syscalls are not actually in the kernel, so I'm thinking about the standard environments it works in: QEMU and GCE x86_64 VMs. Can syzkaller possibly fuzz a DSA switch driver that needs a specific Realtek switch IC on an SMI/MDIO bus and using that particular OF compatible string? Nope! It relies on physical hardware, specifically an embedded Realtek Ethernet switch chip like RTL8365MB or RTL8367S, which are typically found on OpenWrt MIPS/ARM router boards. This means it cannot be probed in a standard QEMU / GCE amd64 environment. Since this device won't probe, none of the driver's functions, like `rtl8365mb_pcs_config`, `rtl8365mb_sds_relatch_work`, `rtl8365mb_vlan_pvid_port_set`, or `rtl8365mb_ext_config_rgmii` are even reachable. Finally, let's double-check the Kconfig, looking for `CONFIG_NET_DSA_REALTEK_RTL8365MB`.



30/2 2026/10/09 06:07 tool grepper
0m Args:
{
  "Expression": "NET_DSA_REALTEK_RTL8365MB",
  "PathPrefixes": [
    "drivers/net/dsa/realtek/"
  ]
}

Results:
{
  "Output": "drivers/net/dsa/realtek/Kconfig=25=config NET_DSA_REALTEK_SMI\n--\ndrivers/net/dsa/realtek/Kconfig-31-\ndrivers/net/dsa/realtek/Kconfig:32:config NET_DSA_REALTEK_RTL8365MB\ndrivers/net/dsa/realtek/Kconfig-33-\ttristate \"Realtek RTL8365MB switch driver\"\n--\ndrivers/net/dsa/realtek/Makefile=17=endif\ndrivers/net/dsa/realtek/Makefile:18:obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o\ndrivers/net/dsa/realtek/Makefile-19-rtl8365mb-objs := rtl8365mb_main.o \\\n"
}

31/2 2026/10/09 06:07 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 49143
							output: 38
							thoughts: 18
						

Thoughts:
Here's a summary of my thought process, as if I were considering that Kconfig file:

**Analyzing the Realtek DSA Kconfig File**

Okay, let's dive into the `drivers/net/dsa/realtek/Kconfig` file. Given my expertise in this area, my initial focus is on understanding how Realtek's DSA (Distributed Switch Architecture) support is configured within the kernel. I'm expecting to see a lot of conditional logic here, determining which Realtek switch models are supported, what features are enabled or disabled based on those models, and how dependencies on other kernel subsystems (e.g., PHY drivers, MDIO support, and potentially other DSA components) are handled.

I want to quickly grasp the overarching structure: What are the main configuration options defined here? Are they individual settings for each switch model, or are there more generic options that apply to a range of models? Are there any obvious default values for options and what are they? I'll be looking for things like `config RTL8365MB`, `config RTL9301`, or potentially more generic things such as `CONFIG_RTL_DSA_SUPPORT`. I'm going to be looking for the `depends on` statements and what they are depending on. What are the dependencies?

I'll be paying close attention to any help text provided for each option. The help text is often a goldmine of information about the purpose of the option, its implications, and the kinds of hardware it's intended to support. This is where I'll get the real context. Any mentions of specific Realtek chips (e.g., RTL8365MB, RTL9301) will immediately pique my interest, as that's a direct connection to the hardware. I want to see if any of these configuration options affect the choice of drivers that are linked into the kernel.

I will be on the lookout for any `select` statements, which would indicate dependencies on other kernel modules. This would tell me what else is being pulled in. I'm expecting some of these options to be enabled by default and others to be modularized. The modularized ones may depend on other Kconfig options.

Ultimately, my goal here is to quickly understand the configuration landscape for Realtek DSA within the kernel and form a mental map of how the various options relate to each other and to the underlying hardware. This will be critical for any debugging, customization, or integration work I might need to do in the future.



32/2 2026/10/09 06:07 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/realtek/Kconfig",
  "FirstLine": 1,
  "LineCount": 50
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\tmenuconfig NET_DSA_REALTEK\n   3:\t\ttristate \"Realtek Ethernet switch family support\"\n   4:\t\tdepends on NET_DSA\n   5:\t\tselect FIXED_PHY\n   6:\t\tselect IRQ_DOMAIN\n   7:\t\tselect REALTEK_PHY\n   8:\t\tselect REGMAP\n   9:\t\thelp\n  10:\t\t  Select to enable support for Realtek Ethernet switch chips.\n  11:\t\n  12:\t\t  Note that at least one interface driver must be enabled for the\n  13:\t\t  subdrivers to be loaded. Moreover, an interface driver cannot achieve\n  14:\t\t  anything without at least one subdriver enabled.\n  15:\t\n  16:\tif NET_DSA_REALTEK\n  17:\t\n  18:\tconfig NET_DSA_REALTEK_MDIO\n  19:\t\tbool \"Realtek MDIO interface support\"\n  20:\t\tdepends on OF\n  21:\t\thelp\n  22:\t\t  Select to enable support for registering switches configured\n  23:\t\t  through MDIO.\n  24:\t\n  25:\tconfig NET_DSA_REALTEK_SMI\n  26:\t\tbool \"Realtek SMI interface support\"\n  27:\t\tdepends on OF\n  28:\t\thelp\n  29:\t\t  Select to enable support for registering switches connected\n  30:\t\t  through SMI.\n  31:\t\n  32:\tconfig NET_DSA_REALTEK_RTL8365MB\n  33:\t\ttristate \"Realtek RTL8365MB switch driver\"\n  34:\t\tdepends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO\n  35:\t\tselect NET_DSA_TAG_RTL8_4\n  36:\t\thelp\n  37:\t\t  Select to enable support for Realtek RTL8365MB-VC and RTL8367S.\n  38:\t\n  39:\tconfig NET_DSA_REALTEK_RTL8366RB\n  40:\t\ttristate \"Realtek RTL8366RB switch driver\"\n  41:\t\tdepends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO\n  42:\t\tselect NET_DSA_TAG_RTL4_A\n  43:\t\thelp\n  44:\t\t  Select to enable support for Realtek RTL8366RB.\n  45:\t\n  46:\tconfig NET_DSA_REALTEK_RTL8366RB_LEDS\n  47:\t\tbool\n  48:\t\tdepends on (LEDS_CLASS=y || LEDS_CLASS=NET_DSA_REALTEK_RTL8366RB)\n  49:\t\tdepends on NET_DSA_REALTEK_RTL8366RB\n  50:\t\tdefault NET_DSA_REALTEK_RTL8366RB\n"
}

33/2 2026/10/09 06:07 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 50474
							output: 114
							thoughts: 488
						

Thoughts:
**WorthFuzzing Assessment: Realtek RTL8365MB Driver**

Okay, let's break this down. I've noticed that both `NET_DSA_REALTEK_MDIO` and `NET_DSA_REALTEK_SMI` depend on `OF`, which is Open Firmware/Device Tree. On amd64, especially in standard virtualized environments like QEMU or KVM, `OF` is often disabled, or even if enabled, there aren't typically any Device Tree nodes for this specific physical switch. This Realtek driver, `rtl8365mb`, is designed to work with *physical* Realtek Ethernet switch chips connected via SMI or MDIO.

Now, let's revisit Section 2 of the triage instructions regarding "NEGATIVE CRITERIA," specifically the point about "Structurally unreachable hardware." It clearly states that we should return `WorthFuzzing=false` if the changes are limited to vendor-specific hardware requiring physical components not emulated in standard QEMU. That's *exactly* what we're looking at here. The RTL8365MB is a vendor-specific Realtek switch ASIC, requiring physical hardware connected via the MDIO or SMI bus, along with a Device Tree binding. This hardware is not emulated in QEMU. All the modified files – `rtl8365mb.h`, `rtl8365mb_main.c`, and `rtl8365mb_vlan.c` – are located within `drivers/net/dsa/realtek/`, which is further confirmation that the changes are isolated to this particular driver.

Therefore, this definitively falls under "structurally unreachable hardware".  We can safely conclude:

*   `WorthFuzzing: false`
*   `FocusSymbols: []`
*   `EnableConfigs: []`
*   `Reasoning: Concise explanation stating that the changes are strictly within the Realtek RTL8365MB DSA Ethernet switch driver (drivers/net/dsa/realtek/rtl8365mb*), which requires a physical Realtek switch chip connected via SMI or MDIO bus (and Device Tree binding) not present or emulated in standard virtualized environments (QEMU/KVM amd64), making the modified code structurally unreachable.`

Let's do a quick double-check: ensure that I am only using the set-results tool once, that I have empty lists for EnableConfigs and FocusSymbols as `WorthFuzzing` is false, and that my reasoning is concise and clear. Yup, it all looks solid. We're good to go.



34/2 2026/10/09 06:07 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The modified code is confined to the Realtek RTL8365MB DSA Ethernet switch driver (drivers/net/dsa/realtek/), which targets vendor-specific discrete switch ASICs connected over SMI or MDIO with Device Tree bindings. This hardware is not present or emulated in standard virtualized fuzzing environments (QEMU/KVM on amd64), making the code structurally unreachable.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The modified code is confined to the Realtek RTL8365MB DSA Ethernet switch driver (drivers/net/dsa/realtek/), which targets vendor-specific discrete switch ASICs connected over SMI or MDIO with Device Tree bindings. This hardware is not present or emulated in standard virtualized fuzzing environments (QEMU/KVM on amd64), making the code structurally unreachable.",
  "WorthFuzzing": false
}

35/1 2026/10/09 06:07 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)