| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/27 06:14 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies a vendor-specific DSA switch driver (Motorcomm YT9215) connected via MDIO. The driver requires physical hardware to probe successfully (it reads a specific chip ID during initialization) and is not emulated in standard QEMU. Therefore, the code is unreachable in a virtualized environment.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/27 06:14 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit c7b24fc75ad0587c2297f10f4986844c6f42a8ff\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Jul 27 06:14:15 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 60cff00953dcbe..f512467753bc24 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -18249,7 +18249,7 @@ M:\tDavid Yang \u003cmmyangfl@gmail.com\u003e\n L:\tnetdev@vger.kernel.org\n S:\tMaintained\n F:\tDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml\n-F:\tdrivers/net/dsa/yt921x.*\n+F:\tdrivers/net/dsa/motorcomm/\n F:\tnet/dsa/tag_yt921x.c\n \n MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD\ndiff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig\nindex 4ab567c5bbafc6..98e9bbe47de7b1 100644\n--- a/drivers/net/dsa/Kconfig\n+++ b/drivers/net/dsa/Kconfig\n@@ -72,6 +72,8 @@ config NET_DSA_MV88E6060\n \n source \"drivers/net/dsa/microchip/Kconfig\"\n \n+source \"drivers/net/dsa/motorcomm/Kconfig\"\n+\n source \"drivers/net/dsa/mv88e6xxx/Kconfig\"\n \n source \"drivers/net/dsa/mxl862xx/Kconfig\"\n@@ -158,12 +160,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM\n \t This enables support for the Vitesse VSC7385, VSC7388, VSC7395\n \t and VSC7398 SparX integrated ethernet switches, connected over\n \t a CPU-attached address bus and work in memory-mapped I/O mode.\n-\n-config NET_DSA_YT921X\n-\ttristate \"Motorcomm YT9215 ethernet switch chip support\"\n-\tselect NET_DSA_TAG_YT921X\n-\tselect NET_IEEE8021Q_HELPERS if DCB\n-\thelp\n-\t This enables support for the Motorcomm YT9215 ethernet switch\n-\t chip.\n endmenu\ndiff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile\nindex d2975badffc093..138225baa4d564 100644\n--- a/drivers/net/dsa/Makefile\n+++ b/drivers/net/dsa/Makefile\n@@ -14,11 +14,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o\n obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o\n obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o\n obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o\n-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o\n obj-y\t\t\t\t+= b53/\n obj-y\t\t\t\t+= hirschmann/\n obj-y\t\t\t\t+= lantiq/\n obj-y\t\t\t\t+= microchip/\n+obj-y\t\t\t\t+= motorcomm/\n obj-y\t\t\t\t+= mv88e6xxx/\n obj-y\t\t\t\t+= mxl862xx/\n obj-y\t\t\t\t+= netc/\ndiff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig\nnew file mode 100644\nindex 00000000000000..3438e0b143611a\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/Kconfig\n@@ -0,0 +1,17 @@\n+# SPDX-License-Identifier: ISC\n+config NET_DSA_YT921X\n+\ttristate \"Motorcomm YT9215 ethernet switch chip support\"\n+\tselect NET_DSA_TAG_YT921X\n+\tselect NET_IEEE8021Q_HELPERS if DCB\n+\thelp\n+\t This enables support for the Motorcomm YT9215 ethernet switch\n+\t chip.\n+\n+config NET_DSA_YT921X_LEDS\n+\tbool \"LED support for Motorcomm YT9215\"\n+\tdefault y\n+\tdepends on NET_DSA_YT921X\n+\tdepends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X\n+\thelp\n+\t This enables support for controlling the LEDs attached to the\n+\t Motorcomm YT9215 switch chips.\ndiff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile\nnew file mode 100644\nindex 00000000000000..5a63db0029ffe2\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/Makefile\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: ISC\n+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o\n+yt921x-objs := chip.o\n+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o\n+yt921x-objs += smi.o\ndiff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/motorcomm/chip.c\nsimilarity index 96%\nrename from drivers/net/dsa/yt921x.c\nrename to drivers/net/dsa/motorcomm/chip.c\nindex 159b16606f6ca4..553efacd4f5ee3 100644\n--- a/drivers/net/dsa/yt921x.c\n+++ b/drivers/net/dsa/motorcomm/chip.c\n@@ -13,7 +13,6 @@\n #include \u003clinux/if_bridge.h\u003e\n #include \u003clinux/if_hsr.h\u003e\n #include \u003clinux/if_vlan.h\u003e\n-#include \u003clinux/iopoll.h\u003e\n #include \u003clinux/mdio.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/of.h\u003e\n@@ -26,7 +25,9 @@\n #include \u003cnet/ieee8021q.h\u003e\n #include \u003cnet/pkt_cls.h\u003e\n \n-#include \"yt921x.h\"\n+#include \"chip.h\"\n+#include \"leds.h\"\n+#include \"smi.h\"\n \n struct yt921x_mib_desc {\n \tunsigned int size;\n@@ -151,13 +152,8 @@ static const struct yt921x_info yt921x_infos[] = {\n \t{}\n };\n \n-#define YT921X_NAME\t\"yt921x\"\n-\n #define YT921X_VID_UNWARE\t4095\n \n-#define YT921X_POLL_SLEEP_US\t10000\n-#define YT921X_POLL_TIMEOUT_US\t100000\n-\n /* The interval should be small enough to avoid overflow of 32bit MIBs.\n *\n * Until we can read MIBs from stats64 call directly (i.e. sleep\n@@ -196,208 +192,6 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)\n \treturn (addr[4] \u003c\u003c 8) | addr[5];\n }\n \n-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)\n-{\n-\tWARN_ON(!mutex_is_locked(\u0026priv-\u003ereg_lock));\n-\n-\treturn priv-\u003ereg_ops-\u003eread(priv-\u003ereg_ctx, reg, valp);\n-}\n-\n-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)\n-{\n-\tWARN_ON(!mutex_is_locked(\u0026priv-\u003ereg_lock));\n-\n-\treturn priv-\u003ereg_ops-\u003ewrite(priv-\u003ereg_ctx, reg, val);\n-}\n-\n-static int\n-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)\n-{\n-\tu32 val;\n-\tint res;\n-\tint ret;\n-\n-\tret = read_poll_timeout(yt921x_reg_read, res,\n-\t\t\t\tres || (val \u0026 mask) == *valp,\n-\t\t\t\tYT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,\n-\t\t\t\tfalse, priv, reg, \u0026val);\n-\tif (ret)\n-\t\treturn ret;\n-\tif (res)\n-\t\treturn res;\n-\n-\t*valp = val;\n-\treturn 0;\n-}\n-\n-static int\n-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)\n-{\n-\tint res;\n-\tu32 v;\n-\tu32 u;\n-\n-\tres = yt921x_reg_read(priv, reg, \u0026v);\n-\tif (res)\n-\t\treturn res;\n-\n-\tu = v;\n-\tu \u0026= ~mask;\n-\tu |= val;\n-\tif (u == v)\n-\t\treturn 0;\n-\n-\treturn yt921x_reg_write(priv, reg, u);\n-}\n-\n-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)\n-{\n-\treturn yt921x_reg_update_bits(priv, reg, 0, mask);\n-}\n-\n-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)\n-{\n-\treturn yt921x_reg_update_bits(priv, reg, mask, 0);\n-}\n-\n-static int\n-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)\n-{\n-\treturn yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);\n-}\n-\n-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single\n- * long register. More specifically, writes to parts of its words won't become\n- * visible, until the last word is written.\n- *\n- * Here we require full read and write operations over these registers to\n- * eliminate potential issues, although partial reads/writes are also possible.\n- */\n-\n-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)\n-{\n-\t*lo \u0026= ~lower_32_bits(mask);\n-\t*hi \u0026= ~upper_32_bits(mask);\n-\t*lo |= lower_32_bits(val);\n-\t*hi |= upper_32_bits(val);\n-}\n-\n-static int\n-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,\n-\t\t unsigned int num_regs)\n-{\n-\tint res;\n-\n-\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n-\t\tres = yt921x_reg_read(priv, reg + 4 * i, \u0026vals[i]);\n-\t\tif (res)\n-\t\t\treturn res;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static int\n-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,\n-\t\t unsigned int num_regs)\n-{\n-\tint res;\n-\n-\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n-\t\tres = yt921x_reg_write(priv, reg + 4 * i, vals[i]);\n-\t\tif (res)\n-\t\t\treturn res;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static int\n-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n-\t\t\tconst u32 *vals, unsigned int num_regs)\n-{\n-\tbool changed = false;\n-\tu32 vs[4];\n-\tint res;\n-\n-\tBUILD_BUG_ON(num_regs \u003e ARRAY_SIZE(vs));\n-\n-\tres = yt921x_regs_read(priv, reg, vs, num_regs);\n-\tif (res)\n-\t\treturn res;\n-\n-\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n-\t\tu32 u = vs[i];\n-\n-\t\tu \u0026= ~masks[i];\n-\t\tu |= vals[i];\n-\t\tif (u != vs[i])\n-\t\t\tchanged = true;\n-\n-\t\tvs[i] = u;\n-\t}\n-\n-\tif (!changed)\n-\t\treturn 0;\n-\n-\treturn yt921x_regs_write(priv, reg, vs, num_regs);\n-}\n-\n-static int\n-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n-\t\t unsigned int num_regs)\n-{\n-\tbool changed = false;\n-\tu32 vs[4];\n-\tint res;\n-\n-\tBUILD_BUG_ON(num_regs \u003e ARRAY_SIZE(vs));\n-\n-\tres = yt921x_regs_read(priv, reg, vs, num_regs);\n-\tif (res)\n-\t\treturn res;\n-\n-\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n-\t\tu32 u = vs[i];\n-\n-\t\tu \u0026= ~masks[i];\n-\t\tif (u != vs[i])\n-\t\t\tchanged = true;\n-\n-\t\tvs[i] = u;\n-\t}\n-\n-\tif (!changed)\n-\t\treturn 0;\n-\n-\treturn yt921x_regs_write(priv, reg, vs, num_regs);\n-}\n-\n-static int\n-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)\n-{\n-\treturn yt921x_regs_write(priv, reg, vals, 2);\n-}\n-\n-static int\n-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n-\t\t\t const u32 *vals)\n-{\n-\treturn yt921x_regs_update_bits(priv, reg, masks, vals, 2);\n-}\n-\n-static int\n-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)\n-{\n-\treturn yt921x_regs_clear_bits(priv, reg, masks, 2);\n-}\n-\n-static int\n-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)\n-{\n-\treturn yt921x_regs_write(priv, reg, vals, 3);\n-}\n-\n static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)\n {\n \tstruct yt921x_reg_mdio *mdio = context;\n@@ -4786,6 +4580,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)\n \tif (res)\n \t\treturn res;\n \n+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n+\tres = yt921x_leds_setup(priv);\n+\tif (res)\n+\t\tdev_warn(dev, \"Failed to setup LEDs: %d\\n\", res);\n+#endif\n+\n \treturn 0;\n }\n \n@@ -4886,6 +4686,10 @@ static void yt921x_mdio_remove(struct mdio_device *mdiodev)\n \tif (!priv)\n \t\treturn;\n \n+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n+\tyt921x_leds_remove(priv);\n+#endif\n+\n \tfor (size_t i = ARRAY_SIZE(priv-\u003eports); i-- \u003e 0; ) {\n \t\tstruct yt921x_port *pp = \u0026priv-\u003eports[i];\n \ndiff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/motorcomm/chip.h\nsimilarity index 99%\nrename from drivers/net/dsa/yt921x.h\nrename to drivers/net/dsa/motorcomm/chip.h\nindex 5f3b99e189c412..e6815cf9cb3cd0 100644\n--- a/drivers/net/dsa/yt921x.h\n+++ b/drivers/net/dsa/motorcomm/chip.h\n@@ -850,9 +850,14 @@ enum yt921x_fdb_entry_status {\n #define YT921X_ACL_NUM\t\t(YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)\n #define YT921X_UDF_NUM\t\t8\n \n+#define YT921X_LED_GROUP_NUM\t3\n+#define YT921X_LED_PORT_NUM\t10\n+\n /* 8 internal + 2 external + 1 mcu */\n #define YT921X_PORT_NUM\t\t\t11\n \n+#define YT921X_NAME\t\"yt921x\"\n+\n #define yt921x_port_is_internal(port) ((port) \u003c 8)\n #define yt921x_port_is_external(port) ((port) == 8 || (port) == 9)\n \n@@ -938,6 +943,13 @@ struct yt921x_port {\n \tstruct yt921x_mib mib;\n \tu64 rx_frames;\n \tu64 tx_frames;\n+\n+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n+\tunsigned char led_duty;\n+\tunsigned short led_cycle;\n+\n+\tstruct yt921x_led *leds[YT921X_LED_GROUP_NUM];\n+#endif\n };\n \n struct yt921x_reg_ops {\ndiff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c\nnew file mode 100644\nindex 00000000000000..37e8d4f0003f81\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/leds.c\n@@ -0,0 +1,660 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright (c) 2026 David Yang\n+ */\n+\n+#include \u003clinux/uleds.h\u003e\n+\n+#include \"chip.h\"\n+#include \"leds.h\"\n+#include \"smi.h\"\n+\n+#define to_yt921x_led(led_cdev) \\\n+\tcontainer_of_const((led_cdev), struct yt921x_led, cdev)\n+#define to_yt921x_port(led) ((led)-\u003eport)\n+#define to_yt921x_priv(pp) \\\n+\tcontainer_of_const((pp), struct yt921x_priv, ports[(pp)-\u003eindex])\n+#define to_device(priv) ((priv)-\u003eds.dev)\n+\n+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)\n+{\n+\tswitch (group) {\n+\tcase 0:\n+\tdefault:\n+\t\treturn YT921X_LED0_PORTn(port);\n+\tcase 1:\n+\t\treturn YT921X_LED1_PORTn(port);\n+\tcase 2:\n+\t\treturn YT921X_LED2_PORTn(port);\n+\t}\n+}\n+\n+static int\n+yt921x_led_force_get(struct yt921x_priv *priv, int port, int group, bool *onp)\n+{\n+\tu32 val;\n+\tint res;\n+\n+\tres = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), \u0026val);\n+\tif (res)\n+\t\treturn res;\n+\n+\t*onp = (val \u0026 YT921X_LED2_PORT_FORCEn_M(group)) ==\n+\t YT921X_LED2_PORT_FORCEn_ON(group);\n+\treturn 0;\n+}\n+\n+static int\n+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)\n+{\n+\tstruct yt921x_port *pp = \u0026priv-\u003eports[port];\n+\tstruct yt921x_led *led = pp-\u003eleds[group];\n+\tu32 ctrl;\n+\tu32 mask;\n+\n+\tled-\u003euse_cycle = false;\n+\tled-\u003euse_duty = false;\n+\n+\tmask = YT921X_LED2_PORT_FORCEn_M(group);\n+\tctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :\n+\t YT921X_LED2_PORT_FORCEn_OFF(group);\n+\treturn yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,\n+\t\t\t\t ctrl);\n+}\n+\n+static int\n+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,\n+\t\t\tunsigned long off, unsigned short *cyclep,\n+\t\t\tunsigned char *dutyp)\n+{\n+\tstatic const unsigned char dutys[] = {\n+\t\tYT921X_LED_DUTY(1, 6),\n+\t\tYT921X_LED_DUTY(1, 4),\n+\t\tYT921X_LED_DUTY(1, 3),\n+\t\tYT921X_LED_DUTY(1, 2),\n+\t};\n+\tunsigned int cycle_upper;\n+\tunsigned int cycle_req;\n+\tunsigned int duty_req;\n+\tunsigned int cycle;\n+\tunsigned int duty;\n+\n+\tcycle = YT921X_LED_BLINK_MAX;\n+\tcycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */\n+\tif (check_add_overflow(on, off, \u0026cycle_req) || cycle_req \u003e= cycle_upper)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tfor (; cycle \u003e YT921X_LED_BLINK_MIN; cycle_upper \u003e\u003e= 1, cycle \u003e\u003e= 1)\n+\t\tif (cycle_req \u003e= cycle_upper \u003e\u003e 1)\n+\t\t\tbreak;\n+\t*cyclep = cycle;\n+\n+\tduty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *\n+\t\t\t\t (on \u003e off ? off : on), cycle_req);\n+\tfor (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)\n+\t\tif (i == 0 || duty_req \u003e= (dutys[i - 1] + dutys[i]) / 2) {\n+\t\t\tduty = dutys[i];\n+\t\t\tbreak;\n+\t\t}\n+\tif (on \u003e off)\n+\t\tduty = YT921X_LED_DUTY_DENOM - duty;\n+\t*dutyp = duty;\n+\n+\treturn 0;\n+}\n+\n+static int\n+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,\n+\t\t unsigned long *onp, unsigned long *offp)\n+{\n+\tstruct yt921x_port *pp = \u0026priv-\u003eports[port];\n+\tstruct yt921x_led *led = pp-\u003eleds[group];\n+\tunsigned short cycle;\n+\tunsigned char duty;\n+\tbool use_cycle;\n+\tu32 ctrl;\n+\tu32 mask;\n+\tu32 val;\n+\tint res;\n+\n+\tif (!*onp \u0026\u0026 !*offp) {\n+\t\tcycle = YT921X_LED_BLINK_DEF;\n+\t\tduty = YT921X_LED_DUTY(1, 2);\n+\t\tfor (unsigned int i = 0; i \u003c YT921X_LED_GROUP_NUM; i++)\n+\t\t\tif (i != group \u0026\u0026 pp-\u003eleds[i] \u0026\u0026\n+\t\t\t pp-\u003eleds[i]-\u003euse_duty) {\n+\t\t\t\tduty = pp-\u003eled_duty;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\tuse_cycle = false;\n+\t} else {\n+\t\tbool change_cycle;\n+\t\tbool change_duty;\n+\n+\t\tres = yt921x_led_blink_select(priv, *onp, *offp, \u0026cycle, \u0026duty);\n+\t\tif (res)\n+\t\t\treturn res;\n+\n+\t\tuse_cycle = cycle \u003c YT921X_LED_BLINK_DEF;\n+\t\tchange_cycle = use_cycle \u0026\u0026 cycle != pp-\u003eled_cycle;\n+\t\tchange_duty = duty != pp-\u003eled_duty;\n+\t\tif (change_cycle || change_duty)\n+\t\t\tfor (unsigned int i = 0; i \u003c YT921X_LED_GROUP_NUM;\n+\t\t\t i++) {\n+\t\t\t\tif (i == group || !pp-\u003eleds[i])\n+\t\t\t\t\tcontinue;\n+\t\t\t\tif ((change_cycle \u0026\u0026 pp-\u003eleds[i]-\u003euse_cycle) ||\n+\t\t\t\t (change_duty \u0026\u0026 pp-\u003eleds[i]-\u003euse_duty))\n+\t\t\t\t\treturn -EOPNOTSUPP;\n+\t\t\t}\n+\t}\n+\n+\t/* The chip seems to jam a while if changing duty directly */\n+\tres = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), \u0026val);\n+\tif (res)\n+\t\treturn res;\n+\n+\tctrl = val \u0026 ~YT921X_LED2_PORT_FORCEn_M(group);\n+\tctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);\n+\tif (val != ctrl) {\n+\t\tres = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\tmask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;\n+\tswitch (duty \u003e= YT921X_LED_DUTY(1, 2) ? duty :\n+\t\tYT921X_LED_DUTY_DENOM - duty) {\n+\tdefault:\n+\t\tduty = YT921X_LED_DUTY(1, 2);\n+\t\tfallthrough;\n+\tcase YT921X_LED_DUTY(1, 2):\n+\t\tctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;\n+\t\tbreak;\n+\tcase YT921X_LED_DUTY(2, 3):\n+\t\tctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;\n+\t\tbreak;\n+\tcase YT921X_LED_DUTY(3, 4):\n+\t\tctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;\n+\t\tbreak;\n+\tcase YT921X_LED_DUTY(5, 6):\n+\t\tctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;\n+\t\tbreak;\n+\t}\n+\tif (duty \u003c YT921X_LED_DUTY(1, 2))\n+\t\tctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;\n+\tif (use_cycle) {\n+\t\tmask |= YT921X_LED1_PORT_OTHER_BLINK_M;\n+\t\tctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));\n+\t}\n+\tres = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);\n+\tif (res)\n+\t\treturn res;\n+\n+\tctrl = val \u0026 ~(YT921X_LED2_PORT_FORCEn_M(group) |\n+\t\t YT921X_LED2_PORT_FORCE_BLINKn_M(group));\n+\tctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);\n+\tif (use_cycle)\n+\t\tctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);\n+\telse\n+\t\tctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);\n+\tres = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);\n+\tif (res)\n+\t\treturn res;\n+\n+\tled-\u003euse_cycle = use_cycle;\n+\tif (use_cycle)\n+\t\tpp-\u003eled_cycle = cycle;\n+\tled-\u003euse_duty = true;\n+\tpp-\u003eled_duty = duty;\n+\n+\t*onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);\n+\t*offp = cycle - *onp;\n+\treturn 0;\n+}\n+\n+struct yt921x_led_trigger_map {\n+\tunsigned long flags;\n+\tu32 mask;\n+};\n+\n+static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = {\n+\t{BIT(TRIGGER_NETDEV_LINK),\n+\t YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL},\n+\t{BIT(TRIGGER_NETDEV_LINK_10), YT921X_LEDx_PORT_ACT_10M},\n+\t{BIT(TRIGGER_NETDEV_LINK_100), YT921X_LEDx_PORT_ACT_100M},\n+\t{BIT(TRIGGER_NETDEV_LINK_1000), YT921X_LEDx_PORT_ACT_1000M},\n+\t{BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF},\n+\t{BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL},\n+\t{BIT(TRIGGER_NETDEV_TX), YT921X_LEDx_PORT_ACT_TX_BLINK},\n+\t{BIT(TRIGGER_NETDEV_RX), YT921X_LEDx_PORT_ACT_RX_BLINK},\n+\t{BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR),\n+\t YT921X_LEDx_PORT_ACT_COLLISION_BLINK},\n+};\n+\n+static bool\n+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,\n+\t\t\t\tint group, unsigned long flags)\n+{\n+\tif (!flags)\n+\t\treturn true;\n+\n+\tfor (unsigned int i = 0; i \u003c ARRAY_SIZE(yt921x_led_trigger_maps); i++) {\n+\t\tconst struct yt921x_led_trigger_map *map = \u0026yt921x_led_trigger_maps[i];\n+\n+\t\tif ((flags \u0026 map-\u003eflags) == map-\u003eflags) {\n+\t\t\tflags \u0026= ~map-\u003eflags;\n+\t\t\tif (!flags)\n+\t\t\t\treturn true;\n+\t\t}\n+\t}\n+\n+\treturn false;\n+}\n+\n+static int\n+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,\n+\t\t unsigned long *flagsp)\n+{\n+\tu32 addr;\n+\tu32 val;\n+\tint res;\n+\n+\tres = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), \u0026val);\n+\tif (res)\n+\t\treturn res;\n+\n+\tif ((val \u0026 YT921X_LED2_PORT_FORCEn_M(group)) !=\n+\t YT921X_LED2_PORT_FORCEn_DONTCARE(group)) {\n+\t\t*flagsp = 0;\n+\t\treturn 0;\n+\t}\n+\n+\tif (group != 2) {\n+\t\taddr = yt921x_led_regaddr(priv, port, group);\n+\t\tres = yt921x_reg_read(priv, addr, \u0026val);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\t*flagsp = 0;\n+\tfor (unsigned int i = 0; i \u003c ARRAY_SIZE(yt921x_led_trigger_maps); i++) {\n+\t\tconst struct yt921x_led_trigger_map *map = \u0026yt921x_led_trigger_maps[i];\n+\n+\t\tif ((val \u0026 map-\u003emask) == map-\u003emask)\n+\t\t\t*flagsp |= map-\u003eflags;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,\n+\t\t unsigned long flags)\n+{\n+\tstruct yt921x_port *pp = \u0026priv-\u003eports[port];\n+\tstruct yt921x_led *led = pp-\u003eleds[group];\n+\tu32 addr;\n+\tu32 ctrl;\n+\tu32 mask;\n+\tint res;\n+\n+\tctrl = 0;\n+\tfor (unsigned int i = 0; i \u003c ARRAY_SIZE(yt921x_led_trigger_maps); i++) {\n+\t\tconst struct yt921x_led_trigger_map *map = \u0026yt921x_led_trigger_maps[i];\n+\n+\t\tif ((flags \u0026 map-\u003eflags) == map-\u003eflags) {\n+\t\t\tflags \u0026= ~map-\u003eflags;\n+\t\t\tctrl |= map-\u003emask;\n+\t\t\tif (!flags)\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\tif (flags)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tled-\u003euse_cycle = false;\n+\tled-\u003euse_duty = false;\n+\n+\tmask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;\n+\tif (group == 2) {\n+\t\tmask |= YT921X_LED2_PORT_FORCEn_M(group);\n+\t\tctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);\n+\t}\n+\taddr = yt921x_led_regaddr(priv, port, group);\n+\tres = yt921x_reg_update_bits(priv, addr, mask, ctrl);\n+\tif (res)\n+\t\treturn res;\n+\n+\tif (group != 2) {\n+\t\tmask = YT921X_LED2_PORT_FORCEn_M(group);\n+\t\tctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);\n+\t\tres = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),\n+\t\t\t\t\t mask, ctrl);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,\n+\t\t\t\t enum led_brightness brightness)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\tint res;\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tres = yt921x_led_force_set(priv, pp-\u003eindex, led-\u003egroup, brightness);\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\treturn res;\n+}\n+\n+static int\n+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,\n+\t\t unsigned long *delay_off)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\tint res;\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tres = yt921x_led_blink_set(priv, pp-\u003eindex, led-\u003egroup, delay_on,\n+\t\t\t\t delay_off);\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\treturn res;\n+}\n+\n+static struct device * __maybe_unused\n+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\tstruct dsa_port *dp;\n+\n+\tdp = dsa_to_port(\u0026priv-\u003eds, pp-\u003eindex);\n+\tif (!dp || !dp-\u003euser)\n+\t\treturn NULL;\n+\treturn \u0026dp-\u003euser-\u003edev;\n+}\n+\n+static int __maybe_unused\n+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,\n+\t\t\t\t unsigned long flags)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\n+\tif (yt921x_led_trigger_is_supported(priv, pp-\u003eindex, led-\u003egroup, flags))\n+\t\treturn 0;\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int __maybe_unused\n+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\tint res;\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tres = yt921x_led_trigger_get(priv, pp-\u003eindex, led-\u003egroup, flagsp);\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\treturn res;\n+}\n+\n+static int __maybe_unused\n+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)\n+{\n+\tstruct yt921x_led *led = to_yt921x_led(led_cdev);\n+\tstruct yt921x_port *pp = to_yt921x_port(led);\n+\tstruct yt921x_priv *priv = to_yt921x_priv(pp);\n+\tint res;\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tres = yt921x_led_trigger_set(priv, pp-\u003eindex, led-\u003egroup, flags);\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\treturn res;\n+}\n+\n+static int\n+yt921x_led_setup(struct yt921x_priv *priv, int port,\n+\t\t struct fwnode_handle *fwnode, u32 *inv_maskp, u32 *inv_ctrlp)\n+{\n+\tstruct yt921x_port *pp = \u0026priv-\u003eports[port];\n+\tstruct device *dev = to_device(priv);\n+\tstruct led_init_data init_data;\n+\tstruct led_classdev *led_cdev;\n+\tchar name[LED_MAX_NAME_SIZE];\n+\tenum led_default_state state;\n+\tstruct yt921x_led *led;\n+\tbool force_high;\n+\tbool force_low;\n+\tint polarity;\n+\tu32 group;\n+\tbool on;\n+\tint res;\n+\n+\tres = fwnode_property_read_u32(fwnode, \"reg\", \u0026group);\n+\tif (res)\n+\t\treturn res;\n+\tif (group \u003e= YT921X_LED_GROUP_NUM) {\n+\t\tdev_err(dev, \"Invalid LED reg %u for port %d\\n\", group, port);\n+\t\treturn -EINVAL;\n+\t}\n+\tif (pp-\u003eleds[group])\n+\t\treturn -EEXIST;\n+\n+\tforce_high = fwnode_property_read_bool(fwnode, \"active-high\");\n+\tforce_low = fwnode_property_read_bool(fwnode, \"active-low\");\n+\tif (force_high \u0026\u0026 force_low) {\n+\t\tdev_err(dev, \"Duplicate polarities for LED %02d:%02u\\n\",\n+\t\t\tport, group);\n+\t\treturn -EINVAL;\n+\t}\n+\tpolarity = force_high ? 1 : force_low ? -1 : 0;\n+\n+\tled = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);\n+\tif (!led)\n+\t\treturn -ENOMEM;\n+\tled-\u003eport = pp;\n+\tled-\u003egroup = group;\n+\n+\tstate = led_init_default_state_get(fwnode);\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tpp-\u003eleds[group] = led;\n+\tswitch (state) {\n+\tcase LEDS_DEFSTATE_OFF:\n+\tcase LEDS_DEFSTATE_ON:\n+\tdefault:\n+\t\ton = state == LEDS_DEFSTATE_ON;\n+\t\tres = yt921x_led_force_set(priv, port, group, on);\n+\t\tbreak;\n+\tcase LEDS_DEFSTATE_KEEP:\n+\t\tres = yt921x_led_force_get(priv, port, group, \u0026on);\n+\t\tbreak;\n+\t}\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\tif (res)\n+\t\tgoto err;\n+\n+\tled_cdev = \u0026led-\u003ecdev;\n+\tled_cdev-\u003ebrightness = on;\n+\tled_cdev-\u003emax_brightness = 1;\n+\tled_cdev-\u003eflags = LED_RETAIN_AT_SHUTDOWN;\n+\tled_cdev-\u003ebrightness_set_blocking = yt921x_cled_brightness_set_blocking;\n+\tled_cdev-\u003eblink_set = yt921x_cled_blink_set;\n+#ifdef CONFIG_LEDS_TRIGGERS\n+\tled_cdev-\u003ehw_control_trigger = \"netdev\";\n+\tled_cdev-\u003ehw_control_get_device = yt921x_cled_hw_control_get_device;\n+\tled_cdev-\u003ehw_control_is_supported = yt921x_cled_hw_control_is_supported;\n+\tled_cdev-\u003ehw_control_get = yt921x_cled_hw_control_get;\n+\tled_cdev-\u003ehw_control_set = yt921x_cled_hw_control_set;\n+#endif\n+\n+\tsnprintf(name, sizeof(name), YT921X_NAME \"-%u:%02d:%02u\",\n+\t\t priv-\u003eds.index, port, group);\n+\tinit_data = (typeof(init_data)){\n+\t\t.fwnode = fwnode,\n+\t\t.devicename = name,\n+\t\t.devname_mandatory = true,\n+\t};\n+\tres = devm_led_classdev_register_ext(dev, led_cdev, \u0026init_data);\n+\tif (res)\n+\t\tgoto err;\n+\n+\tif (polarity) {\n+\t\tu32 bit = YT921X_LED_PAR_INV_INVnm(group, port);\n+\n+\t\t*inv_maskp |= bit;\n+\t\tif (polarity \u003e 0)\n+\t\t\t*inv_ctrlp |= bit;\n+\t}\n+\treturn 0;\n+\n+err:\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\tpp-\u003eleds[group] = NULL;\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\tdevm_kfree(dev, led);\n+\treturn res;\n+}\n+\n+static void yt921x_leds_remove_port(struct yt921x_priv *priv, int port)\n+{\n+\tstruct yt921x_port *pp = \u0026priv-\u003eports[port];\n+\tstruct device *dev = to_device(priv);\n+\tbool has_leds = false;\n+\n+\tfor (int group = 0; group \u003c YT921X_LED_GROUP_NUM; group++) {\n+\t\tstruct yt921x_led *led = pp-\u003eleds[group];\n+\n+\t\tif (led) {\n+\t\t\tdevm_led_classdev_unregister(dev, \u0026led-\u003ecdev);\n+\t\t\thas_leds = true;\n+\t\t}\n+\t}\n+\n+\tif (!has_leds)\n+\t\treturn;\n+\n+\tfor (int group = 0; group \u003c YT921X_LED_GROUP_NUM; group++) {\n+\t\tstruct yt921x_led *led = pp-\u003eleds[group];\n+\n+\t\tif (led) {\n+\t\t\tpp-\u003eleds[group] = NULL;\n+\t\t\tdevm_kfree(dev, led);\n+\t\t}\n+\t}\n+}\n+\n+static int\n+yt921x_leds_setup_port(struct yt921x_priv *priv, int port, u32 *inv_maskp,\n+\t\t u32 *inv_ctrlp)\n+{\n+\tstruct device *dev = to_device(priv);\n+\tstruct dsa_switch *ds = \u0026priv-\u003eds;\n+\tstruct device_node *leds_np;\n+\tstruct dsa_port *dp;\n+\tint res;\n+\n+\tdp = dsa_to_port(ds, port);\n+\tleds_np = of_get_child_by_name(dp-\u003edn, \"leds\");\n+\tif (!leds_np)\n+\t\treturn 0;\n+\n+\tif (port \u003e= YT921X_LED_PORT_NUM) {\n+\t\tdev_err(dev, \"No LEDs for port %d\\n\", port);\n+\t\tof_node_put(leds_np);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tfor_each_child_of_node_scoped(leds_np, led_np) {\n+\t\tres = yt921x_led_setup(priv, port, of_fwnode_handle(led_np),\n+\t\t\t\t inv_maskp, inv_ctrlp);\n+\t\tif (res) {\n+\t\t\tof_node_put(leds_np);\n+\t\t\tgoto err;\n+\t\t}\n+\t}\n+\n+\tof_node_put(leds_np);\n+\treturn 0;\n+\n+err:\n+\tyt921x_leds_remove_port(priv, port);\n+\treturn res;\n+}\n+\n+void yt921x_leds_remove(struct yt921x_priv *priv)\n+{\n+\tfor (int port = 0; port \u003c YT921X_LED_PORT_NUM; port++)\n+\t\tyt921x_leds_remove_port(priv, port);\n+}\n+\n+int yt921x_leds_setup(struct yt921x_priv *priv)\n+{\n+\tstruct device *dev = to_device(priv);\n+\tstruct dsa_switch *ds = \u0026priv-\u003eds;\n+\tstruct dsa_port *dp;\n+\tu32 inv_ctrl = 0;\n+\tu32 inv_mask = 0;\n+\tu32 ctrl;\n+\tu32 mask;\n+\tint res;\n+\n+\tdsa_switch_for_each_port(dp, ds) {\n+\t\tint port = dp-\u003eindex;\n+\n+\t\tif (!dp-\u003edn)\n+\t\t\tcontinue;\n+\n+\t\tres = yt921x_leds_setup_port(priv, port, \u0026inv_mask, \u0026inv_ctrl);\n+\t\tif (res)\n+\t\t\tdev_warn(dev, \"Failed to setup LEDs for port %d: %d\\n\",\n+\t\t\t\t port, res);\n+\t}\n+\n+\tmutex_lock(\u0026priv-\u003ereg_lock);\n+\n+\t/* Inversion is internal - force on will give low logic.\n+\t * In the rest of the file, treat LEDs as if active-low.\n+\t */\n+\tif (inv_mask) {\n+\t\tres = yt921x_reg_update_bits(priv, YT921X_LED_PAR_INV, inv_mask,\n+\t\t\t\t\t inv_ctrl);\n+\t\tif (res)\n+\t\t\tgoto end;\n+\t}\n+\n+\tmask = YT921X_LED_CTRL_MODE_M | YT921X_LED_CTRL_PORT_NUM_M |\n+\t YT921X_LED_CTRL_EN;\n+\tctrl = YT921X_LED_CTRL_MODE_PARALLEL |\n+\t YT921X_LED_CTRL_PORT_NUM(YT921X_PORT_NUM - 1) |\n+\t YT921X_LED_CTRL_EN;\n+\tres = yt921x_reg_update_bits(priv, YT921X_LED_CTRL, mask, ctrl);\n+\n+end:\n+\tmutex_unlock(\u0026priv-\u003ereg_lock);\n+\n+\tif (res) {\n+\t\tyt921x_leds_remove(priv);\n+\t\treturn res;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h\nnew file mode 100644\nindex 00000000000000..67373a190c8f4b\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/leds.h\n@@ -0,0 +1,121 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (c) 2026 David Yang\n+ */\n+\n+#ifndef _YT_LEDS_H\n+#define _YT_LEDS_H\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/kconfig.h\u003e\n+#include \u003clinux/leds.h\u003e\n+\n+#define YT921X_LED_CTRL\t\t\t0xd0000\n+#define YT921X_LED_CTRL_EN\t\t\tBIT(21)\n+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M\tGENMASK(20, 19)\t/* cycle = 512 * x ms */\n+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x)\t\tFIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))\n+#define YT921X_LED_CTRL_PORT_NUM_M\t\tGENMASK(16, 13)\n+#define YT921X_LED_CTRL_PORT_NUM(x)\t\t\tFIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))\n+#define YT921X_LED_CTRL_MODE_M\t\t\tGENMASK(1, 0)\n+#define YT921X_LED_CTRL_MODE(x)\t\t\tFIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))\n+#define YT921X_LED_CTRL_MODE_PARALLEL\t\t\tYT921X_LED_CTRL_MODE(0)\n+#define YT921X_LED_CTRL_MODE_SERIAL\t\t\tYT921X_LED_CTRL_MODE(2)\n+#define YT921X_LED0_PORTn(port)\t\t(0xd0004 + 4 * (port))\n+#define YT921X_LED0_PORT_ACT_M\t\t\tGENMASK(17, 0)\n+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS\tBIT(17)\n+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI\tBIT(16)\n+#define YT921X_LED1_PORTn(port)\t\t(0xd0040 + 4 * (port))\n+#define YT921X_LED1_PORT_OTHER_BLINK_M\t\tGENMASK(31, 30)\t/* cycle = 512 \u003e\u003e x ms */\n+#define YT921X_LED1_PORT_OTHER_BLINK(x)\t\tFIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))\n+#define YT921X_LED1_PORT_EEE_BLINK_M\t\tGENMASK(29, 28)\t/* cycle = 512 \u003e\u003e x ms */\n+#define YT921X_LED1_PORT_EEE_BLINK(x)\t\t\tFIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))\n+#define YT921X_LED1_PORT_BLINK_DUTY_COMP\tBIT(27)\n+#define YT921X_LED1_PORT_BLINK_DUTY_M\t\tGENMASK(26, 25)\n+#define YT921X_LED1_PORT_BLINK_DUTY(x)\t\tFIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))\n+#define YT921X_LED1_PORT_BLINK_DUTY_1_2\t\tYT921X_LED1_PORT_BLINK_DUTY(0)\n+#define YT921X_LED1_PORT_BLINK_DUTY_2_3\t\tYT921X_LED1_PORT_BLINK_DUTY(1)\n+#define YT921X_LED1_PORT_BLINK_DUTY_3_4\t\tYT921X_LED1_PORT_BLINK_DUTY(2)\n+#define YT921X_LED1_PORT_BLINK_DUTY_5_6\t\tYT921X_LED1_PORT_BLINK_DUTY(3)\n+#define YT921X_LED2_PORTn(port)\t\t(0xd0080 + 4 * (port))\n+#define YT921X_LED2_PORT_FORCEn_M(grp)\t\tGENMASK(4 * (grp) + 19, 4 * (grp) + 18)\n+#define YT921X_LED2_PORT_FORCEn(grp, x)\t\t((x) \u003c\u003c (4 * (grp) + 18))\n+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp)\t\tYT921X_LED2_PORT_FORCEn(grp, 0)\n+#define YT921X_LED2_PORT_FORCEn_BLINK(grp)\t\tYT921X_LED2_PORT_FORCEn(grp, 1)\n+#define YT921X_LED2_PORT_FORCEn_ON(grp)\t\tYT921X_LED2_PORT_FORCEn(grp, 2)\n+#define YT921X_LED2_PORT_FORCEn_OFF(grp)\t\tYT921X_LED2_PORT_FORCEn(grp, 3)\n+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp)\tGENMASK(4 * (grp) + 17, 4 * (grp) + 16)\t/* cycle = 512 \u003c\u003c x ms */\n+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x)\t\t((x) \u003c\u003c (4 * (grp) + 16))\n+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp)\tYT921X_LED2_PORT_FORCE_BLINKn(grp, 3)\n+#define YT921X_LEDx_PORT_ACT_M\t\t\tGENMASK(15, 0)\n+#define YT921X_LEDx_PORT_ACT_EEE_BLINK\t\tBIT(15)\n+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK\tBIT(14)\n+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK\tBIT(13)\n+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL\tBIT(12)\n+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF\tBIT(11)\n+#define YT921X_LEDx_PORT_ACT_TX_BLINK\t\tBIT(10)\n+#define YT921X_LEDx_PORT_ACT_RX_BLINK\t\tBIT(9)\n+#define YT921X_LEDx_PORT_ACT_TX\t\tBIT(8)\n+#define YT921X_LEDx_PORT_ACT_RX\t\tBIT(7)\n+#define YT921X_LEDx_PORT_ACT_1000M\t\tBIT(6)\n+#define YT921X_LEDx_PORT_ACT_100M\t\tBIT(5)\n+#define YT921X_LEDx_PORT_ACT_10M\t\tBIT(4)\n+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK\tBIT(3)\n+#define YT921X_LEDx_PORT_ACT_1000M_BLINK\tBIT(2)\n+#define YT921X_LEDx_PORT_ACT_100M_BLINK\tBIT(1)\n+#define YT921X_LEDx_PORT_ACT_10M_BLINK\t\tBIT(0)\n+#define YT921X_LED_SER_CTRL\t\t0xd0100\n+#define YT921X_LED_SER_CTRL_UNK\t\tGENMASK(25, 24)\t/* delay? */\n+#define YT921X_LED_SER_CTRL_ACTIVE_LOW\t\tBIT(4)\n+#define YT921X_LED_SER_CTRL_GRP_NUM_M\t\tGENMASK(1, 0)\t/* #grp - 1 */\n+#define YT921X_LED_SER_CTRL_GRP_NUM(x)\t\tFIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))\n+#define YT921X_LED_SER_MAPnm(grp, port)\t(0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))\n+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port)\tGENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)\n+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x)\t\t((x) \u003c\u003c (6 * ((port) % 5) + 2))\n+#define YT921X_LED_SER_MAP_DSTn_LED_M(port)\tGENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))\n+#define YT921X_LED_SER_MAP_DSTn_LED(port, x)\t\t((x) \u003c\u003c (6 * ((port) % 5)))\n+#define YT921X_LED_PAR_PORTS\t\t0xd01c4\n+#define YT921X_LED_PAR_INV\t\t0xd01c8\n+#define YT921X_LED_PAR_INV_INVnm(grp, port)\tBIT(10 * (grp) + (port))\n+#define YT921X_LED_PAR_MAPn(port)\t(0xd01d0 + 4 * (port))\n+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp)\tGENMASK(6 * (grp) + 5, 6 * (grp) + 2)\n+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x)\t\t((x) \u003c\u003c (6 * (grp) + 2))\n+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp)\tGENMASK(6 * (grp) + 1, 6 * (grp))\n+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x)\t\t((x) \u003c\u003c (6 * (grp)))\n+\n+#define YT921X_LED_BLINK_MIN\t64\n+#define YT921X_LED_BLINK_DEF\t512\n+#define YT921X_LED_BLINK_MAX\t2048\n+\n+/* 2 * lcm(2, 3, 4, 6) */\n+#define YT921X_LED_DUTY_DENOM\t\t24\n+#define YT921X_LED_DUTY(nom, denom)\t(YT921X_LED_DUTY_DENOM * (nom) / (denom))\n+\n+struct yt921x_priv;\n+\n+struct yt921x_led {\n+\tstruct led_classdev cdev;\n+\tstruct yt921x_port *port;\n+\tunsigned char group;\n+\n+\tbool use_cycle:1;\n+\tbool use_duty:1;\n+};\n+\n+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n+\n+void yt921x_leds_remove(struct yt921x_priv *priv);\n+int yt921x_leds_setup(struct yt921x_priv *priv);\n+\n+#else\n+\n+static inline void yt921x_leds_remove(struct yt921x_priv *priv) {}\n+\n+static inline int yt921x_leds_setup(struct yt921x_priv *priv)\n+{\n+\treturn 0;\n+}\n+\n+#endif\n+\n+#endif\ndiff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c\nnew file mode 100644\nindex 00000000000000..9054896e4cd12b\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/smi.c\n@@ -0,0 +1,157 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright (c) 2026 David Yang\n+ */\n+\n+#include \u003clinux/iopoll.h\u003e\n+\n+#include \"chip.h\"\n+#include \"smi.h\"\n+\n+#define YT921X_POLL_SLEEP_US\t10000\n+#define YT921X_POLL_TIMEOUT_US\t100000\n+\n+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)\n+{\n+\tlockdep_assert_held_once(\u0026priv-\u003ereg_lock);\n+\n+\treturn priv-\u003ereg_ops-\u003eread(priv-\u003ereg_ctx, reg, valp);\n+}\n+\n+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)\n+{\n+\tlockdep_assert_held_once(\u0026priv-\u003ereg_lock);\n+\n+\treturn priv-\u003ereg_ops-\u003ewrite(priv-\u003ereg_ctx, reg, val);\n+}\n+\n+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)\n+{\n+\tu32 val;\n+\tint res;\n+\tint ret;\n+\n+\tret = read_poll_timeout(yt921x_reg_read, res,\n+\t\t\t\tres || (val \u0026 mask) == *valp,\n+\t\t\t\tYT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,\n+\t\t\t\tfalse, priv, reg, \u0026val);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (res)\n+\t\treturn res;\n+\n+\t*valp = val;\n+\treturn 0;\n+}\n+\n+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)\n+{\n+\tint res;\n+\tu32 v;\n+\tu32 u;\n+\n+\tres = yt921x_reg_read(priv, reg, \u0026v);\n+\tif (res)\n+\t\treturn res;\n+\n+\tu = v;\n+\tu \u0026= ~mask;\n+\tu |= val;\n+\tif (u == v)\n+\t\treturn 0;\n+\n+\treturn yt921x_reg_write(priv, reg, u);\n+}\n+\n+int\n+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,\n+\t\t unsigned int num_regs)\n+{\n+\tint res;\n+\n+\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n+\t\tres = yt921x_reg_read(priv, reg + 4 * i, \u0026vals[i]);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,\n+\t\t unsigned int num_regs)\n+{\n+\tint res;\n+\n+\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n+\t\tres = yt921x_reg_write(priv, reg + 4 * i, vals[i]);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n+\t\t\tconst u32 *vals, unsigned int num_regs)\n+{\n+\tbool changed = false;\n+\tu32 vs[4];\n+\tint res;\n+\n+\tif (WARN_ON_ONCE(num_regs \u003e ARRAY_SIZE(vs)))\n+\t\treturn -EINVAL;\n+\n+\tres = yt921x_regs_read(priv, reg, vs, num_regs);\n+\tif (res)\n+\t\treturn res;\n+\n+\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n+\t\tu32 u = vs[i];\n+\n+\t\tu \u0026= ~masks[i];\n+\t\tu |= vals[i];\n+\t\tif (u != vs[i])\n+\t\t\tchanged = true;\n+\n+\t\tvs[i] = u;\n+\t}\n+\n+\tif (!changed)\n+\t\treturn 0;\n+\n+\treturn yt921x_regs_write(priv, reg, vs, num_regs);\n+}\n+\n+int\n+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n+\t\t unsigned int num_regs)\n+{\n+\tbool changed = false;\n+\tu32 vs[4];\n+\tint res;\n+\n+\tif (WARN_ON_ONCE(num_regs \u003e ARRAY_SIZE(vs)))\n+\t\treturn -EINVAL;\n+\n+\tres = yt921x_regs_read(priv, reg, vs, num_regs);\n+\tif (res)\n+\t\treturn res;\n+\n+\tfor (unsigned int i = 0; i \u003c num_regs; i++) {\n+\t\tu32 u = vs[i];\n+\n+\t\tu \u0026= ~masks[i];\n+\t\tif (u != vs[i])\n+\t\t\tchanged = true;\n+\n+\t\tvs[i] = u;\n+\t}\n+\n+\tif (!changed)\n+\t\treturn 0;\n+\n+\treturn yt921x_regs_write(priv, reg, vs, num_regs);\n+}\ndiff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h\nnew file mode 100644\nindex 00000000000000..2e956065eb90ee\n--- /dev/null\n+++ b/drivers/net/dsa/motorcomm/smi.h\n@@ -0,0 +1,88 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (c) 2026 David Yang\n+ */\n+\n+#ifndef _YT_SMI_H\n+#define _YT_SMI_H\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/wordpart.h\u003e\n+\n+struct yt921x_priv;\n+\n+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);\n+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);\n+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);\n+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,\n+\t\t\t u32 val);\n+\n+static inline int\n+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)\n+{\n+\treturn yt921x_reg_update_bits(priv, reg, 0, mask);\n+}\n+\n+static inline int\n+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)\n+{\n+\treturn yt921x_reg_update_bits(priv, reg, mask, 0);\n+}\n+\n+static inline int\n+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)\n+{\n+\treturn yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);\n+}\n+\n+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single\n+ * long register. More specifically, writes to parts of its words won't become\n+ * visible, until the last word is written.\n+ *\n+ * Here we require full read and write operations over these registers to\n+ * eliminate potential issues, although partial reads/writes are also possible.\n+ */\n+\n+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)\n+{\n+\t*lo \u0026= ~lower_32_bits(mask);\n+\t*hi \u0026= ~upper_32_bits(mask);\n+\t*lo |= lower_32_bits(val);\n+\t*hi |= upper_32_bits(val);\n+}\n+\n+int yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,\n+\t\t unsigned int num_regs);\n+int yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,\n+\t\t unsigned int num_regs);\n+int yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n+\t\t\t const u32 *vals, unsigned int num_regs);\n+int yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n+\t\t\t unsigned int num_regs);\n+\n+static inline int\n+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)\n+{\n+\treturn yt921x_regs_write(priv, reg, vals, 2);\n+}\n+\n+static inline int\n+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,\n+\t\t\t const u32 *vals)\n+{\n+\treturn yt921x_regs_update_bits(priv, reg, masks, vals, 2);\n+}\n+\n+static inline int\n+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)\n+{\n+\treturn yt921x_regs_clear_bits(priv, reg, masks, 2);\n+}\n+\n+static inline int\n+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)\n+{\n+\treturn yt921x_regs_write(priv, reg, vals, 3);\n+}\n+\n+#endif\n"
} |
| 2/1 | 2026/07/27 06:14 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies a vendor-specific DSA switch driver (Motorcomm YT9215) connected via MDIO. The driver requires physical hardware to probe successfully (it reads a specific chip ID during initialization) and is not emulated in standard QEMU. Therefore, the code is unreachable in a virtualized environment.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit c7b24fc75ad0587c2297f10f4986844c6f42a8ff
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Jul 27 06:14:15 2026 +0000
syz-cluster: applied patch under review
diff --git a/MAINTAINERS b/MAINTAINERS
index 60cff00953dcbe..f512467753bc24 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18249,7 +18249,7 @@ M: David Yang <mmyangfl@gmail.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
-F: drivers/net/dsa/yt921x.*
+F: drivers/net/dsa/motorcomm/
F: net/dsa/tag_yt921x.c
MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbafc6..98e9bbe47de7b1 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -72,6 +72,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/motorcomm/Kconfig"
+
source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
@@ -158,12 +160,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
-
-config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
- select NET_DSA_TAG_YT921X
- select NET_IEEE8021Q_HELPERS if DCB
- help
- This enables support for the Motorcomm YT9215 ethernet switch
- chip.
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc093..138225baa4d564 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -14,11 +14,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
obj-y += b53/
obj-y += hirschmann/
obj-y += lantiq/
obj-y += microchip/
+obj-y += motorcomm/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
obj-y += netc/
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
new file mode 100644
index 00000000000000..3438e0b143611a
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: ISC
+config NET_DSA_YT921X
+ tristate "Motorcomm YT9215 ethernet switch chip support"
+ select NET_DSA_TAG_YT921X
+ select NET_IEEE8021Q_HELPERS if DCB
+ help
+ This enables support for the Motorcomm YT9215 ethernet switch
+ chip.
+
+config NET_DSA_YT921X_LEDS
+ bool "LED support for Motorcomm YT9215"
+ default y
+ depends on NET_DSA_YT921X
+ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
+ help
+ This enables support for controlling the LEDs attached to the
+ Motorcomm YT9215 switch chips.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
new file mode 100644
index 00000000000000..5a63db0029ffe2
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: ISC
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+yt921x-objs := chip.o
+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
+yt921x-objs += smi.o
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/motorcomm/chip.c
similarity index 96%
rename from drivers/net/dsa/yt921x.c
rename to drivers/net/dsa/motorcomm/chip.c
index 159b16606f6ca4..553efacd4f5ee3 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -13,7 +13,6 @@
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -26,7 +25,9 @@
#include <net/ieee8021q.h>
#include <net/pkt_cls.h>
-#include "yt921x.h"
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
struct yt921x_mib_desc {
unsigned int size;
@@ -151,13 +152,8 @@ static const struct yt921x_info yt921x_infos[] = {
{}
};
-#define YT921X_NAME "yt921x"
-
#define YT921X_VID_UNWARE 4095
-#define YT921X_POLL_SLEEP_US 10000
-#define YT921X_POLL_TIMEOUT_US 100000
-
/* The interval should be small enough to avoid overflow of 32bit MIBs.
*
* Until we can read MIBs from stats64 call directly (i.e. sleep
@@ -196,208 +192,6 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
return (addr[4] << 8) | addr[5];
}
-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->read(priv->reg_ctx, reg, valp);
-}
-
-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->write(priv->reg_ctx, reg, val);
-}
-
-static int
-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
-{
- u32 val;
- int res;
- int ret;
-
- ret = read_poll_timeout(yt921x_reg_read, res,
- res || (val & mask) == *valp,
- YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
- false, priv, reg, &val);
- if (ret)
- return ret;
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
-{
- int res;
- u32 v;
- u32 u;
-
- res = yt921x_reg_read(priv, reg, &v);
- if (res)
- return res;
-
- u = v;
- u &= ~mask;
- u |= val;
- if (u == v)
- return 0;
-
- return yt921x_reg_write(priv, reg, u);
-}
-
-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, 0, mask);
-}
-
-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, mask, 0);
-}
-
-static int
-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
-{
- return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
-}
-
-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
- * long register. More specifically, writes to parts of its words won't become
- * visible, until the last word is written.
- *
- * Here we require full read and write operations over these registers to
- * eliminate potential issues, although partial reads/writes are also possible.
- */
-
-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
-{
- *lo &= ~lower_32_bits(mask);
- *hi &= ~upper_32_bits(mask);
- *lo |= lower_32_bits(val);
- *hi |= upper_32_bits(val);
-}
-
-static int
-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals, unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- u |= vals[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 2);
-}
-
-static int
-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals)
-{
- return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
-}
-
-static int
-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
-{
- return yt921x_regs_clear_bits(priv, reg, masks, 2);
-}
-
-static int
-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 3);
-}
-
static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
{
struct yt921x_reg_mdio *mdio = context;
@@ -4786,6 +4580,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
if (res)
return res;
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ res = yt921x_leds_setup(priv);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs: %d\n", res);
+#endif
+
return 0;
}
@@ -4886,6 +4686,10 @@ static void yt921x_mdio_remove(struct mdio_device *mdiodev)
if (!priv)
return;
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ yt921x_leds_remove(priv);
+#endif
+
for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
struct yt921x_port *pp = &priv->ports[i];
diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/motorcomm/chip.h
similarity index 99%
rename from drivers/net/dsa/yt921x.h
rename to drivers/net/dsa/motorcomm/chip.h
index 5f3b99e189c412..e6815cf9cb3cd0 100644
--- a/drivers/net/dsa/yt921x.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -850,9 +850,14 @@ enum yt921x_fdb_entry_status {
#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
#define YT921X_UDF_NUM 8
+#define YT921X_LED_GROUP_NUM 3
+#define YT921X_LED_PORT_NUM 10
+
/* 8 internal + 2 external + 1 mcu */
#define YT921X_PORT_NUM 11
+#define YT921X_NAME "yt921x"
+
#define yt921x_port_is_internal(port) ((port) < 8)
#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9)
@@ -938,6 +943,13 @@ struct yt921x_port {
struct yt921x_mib mib;
u64 rx_frames;
u64 tx_frames;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ unsigned char led_duty;
+ unsigned short led_cycle;
+
+ struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
+#endif
};
struct yt921x_reg_ops {
diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
new file mode 100644
index 00000000000000..37e8d4f0003f81
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.c
@@ -0,0 +1,660 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/uleds.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
+
+#define to_yt921x_led(led_cdev) \
+ container_of_const((led_cdev), struct yt921x_led, cdev)
+#define to_yt921x_port(led) ((led)->port)
+#define to_yt921x_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
+#define to_device(priv) ((priv)->ds.dev)
+
+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)
+{
+ switch (group) {
+ case 0:
+ default:
+ return YT921X_LED0_PORTn(port);
+ case 1:
+ return YT921X_LED1_PORTn(port);
+ case 2:
+ return YT921X_LED2_PORTn(port);
+ }
+}
+
+static int
+yt921x_led_force_get(struct yt921x_priv *priv, int port, int group, bool *onp)
+{
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ *onp = (val & YT921X_LED2_PORT_FORCEn_M(group)) ==
+ YT921X_LED2_PORT_FORCEn_ON(group);
+ return 0;
+}
+
+static int
+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ u32 ctrl;
+ u32 mask;
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,
+ ctrl);
+}
+
+static int
+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
+ unsigned long off, unsigned short *cyclep,
+ unsigned char *dutyp)
+{
+ static const unsigned char dutys[] = {
+ YT921X_LED_DUTY(1, 6),
+ YT921X_LED_DUTY(1, 4),
+ YT921X_LED_DUTY(1, 3),
+ YT921X_LED_DUTY(1, 2),
+ };
+ unsigned int cycle_upper;
+ unsigned int cycle_req;
+ unsigned int duty_req;
+ unsigned int cycle;
+ unsigned int duty;
+
+ cycle = YT921X_LED_BLINK_MAX;
+ cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */
+ if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper)
+ return -EOPNOTSUPP;
+
+ for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1)
+ if (cycle_req >= cycle_upper >> 1)
+ break;
+ *cyclep = cycle;
+
+ duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
+ (on > off ? off : on), cycle_req);
+ for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
+ if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
+ duty = dutys[i];
+ break;
+ }
+ if (on > off)
+ duty = YT921X_LED_DUTY_DENOM - duty;
+ *dutyp = duty;
+
+ return 0;
+}
+
+static int
+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long *onp, unsigned long *offp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ unsigned short cycle;
+ unsigned char duty;
+ bool use_cycle;
+ u32 ctrl;
+ u32 mask;
+ u32 val;
+ int res;
+
+ if (!*onp && !*offp) {
+ cycle = YT921X_LED_BLINK_DEF;
+ duty = YT921X_LED_DUTY(1, 2);
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM; i++)
+ if (i != group && pp->leds[i] &&
+ pp->leds[i]->use_duty) {
+ duty = pp->led_duty;
+ break;
+ }
+
+ use_cycle = false;
+ } else {
+ bool change_cycle;
+ bool change_duty;
+
+ res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
+ if (res)
+ return res;
+
+ use_cycle = cycle < YT921X_LED_BLINK_DEF;
+ change_cycle = use_cycle && cycle != pp->led_cycle;
+ change_duty = duty != pp->led_duty;
+ if (change_cycle || change_duty)
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM;
+ i++) {
+ if (i == group || !pp->leds[i])
+ continue;
+ if ((change_cycle && pp->leds[i]->use_cycle) ||
+ (change_duty && pp->leds[i]->use_duty))
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* The chip seems to jam a while if changing duty directly */
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ if (val != ctrl) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ switch (duty >= YT921X_LED_DUTY(1, 2) ? duty :
+ YT921X_LED_DUTY_DENOM - duty) {
+ default:
+ duty = YT921X_LED_DUTY(1, 2);
+ fallthrough;
+ case YT921X_LED_DUTY(1, 2):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;
+ break;
+ case YT921X_LED_DUTY(2, 3):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;
+ break;
+ case YT921X_LED_DUTY(3, 4):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;
+ break;
+ case YT921X_LED_DUTY(5, 6):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;
+ break;
+ }
+ if (duty < YT921X_LED_DUTY(1, 2))
+ ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ if (use_cycle) {
+ mask |= YT921X_LED1_PORT_OTHER_BLINK_M;
+ ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));
+ }
+ res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) |
+ YT921X_LED2_PORT_FORCE_BLINKn_M(group));
+ ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);
+ if (use_cycle)
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);
+ else
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+
+ led->use_cycle = use_cycle;
+ if (use_cycle)
+ pp->led_cycle = cycle;
+ led->use_duty = true;
+ pp->led_duty = duty;
+
+ *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
+ *offp = cycle - *onp;
+ return 0;
+}
+
+struct yt921x_led_trigger_map {
+ unsigned long flags;
+ u32 mask;
+};
+
+static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = {
+ {BIT(TRIGGER_NETDEV_LINK),
+ YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_LINK_10), YT921X_LEDx_PORT_ACT_10M},
+ {BIT(TRIGGER_NETDEV_LINK_100), YT921X_LEDx_PORT_ACT_100M},
+ {BIT(TRIGGER_NETDEV_LINK_1000), YT921X_LEDx_PORT_ACT_1000M},
+ {BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF},
+ {BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_TX), YT921X_LEDx_PORT_ACT_TX_BLINK},
+ {BIT(TRIGGER_NETDEV_RX), YT921X_LEDx_PORT_ACT_RX_BLINK},
+ {BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR),
+ YT921X_LEDx_PORT_ACT_COLLISION_BLINK},
+};
+
+static bool
+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,
+ int group, unsigned long flags)
+{
+ if (!flags)
+ return true;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ if (!flags)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int
+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,
+ unsigned long *flagsp)
+{
+ u32 addr;
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ if ((val & YT921X_LED2_PORT_FORCEn_M(group)) !=
+ YT921X_LED2_PORT_FORCEn_DONTCARE(group)) {
+ *flagsp = 0;
+ return 0;
+ }
+
+ if (group != 2) {
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_read(priv, addr, &val);
+ if (res)
+ return res;
+ }
+
+ *flagsp = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((val & map->mask) == map->mask)
+ *flagsp |= map->flags;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ u32 addr;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ ctrl = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ ctrl |= map->mask;
+ if (!flags)
+ break;
+ }
+ }
+ if (flags)
+ return -EOPNOTSUPP;
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;
+ if (group == 2) {
+ mask |= YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ }
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_update_bits(priv, addr, mask, ctrl);
+ if (res)
+ return res;
+
+ if (group != 2) {
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_force_set(priv, pp->index, led->group, brightness);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on,
+ delay_off);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static struct device * __maybe_unused
+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(&priv->ds, pp->index);
+ if (!dp || !dp->user)
+ return NULL;
+ return &dp->user->dev;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+
+ if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags))
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_set(priv, pp->index, led->group, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_led_setup(struct yt921x_priv *priv, int port,
+ struct fwnode_handle *fwnode, u32 *inv_maskp, u32 *inv_ctrlp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+ struct led_init_data init_data;
+ struct led_classdev *led_cdev;
+ char name[LED_MAX_NAME_SIZE];
+ enum led_default_state state;
+ struct yt921x_led *led;
+ bool force_high;
+ bool force_low;
+ int polarity;
+ u32 group;
+ bool on;
+ int res;
+
+ res = fwnode_property_read_u32(fwnode, "reg", &group);
+ if (res)
+ return res;
+ if (group >= YT921X_LED_GROUP_NUM) {
+ dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
+ return -EINVAL;
+ }
+ if (pp->leds[group])
+ return -EEXIST;
+
+ force_high = fwnode_property_read_bool(fwnode, "active-high");
+ force_low = fwnode_property_read_bool(fwnode, "active-low");
+ if (force_high && force_low) {
+ dev_err(dev, "Duplicate polarities for LED %02d:%02u\n",
+ port, group);
+ return -EINVAL;
+ }
+ polarity = force_high ? 1 : force_low ? -1 : 0;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+ led->port = pp;
+ led->group = group;
+
+ state = led_init_default_state_get(fwnode);
+
+ mutex_lock(&priv->reg_lock);
+ pp->leds[group] = led;
+ switch (state) {
+ case LEDS_DEFSTATE_OFF:
+ case LEDS_DEFSTATE_ON:
+ default:
+ on = state == LEDS_DEFSTATE_ON;
+ res = yt921x_led_force_set(priv, port, group, on);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ res = yt921x_led_force_get(priv, port, group, &on);
+ break;
+ }
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ goto err;
+
+ led_cdev = &led->cdev;
+ led_cdev->brightness = on;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking;
+ led_cdev->blink_set = yt921x_cled_blink_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device;
+ led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported;
+ led_cdev->hw_control_get = yt921x_cled_hw_control_get;
+ led_cdev->hw_control_set = yt921x_cled_hw_control_set;
+#endif
+
+ snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u",
+ priv->ds.index, port, group);
+ init_data = (typeof(init_data)){
+ .fwnode = fwnode,
+ .devicename = name,
+ .devname_mandatory = true,
+ };
+ res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
+ if (res)
+ goto err;
+
+ if (polarity) {
+ u32 bit = YT921X_LED_PAR_INV_INVnm(group, port);
+
+ *inv_maskp |= bit;
+ if (polarity > 0)
+ *inv_ctrlp |= bit;
+ }
+ return 0;
+
+err:
+ mutex_lock(&priv->reg_lock);
+ pp->leds[group] = NULL;
+ mutex_unlock(&priv->reg_lock);
+
+ devm_kfree(dev, led);
+ return res;
+}
+
+static void yt921x_leds_remove_port(struct yt921x_priv *priv, int port)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+ bool has_leds = false;
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (led) {
+ devm_led_classdev_unregister(dev, &led->cdev);
+ has_leds = true;
+ }
+ }
+
+ if (!has_leds)
+ return;
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (led) {
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ }
+ }
+}
+
+static int
+yt921x_leds_setup_port(struct yt921x_priv *priv, int port, u32 *inv_maskp,
+ u32 *inv_ctrlp)
+{
+ struct device *dev = to_device(priv);
+ struct dsa_switch *ds = &priv->ds;
+ struct device_node *leds_np;
+ struct dsa_port *dp;
+ int res;
+
+ dp = dsa_to_port(ds, port);
+ leds_np = of_get_child_by_name(dp->dn, "leds");
+ if (!leds_np)
+ return 0;
+
+ if (port >= YT921X_LED_PORT_NUM) {
+ dev_err(dev, "No LEDs for port %d\n", port);
+ of_node_put(leds_np);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node_scoped(leds_np, led_np) {
+ res = yt921x_led_setup(priv, port, of_fwnode_handle(led_np),
+ inv_maskp, inv_ctrlp);
+ if (res) {
+ of_node_put(leds_np);
+ goto err;
+ }
+ }
+
+ of_node_put(leds_np);
+ return 0;
+
+err:
+ yt921x_leds_remove_port(priv, port);
+ return res;
+}
+
+void yt921x_leds_remove(struct yt921x_priv *priv)
+{
+ for (int port = 0; port < YT921X_LED_PORT_NUM; port++)
+ yt921x_leds_remove_port(priv, port);
+}
+
+int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ struct device *dev = to_device(priv);
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
+ u32 inv_ctrl = 0;
+ u32 inv_mask = 0;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ dsa_switch_for_each_port(dp, ds) {
+ int port = dp->index;
+
+ if (!dp->dn)
+ continue;
+
+ res = yt921x_leds_setup_port(priv, port, &inv_mask, &inv_ctrl);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs for port %d: %d\n",
+ port, res);
+ }
+
+ mutex_lock(&priv->reg_lock);
+
+ /* Inversion is internal - force on will give low logic.
+ * In the rest of the file, treat LEDs as if active-low.
+ */
+ if (inv_mask) {
+ res = yt921x_reg_update_bits(priv, YT921X_LED_PAR_INV, inv_mask,
+ inv_ctrl);
+ if (res)
+ goto end;
+ }
+
+ mask = YT921X_LED_CTRL_MODE_M | YT921X_LED_CTRL_PORT_NUM_M |
+ YT921X_LED_CTRL_EN;
+ ctrl = YT921X_LED_CTRL_MODE_PARALLEL |
+ YT921X_LED_CTRL_PORT_NUM(YT921X_PORT_NUM - 1) |
+ YT921X_LED_CTRL_EN;
+ res = yt921x_reg_update_bits(priv, YT921X_LED_CTRL, mask, ctrl);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res) {
+ yt921x_leds_remove(priv);
+ return res;
+ }
+
+ return 0;
+}
diff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h
new file mode 100644
index 00000000000000..67373a190c8f4b
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_LEDS_H
+#define _YT_LEDS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/kconfig.h>
+#include <linux/leds.h>
+
+#define YT921X_LED_CTRL 0xd0000
+#define YT921X_LED_CTRL_EN BIT(21)
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M GENMASK(20, 19) /* cycle = 512 * x ms */
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x) FIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))
+#define YT921X_LED_CTRL_PORT_NUM_M GENMASK(16, 13)
+#define YT921X_LED_CTRL_PORT_NUM(x) FIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))
+#define YT921X_LED_CTRL_MODE_M GENMASK(1, 0)
+#define YT921X_LED_CTRL_MODE(x) FIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))
+#define YT921X_LED_CTRL_MODE_PARALLEL YT921X_LED_CTRL_MODE(0)
+#define YT921X_LED_CTRL_MODE_SERIAL YT921X_LED_CTRL_MODE(2)
+#define YT921X_LED0_PORTn(port) (0xd0004 + 4 * (port))
+#define YT921X_LED0_PORT_ACT_M GENMASK(17, 0)
+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS BIT(17)
+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI BIT(16)
+#define YT921X_LED1_PORTn(port) (0xd0040 + 4 * (port))
+#define YT921X_LED1_PORT_OTHER_BLINK_M GENMASK(31, 30) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_OTHER_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))
+#define YT921X_LED1_PORT_EEE_BLINK_M GENMASK(29, 28) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_EEE_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_COMP BIT(27)
+#define YT921X_LED1_PORT_BLINK_DUTY_M GENMASK(26, 25)
+#define YT921X_LED1_PORT_BLINK_DUTY(x) FIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_1_2 YT921X_LED1_PORT_BLINK_DUTY(0)
+#define YT921X_LED1_PORT_BLINK_DUTY_2_3 YT921X_LED1_PORT_BLINK_DUTY(1)
+#define YT921X_LED1_PORT_BLINK_DUTY_3_4 YT921X_LED1_PORT_BLINK_DUTY(2)
+#define YT921X_LED1_PORT_BLINK_DUTY_5_6 YT921X_LED1_PORT_BLINK_DUTY(3)
+#define YT921X_LED2_PORTn(port) (0xd0080 + 4 * (port))
+#define YT921X_LED2_PORT_FORCEn_M(grp) GENMASK(4 * (grp) + 19, 4 * (grp) + 18)
+#define YT921X_LED2_PORT_FORCEn(grp, x) ((x) << (4 * (grp) + 18))
+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp) YT921X_LED2_PORT_FORCEn(grp, 0)
+#define YT921X_LED2_PORT_FORCEn_BLINK(grp) YT921X_LED2_PORT_FORCEn(grp, 1)
+#define YT921X_LED2_PORT_FORCEn_ON(grp) YT921X_LED2_PORT_FORCEn(grp, 2)
+#define YT921X_LED2_PORT_FORCEn_OFF(grp) YT921X_LED2_PORT_FORCEn(grp, 3)
+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp) GENMASK(4 * (grp) + 17, 4 * (grp) + 16) /* cycle = 512 << x ms */
+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x) ((x) << (4 * (grp) + 16))
+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp) YT921X_LED2_PORT_FORCE_BLINKn(grp, 3)
+#define YT921X_LEDx_PORT_ACT_M GENMASK(15, 0)
+#define YT921X_LEDx_PORT_ACT_EEE_BLINK BIT(15)
+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK BIT(14)
+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK BIT(13)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL BIT(12)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF BIT(11)
+#define YT921X_LEDx_PORT_ACT_TX_BLINK BIT(10)
+#define YT921X_LEDx_PORT_ACT_RX_BLINK BIT(9)
+#define YT921X_LEDx_PORT_ACT_TX BIT(8)
+#define YT921X_LEDx_PORT_ACT_RX BIT(7)
+#define YT921X_LEDx_PORT_ACT_1000M BIT(6)
+#define YT921X_LEDx_PORT_ACT_100M BIT(5)
+#define YT921X_LEDx_PORT_ACT_10M BIT(4)
+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK BIT(3)
+#define YT921X_LEDx_PORT_ACT_1000M_BLINK BIT(2)
+#define YT921X_LEDx_PORT_ACT_100M_BLINK BIT(1)
+#define YT921X_LEDx_PORT_ACT_10M_BLINK BIT(0)
+#define YT921X_LED_SER_CTRL 0xd0100
+#define YT921X_LED_SER_CTRL_UNK GENMASK(25, 24) /* delay? */
+#define YT921X_LED_SER_CTRL_ACTIVE_LOW BIT(4)
+#define YT921X_LED_SER_CTRL_GRP_NUM_M GENMASK(1, 0) /* #grp - 1 */
+#define YT921X_LED_SER_CTRL_GRP_NUM(x) FIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))
+#define YT921X_LED_SER_MAPnm(grp, port) (0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))
+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port) GENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)
+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x) ((x) << (6 * ((port) % 5) + 2))
+#define YT921X_LED_SER_MAP_DSTn_LED_M(port) GENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))
+#define YT921X_LED_SER_MAP_DSTn_LED(port, x) ((x) << (6 * ((port) % 5)))
+#define YT921X_LED_PAR_PORTS 0xd01c4
+#define YT921X_LED_PAR_INV 0xd01c8
+#define YT921X_LED_PAR_INV_INVnm(grp, port) BIT(10 * (grp) + (port))
+#define YT921X_LED_PAR_MAPn(port) (0xd01d0 + 4 * (port))
+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp) GENMASK(6 * (grp) + 5, 6 * (grp) + 2)
+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x) ((x) << (6 * (grp) + 2))
+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp) GENMASK(6 * (grp) + 1, 6 * (grp))
+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x) ((x) << (6 * (grp)))
+
+#define YT921X_LED_BLINK_MIN 64
+#define YT921X_LED_BLINK_DEF 512
+#define YT921X_LED_BLINK_MAX 2048
+
+/* 2 * lcm(2, 3, 4, 6) */
+#define YT921X_LED_DUTY_DENOM 24
+#define YT921X_LED_DUTY(nom, denom) (YT921X_LED_DUTY_DENOM * (nom) / (denom))
+
+struct yt921x_priv;
+
+struct yt921x_led {
+ struct led_classdev cdev;
+ struct yt921x_port *port;
+ unsigned char group;
+
+ bool use_cycle:1;
+ bool use_duty:1;
+};
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+
+void yt921x_leds_remove(struct yt921x_priv *priv);
+int yt921x_leds_setup(struct yt921x_priv *priv);
+
+#else
+
+static inline void yt921x_leds_remove(struct yt921x_priv *priv) {}
+
+static inline int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ return 0;
+}
+
+#endif
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
new file mode 100644
index 00000000000000..9054896e4cd12b
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/iopoll.h>
+
+#include "chip.h"
+#include "smi.h"
+
+#define YT921X_POLL_SLEEP_US 10000
+#define YT921X_POLL_TIMEOUT_US 100000
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->read(priv->reg_ctx, reg, valp);
+}
+
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->write(priv->reg_ctx, reg, val);
+}
+
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
+{
+ u32 val;
+ int res;
+ int ret;
+
+ ret = read_poll_timeout(yt921x_reg_read, res,
+ res || (val & mask) == *valp,
+ YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
+ false, priv, reg, &val);
+ if (ret)
+ return ret;
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+ int res;
+ u32 v;
+ u32 u;
+
+ res = yt921x_reg_read(priv, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ return yt921x_reg_write(priv, reg, u);
+}
+
+int
+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ u |= vals[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+int
+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
new file mode 100644
index 00000000000000..2e956065eb90ee
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_SMI_H
+#define _YT_SMI_H
+
+#include <linux/types.h>
+#include <linux/wordpart.h>
+
+struct yt921x_priv;
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,
+ u32 val);
+
+static inline int
+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, 0, mask);
+}
+
+static inline int
+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, 0);
+}
+
+static inline int
+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
+ * long register. More specifically, writes to parts of its words won't become
+ * visible, until the last word is written.
+ *
+ * Here we require full read and write operations over these registers to
+ * eliminate potential issues, although partial reads/writes are also possible.
+ */
+
+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+ *lo &= ~lower_32_bits(mask);
+ *hi &= ~upper_32_bits(mask);
+ *lo |= lower_32_bits(val);
+ *hi |= upper_32_bits(val);
+}
+
+int yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs);
+int yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs);
+
+static inline int
+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 2);
+}
+
+static inline int
+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals)
+{
+ return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
+}
+
+static inline int
+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
+{
+ return yt921x_regs_clear_bits(priv, reg, masks, 2);
+}
+
+static inline int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 3);
+}
+
+#endif
|
| 3/2 | 2026/07/27 06:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21136 output: 24 thoughts: 512 Thoughts: **WorthFuzzing Assessment of Motorcomm YT9215 LED Support** Okay, so we have a patch that introduces LED support for the Motorcomm YT9215 ethernet switch chip, moving the driver file and adding a new config option. My initial thought is, can this even be reached in a virtualized environment? DSA switches are typically connected via MDIO, SPI, or I2C, and typically require the physical hardware to properly respond to MDIO reads/writes. Let's dig into the driver code itself (`drivers/net/dsa/motorcomm/chip.c`) to see how it's being registered, but generally, DSA drivers are meant for *physical* hardware. I know some DSA drivers can be instantiated with device tree overlays or dummy MDIO buses, but that's not the norm. The question then is, is there any QEMU emulation for this specific Motorcomm chip? Probably not. Can we fake it with a dummy MDIO bus? Maybe, but syzkaller doesn't have a generic mechanism for fuzzing arbitrary MDIO devices unless they are specifically supported, or we have a way to inject MDIO traffic directly. Now, let's step back and consider the prompt. It's asking if the patch is *worth* fuzzing. The prompt explicitly says to return `WorthFuzzing=false` for vendor-specific drivers that require *physical* PCIe hardware, which this isn't, but what about MDIO, I2C, or SPI devices? The usual case is that if this is a hardware driver for a specific chip, especially a DSA switch, and it isn't emulated, it's probably not reachable. So let's check: Is `yt921x` already fuzzed by syzkaller? I can't use `grepper` to search the `sys/` directory. Let me explore the code and see if there is any way to reach `yt921x_leds_setup` or `yt921x_cled_brightness_set_blocking`. The driver *is* registered as an MDIO device. This all points towards a low probability of being directly reachable. |
| 4/2 | 2026/07/27 06:14 | tool | grepper |
0mArgs:{
"Expression": "yt921x_mdio_driver"
}Results: {
"Output": "drivers/net/dsa/motorcomm/chip.c=4770=MODULE_DEVICE_TABLE(of, yt921x_of_match);\ndrivers/net/dsa/motorcomm/chip.c-4771-\ndrivers/net/dsa/motorcomm/chip.c:4772:static struct mdio_driver yt921x_mdio_driver = {\ndrivers/net/dsa/motorcomm/chip.c-4773-\t.probe = yt921x_mdio_probe,\n--\ndrivers/net/dsa/motorcomm/chip.c-4781-\ndrivers/net/dsa/motorcomm/chip.c:4782:mdio_module_driver(yt921x_mdio_driver);\ndrivers/net/dsa/motorcomm/chip.c-4783-\n"
} |
| 5/2 | 2026/07/27 06:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22359 output: 24 thoughts: 116 Thoughts: **Analyzing the MDIO Driver** Okay, so I'm looking at this MDIO driver, specifically the `mdio_module_driver(yt921x_mdio_driver);` line. Right, that's the standard way to register an MDIO driver. My initial thought is the usual setup – the MDIO devices, like this `yt921x` one, are generally probed in a couple of ways: either the device tree defines them, and the MDIO bus automatically scans, or there's an ID match. That makes sense, pretty standard stuff. But here's the kicker, and I need to be careful. I don't have the actual hardware or an emulator to play with right now. This means that the probe function, the `yt921x_mdio_probe` function is going to fail. I can all but guarantee that. Why? Because these things *always* check for the physical presence of the chip, usually by reading some sort of chip ID register. No chip, no ID, no dice. So, where does that leave me? I need to dig into `yt921x_mdio_probe` itself. That's the next logical step. I need to see *exactly* how it's checking for the device, what registers it's reading, and how it's determining success or failure. This will tell me what assumptions it's making about the hardware. |
| 6/2 | 2026/07/27 06:14 | tool | grepper |
0mArgs:{
"Expression": "yt921x_mdio_probe"
}Results: {
"Output": "drivers/net/dsa/motorcomm/chip.c=4682=static void yt921x_mdio_remove(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/motorcomm/chip.c-4717-\ndrivers/net/dsa/motorcomm/chip.c:4718:static int yt921x_mdio_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/motorcomm/chip.c-4719-{\n--\ndrivers/net/dsa/motorcomm/chip.c=4772=static struct mdio_driver yt921x_mdio_driver = {\ndrivers/net/dsa/motorcomm/chip.c:4773:\t.probe = yt921x_mdio_probe,\ndrivers/net/dsa/motorcomm/chip.c-4774-\t.remove = yt921x_mdio_remove,\n"
} |
| 7/2 | 2026/07/27 06:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23058 output: 42 thoughts: 16 Thoughts: Okay, let's dive into `yt921x_mdio_probe`. I'm going to approach this with the assumption that I understand the general principles of MDIO and driver probing, so I'll be looking for specifics related to the YT921x chipset and anything unusual or noteworthy in its implementation. I'll be paying close attention to: **Initial Assessment: yt921x_mdio_probe** * **Entry Point and Purpose:** First, I need to confirm this is indeed the probe function for the MDIO interface. I expect to see it as part of the driver's registration. I need to understand what triggered the probe; likely, the system detected a device matching a known ID, and now this function is being called. * **Resource Allocation:** I'll be looking at what resources are allocated, especially memory for data structures. This includes any device-specific information needed for the MDIO communication or PHY management. Are there any unusual memory mapping strategies being used? Are there potential resource conflicts to look out for? * **MDIO Initialization:** The critical part. How is the MDIO interface initialized? Are there specific sequences, register writes, or clock configurations necessary for the YT921x? I'm expecting standard MDIO initialization steps, potentially with a vendor-specific twist. I'll need to understand how the driver interacts with the underlying hardware MDIO controller. * **PHY Detection and Configuration:** Is there PHY detection involved here, or is it assumed a specific PHY is connected? How does it handle various PHY types? I'll need to figure out how it sets up and configures the PHY, including any power-up or reset sequences. What about the MDIO bus speed, are there any speed adjustments? * **Error Handling and Robustness:** How robust is the error handling? What does it do if the device isn't responding, if there are MDIO errors, or if the PHY configuration fails? Are there retry mechanisms? Any potential race conditions? I'll look for `printk` calls and other debugging statements to understand the error reporting. * **Driver Model Integration:** How does this function integrate into the broader kernel driver model? Are there bindings to other parts of the system, like network interfaces or the device tree? I'll check for the use of the device structure, device private data, and of course any network initialization and registration. * **Power Management:** How does it handle power management? Are there any power-up or power-down sequences or callbacks defined? * **Concurrency:** Are there any concurrency issues I need to be concerned with? Are there any locks being used to protect shared data or hardware registers? Essentially, I want to understand how this driver brings up the MDIO interface and PHY. This includes how it manages hardware resources, detects and configures the PHY, and handles potential errors in the process. The core of my interest is how this driver brings up and configures the YT921x MDIO interface. I will carefully review the code for any subtle areas, that can lead to subtle bugs. |
| 8/2 | 2026/07/27 06:14 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4718,
"LineCount": 50
}Results: {
"Contents": "4718:\tstatic int yt921x_mdio_probe(struct mdio_device *mdiodev)\n4719:\t{\n4720:\t\tstruct device *dev = \u0026mdiodev-\u003edev;\n4721:\t\tstruct yt921x_reg_mdio *mdio;\n4722:\t\tstruct yt921x_priv *priv;\n4723:\t\tstruct dsa_switch *ds;\n4724:\t\n4725:\t\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n4726:\t\tif (!priv)\n4727:\t\t\treturn -ENOMEM;\n4728:\t\n4729:\t\tmdio = devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL);\n4730:\t\tif (!mdio)\n4731:\t\t\treturn -ENOMEM;\n4732:\t\n4733:\t\tmdio-\u003ebus = mdiodev-\u003ebus;\n4734:\t\tmdio-\u003eaddr = mdiodev-\u003eaddr;\n4735:\t\tmdio-\u003eswitchid = 0;\n4736:\t\n4737:\t\tmutex_init(\u0026priv-\u003ereg_lock);\n4738:\t\n4739:\t\tpriv-\u003ereg_ops = \u0026yt921x_reg_ops_mdio;\n4740:\t\tpriv-\u003ereg_ctx = mdio;\n4741:\t\n4742:\t\tfor (size_t i = 0; i \u003c ARRAY_SIZE(priv-\u003eports); i++) {\n4743:\t\t\tstruct yt921x_port *pp = \u0026priv-\u003eports[i];\n4744:\t\n4745:\t\t\tpp-\u003eindex = i;\n4746:\t\t\tINIT_DELAYED_WORK(\u0026pp-\u003emib_read, yt921x_poll_mib);\n4747:\t\t}\n4748:\t\n4749:\t\tds = \u0026priv-\u003eds;\n4750:\t\tds-\u003edev = dev;\n4751:\t\tds-\u003eassisted_learning_on_cpu_port = true;\n4752:\t\tds-\u003edscp_prio_mapping_is_global = true;\n4753:\t\tds-\u003epriv = priv;\n4754:\t\tds-\u003eops = \u0026yt921x_dsa_switch_ops;\n4755:\t\tds-\u003eageing_time_min = 1 * 5000;\n4756:\t\tds-\u003eageing_time_max = U16_MAX * 5000;\n4757:\t\tds-\u003ephylink_mac_ops = \u0026yt921x_phylink_mac_ops;\n4758:\t\tds-\u003enum_lag_ids = YT921X_LAG_NUM;\n4759:\t\tds-\u003enum_ports = YT921X_PORT_NUM;\n4760:\t\n4761:\t\tmdiodev_set_drvdata(mdiodev, priv);\n4762:\t\n4763:\t\treturn dsa_register_switch(ds);\n4764:\t}\n4765:\t\n4766:\tstatic const struct of_device_id yt921x_of_match[] = {\n4767:\t\t{ .compatible = \"motorcomm,yt9215\" },\n"
} |
| 9/2 | 2026/07/27 06:14 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24630 output: 26 thoughts: 84 Thoughts: **Analyzing the DSA Switch Probe** Okay, hold on a second. This `yt921x_mdio_probe` function…it seems all it's doing is calling `dsa_register_switch(ds)`. That's it? I was expecting it to handle the initial chip ID read or something, but no, the probe itself is remarkably thin. This is interesting. So, if it's not happening here, where *is* the initialization? `dsa_register_switch` is the key. That function has to be doing the heavy lifting. And that means the probe function is effectively delegating the real work to the `setup` function within the switch operations structure, right? It will eventually call that function, and *that's* where the actual hardware interaction and ID retrieval probably happen. Right, so I need to check the `yt921x_dsa_switch_ops` structure now. That's where the magic should be. I need to understand what its `setup` function is doing. Let's see... |
| 10/2 | 2026/07/27 06:15 | tool | grepper |
0mArgs:{
"Expression": "yt921x_dsa_switch_ops"
}Results: {
"Output": "drivers/net/dsa/motorcomm/chip.c=4592=static const struct phylink_mac_ops yt921x_phylink_mac_ops = {\n--\ndrivers/net/dsa/motorcomm/chip.c-4597-\ndrivers/net/dsa/motorcomm/chip.c:4598:static const struct dsa_switch_ops yt921x_dsa_switch_ops = {\ndrivers/net/dsa/motorcomm/chip.c-4599-\t/* mib */\n--\ndrivers/net/dsa/motorcomm/chip.c=4718=static int yt921x_mdio_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/motorcomm/chip.c-4753-\tds-\u003epriv = priv;\ndrivers/net/dsa/motorcomm/chip.c:4754:\tds-\u003eops = \u0026yt921x_dsa_switch_ops;\ndrivers/net/dsa/motorcomm/chip.c-4755-\tds-\u003eageing_time_min = 1 * 5000;\n"
} |
| 11/2 | 2026/07/27 06:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25265 output: 42 thoughts: 18 Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own: **Analyzing the DSA Switch Operations Module** Alright, time to get to work. First up, I need to familiarize myself with `yt921x_dsa_switch_ops`. This suggests we're dealing with the operations for a DSA (Distributed Switch Architecture) switch, specifically related to the yt921x chipset. My initial thought is to quickly ascertain: what are the core functions this module provides? I'll be looking for things like initialization routines, port configuration functions, VLAN manipulation capabilities, and any special features specific to the yt921x hardware. I need to understand how the driver interacts with the underlying hardware. Are there any particular quirks or optimizations this module employs? I'll probably start by looking at function prototypes, comments, and any obvious data structures to get a high-level overview before I begin a deeper dive into the control flow. The goal is to establish a strong mental model of how this module functions within the larger networking stack. |
| 12/2 | 2026/07/27 06:15 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4598,
"LineCount": 50
}Results: {
"Contents": "4598:\tstatic const struct dsa_switch_ops yt921x_dsa_switch_ops = {\n4599:\t\t/* mib */\n4600:\t\t.get_strings\t\t= yt921x_dsa_get_strings,\n4601:\t\t.get_ethtool_stats\t= yt921x_dsa_get_ethtool_stats,\n4602:\t\t.get_sset_count\t\t= yt921x_dsa_get_sset_count,\n4603:\t\t.get_eth_mac_stats\t= yt921x_dsa_get_eth_mac_stats,\n4604:\t\t.get_eth_ctrl_stats\t= yt921x_dsa_get_eth_ctrl_stats,\n4605:\t\t.get_rmon_stats\t\t= yt921x_dsa_get_rmon_stats,\n4606:\t\t.get_stats64\t\t= yt921x_dsa_get_stats64,\n4607:\t\t.get_pause_stats\t= yt921x_dsa_get_pause_stats,\n4608:\t\t/* eee */\n4609:\t\t.support_eee\t\t= dsa_supports_eee,\n4610:\t\t.set_mac_eee\t\t= yt921x_dsa_set_mac_eee,\n4611:\t\t/* mtu */\n4612:\t\t.port_change_mtu\t= yt921x_dsa_port_change_mtu,\n4613:\t\t.port_max_mtu\t\t= yt921x_dsa_port_max_mtu,\n4614:\t\t/* rate */\n4615:\t\t.port_policer_del\t= yt921x_dsa_port_policer_del,\n4616:\t\t.port_policer_add\t= yt921x_dsa_port_policer_add,\n4617:\t\t.port_setup_tc\t\t= yt921x_dsa_port_setup_tc,\n4618:\t\t/* acl */\n4619:\t\t.cls_flower_del\t\t= yt921x_dsa_cls_flower_del,\n4620:\t\t.cls_flower_add\t\t= yt921x_dsa_cls_flower_add,\n4621:\t\t/* hsr */\n4622:\t\t.port_hsr_leave\t\t= dsa_port_simple_hsr_leave,\n4623:\t\t.port_hsr_join\t\t= dsa_port_simple_hsr_join,\n4624:\t\t/* mirror */\n4625:\t\t.port_mirror_del\t= yt921x_dsa_port_mirror_del,\n4626:\t\t.port_mirror_add\t= yt921x_dsa_port_mirror_add,\n4627:\t\t/* lag */\n4628:\t\t.port_lag_leave\t\t= yt921x_dsa_port_lag_leave,\n4629:\t\t.port_lag_join\t\t= yt921x_dsa_port_lag_join,\n4630:\t\t/* fdb */\n4631:\t\t.port_fdb_dump\t\t= yt921x_dsa_port_fdb_dump,\n4632:\t\t.port_fast_age\t\t= yt921x_dsa_port_fast_age,\n4633:\t\t.set_ageing_time\t= yt921x_dsa_set_ageing_time,\n4634:\t\t.port_fdb_del\t\t= yt921x_dsa_port_fdb_del,\n4635:\t\t.port_fdb_add\t\t= yt921x_dsa_port_fdb_add,\n4636:\t\t.port_mdb_del\t\t= yt921x_dsa_port_mdb_del,\n4637:\t\t.port_mdb_add\t\t= yt921x_dsa_port_mdb_add,\n4638:\t\t/* vlan */\n4639:\t\t.port_vlan_filtering\t= yt921x_dsa_port_vlan_filtering,\n4640:\t\t.port_vlan_del\t\t= yt921x_dsa_port_vlan_del,\n4641:\t\t.port_vlan_add\t\t= yt921x_dsa_port_vlan_add,\n4642:\t\t/* bridge */\n4643:\t\t.port_pre_bridge_flags\t= yt921x_dsa_port_pre_bridge_flags,\n4644:\t\t.port_bridge_flags\t= yt921x_dsa_port_bridge_flags,\n4645:\t\t.port_bridge_leave\t= yt921x_dsa_port_bridge_leave,\n4646:\t\t.port_bridge_join\t= yt921x_dsa_port_bridge_join,\n4647:\t\t/* mst */\n"
} |
| 13/2 | 2026/07/27 06:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26852 output: 42 thoughts: 8 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience: **The Missing Setup: A Brief Moment of Disorientation** Alright, deep in this workflow, I'm thinking, I’m getting ready to execute the next logical step and… hold on. Something feels off. My mental model is expecting a specific function, a critical initialization routine, that I've dubbed `setup`, to be present. It's the lynchpin, the foundation upon which the subsequent operations depend. I can *feel* its absence; the architecture in my head is slightly jarred. The elegance of the planned flow is momentarily disrupted. Where the hell did `setup` go? It's like reaching for a crucial tool that's suddenly vanished from the workbench. Now I have to backtrack and figure out what happened to my `setup`. |
| 14/2 | 2026/07/27 06:16 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4648,
"LineCount": 50
}Results: {
"Contents": "4648:\t\t.port_mst_state_set\t= yt921x_dsa_port_mst_state_set,\n4649:\t\t.vlan_msti_set\t\t= yt921x_dsa_vlan_msti_set,\n4650:\t\t.port_stp_state_set\t= yt921x_dsa_port_stp_state_set,\n4651:\t#if IS_ENABLED(CONFIG_DCB)\n4652:\t\t/* dcb */\n4653:\t\t.port_get_default_prio\t= yt921x_dsa_port_get_default_prio,\n4654:\t\t.port_set_default_prio\t= yt921x_dsa_port_set_default_prio,\n4655:\t\t.port_get_apptrust\t= yt921x_dsa_port_get_apptrust,\n4656:\t\t.port_set_apptrust\t= yt921x_dsa_port_set_apptrust,\n4657:\t#endif\n4658:\t\t/* port */\n4659:\t\t.get_tag_protocol\t= yt921x_dsa_get_tag_protocol,\n4660:\t\t.phylink_get_caps\t= yt921x_dsa_phylink_get_caps,\n4661:\t\t.port_setup\t\t= yt921x_dsa_port_setup,\n4662:\t#if IS_ENABLED(CONFIG_DCB)\n4663:\t\t/* dscp */\n4664:\t\t.port_get_dscp_prio\t= yt921x_dsa_port_get_dscp_prio,\n4665:\t\t.port_del_dscp_prio\t= yt921x_dsa_port_del_dscp_prio,\n4666:\t\t.port_add_dscp_prio\t= yt921x_dsa_port_add_dscp_prio,\n4667:\t#endif\n4668:\t\t/* chip */\n4669:\t\t.setup\t\t\t= yt921x_dsa_setup,\n4670:\t};\n4671:\t\n4672:\tstatic void yt921x_mdio_shutdown(struct mdio_device *mdiodev)\n4673:\t{\n4674:\t\tstruct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);\n4675:\t\n4676:\t\tif (!priv)\n4677:\t\t\treturn;\n4678:\t\n4679:\t\tdsa_switch_shutdown(\u0026priv-\u003eds);\n4680:\t}\n4681:\t\n4682:\tstatic void yt921x_mdio_remove(struct mdio_device *mdiodev)\n4683:\t{\n4684:\t\tstruct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);\n4685:\t\n4686:\t\tif (!priv)\n4687:\t\t\treturn;\n4688:\t\n4689:\t#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n4690:\t\tyt921x_leds_remove(priv);\n4691:\t#endif\n4692:\t\n4693:\t\tfor (size_t i = ARRAY_SIZE(priv-\u003eports); i-- \u003e 0; ) {\n4694:\t\t\tstruct yt921x_port *pp = \u0026priv-\u003eports[i];\n4695:\t\n4696:\t\t\tdisable_delayed_work_sync(\u0026pp-\u003emib_read);\n4697:\t\t}\n"
} |
| 15/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28062 output: 24 thoughts: 34 Thoughts: Here's my take on the thought process: **Diving into the DSA Setup** Alright, so `yt921x_dsa_setup` is the function we're starting with. Time to dissect it. Let's see what this `yt921x_dsa_setup` function actually *does*. I need to understand its role in the overall DSA implementation. It's the entry point, the initialization - I'll need to go through it carefully. Let's pull up the code and get to it. |
| 16/2 | 2026/07/27 06:16 | tool | grepper |
0mArgs:{
"Expression": "yt921x_dsa_setup"
}Results: {
"Output": "drivers/net/dsa/motorcomm/chip.c=4495=static int yt921x_chip_setup(struct yt921x_priv *priv)\n--\ndrivers/net/dsa/motorcomm/chip.c-4536-\ndrivers/net/dsa/motorcomm/chip.c:4537:static int yt921x_dsa_setup(struct dsa_switch *ds)\ndrivers/net/dsa/motorcomm/chip.c-4538-{\n--\ndrivers/net/dsa/motorcomm/chip.c=4598=static const struct dsa_switch_ops yt921x_dsa_switch_ops = {\n--\ndrivers/net/dsa/motorcomm/chip.c-4668-\t/* chip */\ndrivers/net/dsa/motorcomm/chip.c:4669:\t.setup\t\t\t= yt921x_dsa_setup,\ndrivers/net/dsa/motorcomm/chip.c-4670-};\n"
} |
| 17/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28503 output: 42 thoughts: 16 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Initial Assessment: Navigating the Setup** Alright, so the first step is clear: I'm diving into `yt921x_dsa_setup`. The naming convention strongly suggests this is a script or configuration file related to the Digital Signal Analyzer (DSA), specifically the yt921x. Given my experience, I'm already anticipating certain elements: hardware initialization routines, potentially some register settings, calibrations, and possibly communication protocol setup. I'll need to rapidly identify key sections like: driver-specific sections, error handling, default configurations, and any user-configurable parameters. Understanding the structure of this setup is crucial for any modifications or troubleshooting I might need to perform down the line. It will be important to quickly understand if this is part of a larger firmware or application setup. |
| 18/2 | 2026/07/27 06:16 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4537,
"LineCount": 50
}Results: {
"Contents": "4537:\tstatic int yt921x_dsa_setup(struct dsa_switch *ds)\n4538:\t{\n4539:\t\tstruct yt921x_priv *priv = to_yt921x_priv(ds);\n4540:\t\tstruct device *dev = to_device(priv);\n4541:\t\tstruct device_node *np = dev-\u003eof_node;\n4542:\t\tstruct device_node *child;\n4543:\t\tint res;\n4544:\t\n4545:\t\tmutex_lock(\u0026priv-\u003ereg_lock);\n4546:\t\tres = yt921x_chip_reset(priv);\n4547:\t\tmutex_unlock(\u0026priv-\u003ereg_lock);\n4548:\t\n4549:\t\tif (res)\n4550:\t\t\treturn res;\n4551:\t\n4552:\t\t/* Register the internal mdio bus. Nodes for internal ports should have\n4553:\t\t * proper phy-handle pointing to their PHYs. Not enabling the internal\n4554:\t\t * bus is possible, though pretty wired, if internal ports are not used.\n4555:\t\t */\n4556:\t\tchild = of_get_child_by_name(np, \"mdio\");\n4557:\t\tif (child) {\n4558:\t\t\tres = yt921x_mbus_int_init(priv, child);\n4559:\t\t\tof_node_put(child);\n4560:\t\t\tif (res)\n4561:\t\t\t\treturn res;\n4562:\t\t}\n4563:\t\n4564:\t\t/* External mdio bus is optional */\n4565:\t\tchild = of_get_child_by_name(np, \"mdio-external\");\n4566:\t\tif (child) {\n4567:\t\t\tres = yt921x_mbus_ext_init(priv, child);\n4568:\t\t\tof_node_put(child);\n4569:\t\t\tif (res)\n4570:\t\t\t\treturn res;\n4571:\t\n4572:\t\t\tdev_err(dev, \"Untested external mdio bus\\n\");\n4573:\t\t\treturn -ENODEV;\n4574:\t\t}\n4575:\t\n4576:\t\tmutex_lock(\u0026priv-\u003ereg_lock);\n4577:\t\tres = yt921x_chip_setup(priv);\n4578:\t\tmutex_unlock(\u0026priv-\u003ereg_lock);\n4579:\t\n4580:\t\tif (res)\n4581:\t\t\treturn res;\n4582:\t\n4583:\t#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)\n4584:\t\tres = yt921x_leds_setup(priv);\n4585:\t\tif (res)\n4586:\t\t\tdev_warn(dev, \"Failed to setup LEDs: %d\\n\", res);\n"
} |
| 19/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29529 output: 23 thoughts: 294 Thoughts: **Analyzing the `yt921x_chip_reset` Function** Okay, so `yt921x_chip_reset` likely involves register reads and writes. Seems straightforward enough. The immediate concern, though, is how we can even hit this code path, especially in an environment where the hardware isn't directly available. My initial instinct is that if it's an MDIO device, and we *don't* have the hardware, the function will just fail. But, wait... could syzkaller somehow inject MDIO traffic? No, it doesn't have a *generic* MDIO fuzzer. It's more focused on the core kernel components. Alright, what about device tree fuzzing? Syzkaller's certainly got that capability, but the device tree parsing for this device seems to be handled in `yt921x_dsa_setup` and `yt921x_leds_setup`, right? If the device doesn't probe, we're stuck. So, the question becomes, can we simulate a probe in a virtualized setting? The lightbulb clicks! If we add a device tree node for a `motorcomm,yt9215` device on a dummy MDIO bus, something like `fixed-mdio` or `mdio-mockup`, the `yt921x_mdio_probe` function should be called. Following that, `dsa_register_switch` will get called, which *then* calls `yt921x_dsa_setup`. And *then*, finally, the target: `yt921x_chip_reset`. Looks like we've got a plan. Time to go check out `yt921x_chip_reset` itself. |
| 20/2 | 2026/07/27 06:16 | tool | grepper |
0mArgs:{
"Expression": "yt921x_chip_reset"
}Results: {
"Output": "drivers/net/dsa/motorcomm/chip.c=4227=static int yt921x_chip_detect(struct yt921x_priv *priv)\n--\ndrivers/net/dsa/motorcomm/chip.c-4293-\ndrivers/net/dsa/motorcomm/chip.c:4294:static int yt921x_chip_reset(struct yt921x_priv *priv)\ndrivers/net/dsa/motorcomm/chip.c-4295-{\n--\ndrivers/net/dsa/motorcomm/chip.c=4537=static int yt921x_dsa_setup(struct dsa_switch *ds)\n--\ndrivers/net/dsa/motorcomm/chip.c-4545-\tmutex_lock(\u0026priv-\u003ereg_lock);\ndrivers/net/dsa/motorcomm/chip.c:4546:\tres = yt921x_chip_reset(priv);\ndrivers/net/dsa/motorcomm/chip.c-4547-\tmutex_unlock(\u0026priv-\u003ereg_lock);\n"
} |
| 21/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30509 output: 42 thoughts: 15 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Initial Assessment: Examining a Chip Reset Sequence** Right, let's dive into this. "yt921x_chip_reset"... my immediate thought is that this likely refers to a reset sequence, likely for a specific System-on-Chip (SoC) or a dedicated component labeled "yt921x". Given my experience, I'm expecting this is probably a configuration file, a script, or maybe even firmware related to the device's boot process. The "reset" implication strongly suggests a low-level operation affecting hardware state. I need to understand *how* this reset is implemented, and *why*. What triggers it? What are the consequences? I'll be looking for clues on power-up states, clock configurations, interrupt handling, and maybe even some kind of hardware watchdog involvement. The goal is to fully grasp the chip's power-on behavior, potential recovery mechanisms, and any specific error-handling related to reset scenarios. |
| 22/2 | 2026/07/27 06:16 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4294,
"LineCount": 50
}Results: {
"Contents": "4294:\tstatic int yt921x_chip_reset(struct yt921x_priv *priv)\n4295:\t{\n4296:\t\tstruct device *dev = to_device(priv);\n4297:\t\tu16 eth_p_tag;\n4298:\t\tu32 val;\n4299:\t\tint res;\n4300:\t\n4301:\t\tres = yt921x_chip_detect(priv);\n4302:\t\tif (res)\n4303:\t\t\treturn res;\n4304:\t\n4305:\t\t/* Reset */\n4306:\t\tres = yt921x_reg_write(priv, YT921X_RST, YT921X_RST_HW);\n4307:\t\tif (res)\n4308:\t\t\treturn res;\n4309:\t\n4310:\t\t/* RST_HW is almost same as GPIO hard reset, so we need this delay. */\n4311:\t\tfsleep(YT921X_RST_DELAY_US);\n4312:\t\n4313:\t\tval = 0;\n4314:\t\tres = yt921x_reg_wait(priv, YT921X_RST, ~0, \u0026val);\n4315:\t\tif (res)\n4316:\t\t\treturn res;\n4317:\t\n4318:\t\t/* Check for tag EtherType; do it after reset in case you messed it up\n4319:\t\t * before.\n4320:\t\t */\n4321:\t\tres = yt921x_reg_read(priv, YT921X_CPU_TAG_TPID, \u0026val);\n4322:\t\tif (res)\n4323:\t\t\treturn res;\n4324:\t\teth_p_tag = FIELD_GET(YT921X_CPU_TAG_TPID_TPID_M, val);\n4325:\t\tif (eth_p_tag != ETH_P_YT921X) {\n4326:\t\t\tdev_err(dev, \"Tag type 0x%x != 0x%x\\n\", eth_p_tag,\n4327:\t\t\t\tETH_P_YT921X);\n4328:\t\t\t/* Despite being possible, we choose not to set CPU_TAG_TPID,\n4329:\t\t\t * since there is no way it can be different unless you have the\n4330:\t\t\t * wrong chip.\n4331:\t\t\t */\n4332:\t\t\treturn -EINVAL;\n4333:\t\t}\n4334:\t\n4335:\t\treturn 0;\n4336:\t}\n4337:\t\n4338:\tstatic int yt921x_chip_setup_dsa(struct yt921x_priv *priv)\n4339:\t{\n4340:\t\tstruct dsa_switch *ds = \u0026priv-\u003eds;\n4341:\t\tunsigned long cpu_ports_mask;\n4342:\t\tu32 ctrls[2];\n4343:\t\tu32 ctrl;\n"
} |
| 23/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31603 output: 42 thoughts: 21 Thoughts: Here's a summary of my thought process: **Diving into Chip Detection** Okay, the first thing on the agenda is `yt921x_chip_detect`. I need to get a handle on what this function is doing. So, I'll start by reading its code and figuring out the core logic. This is my entry point for understanding the system's interaction with the `yt921x` chip. I'm looking for the essential steps involved in its detection routine: how it identifies the chip, what methods it uses, and what preliminary checks are performed. I want to build a solid mental model of this initial chip handshake. |
| 24/2 | 2026/07/27 06:16 | tool | read-file |
0mArgs:{
"File": "drivers/net/dsa/motorcomm/chip.c",
"FirstLine": 4227,
"LineCount": 50
}Results: {
"Contents": "4227:\tstatic int yt921x_chip_detect(struct yt921x_priv *priv)\n4228:\t{\n4229:\t\tstruct device *dev = to_device(priv);\n4230:\t\tconst struct yt921x_info *info;\n4231:\t\tu8 extmode;\n4232:\t\tu32 chipid;\n4233:\t\tu32 major;\n4234:\t\tu32 mode;\n4235:\t\tu32 val;\n4236:\t\tint res;\n4237:\t\n4238:\t\tres = yt921x_reg_read(priv, YT921X_CHIP_ID, \u0026chipid);\n4239:\t\tif (res)\n4240:\t\t\treturn res;\n4241:\t\n4242:\t\tmajor = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);\n4243:\t\n4244:\t\tfor (info = yt921x_infos; info-\u003ename; info++)\n4245:\t\t\tif (info-\u003emajor == major)\n4246:\t\t\t\tbreak;\n4247:\t\tif (!info-\u003ename) {\n4248:\t\t\tdev_err(dev, \"Unexpected chipid 0x%x\\n\", chipid);\n4249:\t\t\treturn -ENODEV;\n4250:\t\t}\n4251:\t\n4252:\t\tres = yt921x_reg_read(priv, YT921X_CHIP_MODE, \u0026mode);\n4253:\t\tif (res)\n4254:\t\t\treturn res;\n4255:\t\tres = yt921x_edata_read(priv, YT921X_EDATA_EXTMODE, \u0026extmode);\n4256:\t\tif (res)\n4257:\t\t\treturn res;\n4258:\t\n4259:\t\tfor (; info-\u003ename; info++)\n4260:\t\t\tif (info-\u003emajor == major \u0026\u0026 info-\u003emode == mode \u0026\u0026\n4261:\t\t\t info-\u003eextmode == extmode)\n4262:\t\t\t\tbreak;\n4263:\t\tif (!info-\u003ename) {\n4264:\t\t\tdev_err(dev,\n4265:\t\t\t\t\"Unsupported chipid 0x%x with chipmode 0x%x 0x%x\\n\",\n4266:\t\t\t\tchipid, mode, extmode);\n4267:\t\t\treturn -ENODEV;\n4268:\t\t}\n4269:\t\n4270:\t\tres = yt921x_reg_read(priv, YT921X_SYS_CLK, \u0026val);\n4271:\t\tif (res)\n4272:\t\t\treturn res;\n4273:\t\tswitch (FIELD_GET(YT921X_SYS_CLK_SEL_M, val)) {\n4274:\t\tcase 0:\n4275:\t\t\tpriv-\u003ecycle_ns = info-\u003emajor == YT9215_MAJOR ? 8 : 6;\n4276:\t\t\tbreak;\n"
} |
| 25/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32611 output: 22 thoughts: 287 Thoughts: **Analyzing the Fuzzing Potential** Okay, so the `yt921x_chip_detect` function tries to read the `YT921X_CHIP_ID` via MDIO. If it fails or gets the wrong ID, it immediately errors out with `-ENODEV`. This is a hardware driver for the Motorcomm YT9215 chip, which means the probe function is going to fail if the right hardware isn't present. My initial thought is, can syzkaller even mock these MDIO reads? I don't think so, and a quick check confirms that syzkaller doesn't have a generic MDIO mocking framework. So, the question becomes, is there *any* other way this code path could be hit? I see the patch adds LED support, but the core of the driver is still tied to that specific hardware. Looking back at the prompt's criteria: "Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers... that require physical PCIe hardware cards not emulated in standard QEMU". This *definitely* fits the bill. It's a vendor-specific DSA switch driver for the Motorcomm YT9215 that requires physical hardware connected via MDIO, which isn't emulated in standard QEMU. That means it's unreachable in a virtualized environment. Let me just quickly confirm there's zero QEMU emulation for this. |
| 26/2 | 2026/07/27 06:16 | tool | grepper |
0mArgs:{
"Expression": "motorcomm,yt9215"
}Results: {
"Output": "Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml=24=properties:\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml-25- compatible:\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml:26: const: motorcomm,yt9215\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml-27-\n--\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml=59=examples:\n--\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml-67- switch@1d {\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml:68: compatible = \"motorcomm,yt9215\";\nDocumentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml-69- /* default 0x1d, alternate 0x0 */\n--\ndrivers/net/dsa/motorcomm/chip.c=4766=static const struct of_device_id yt921x_of_match[] = {\ndrivers/net/dsa/motorcomm/chip.c:4767:\t{ .compatible = \"motorcomm,yt9215\" },\ndrivers/net/dsa/motorcomm/chip.c-4768-\t{}\n"
} |
| 27/2 | 2026/07/27 06:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33622 output: 96 thoughts: 308 Thoughts: **My Assessment of the Fuzzing Potential** Okay, so I've looked at this driver, and my initial hunch is confirmed: this isn't going to be fruitful for fuzzing. There's no emulation available for this specific chip, and that's a major roadblock. The driver is specifically designed to interact with a physical hardware switch, accessed through MDIO. I know it's going to fail to probe in a virtualized environment because it explicitly requires reading the chip ID – something that's simply not possible without the actual hardware present. Therefore, any code path within this driver would be unreachable during fuzzing. Let me just quickly double-check for any potential bypasses or alternate entry points. Right, the patch adds `drivers/net/dsa/motorcomm/leds.c` and `drivers/net/dsa/motorcomm/smi.c`. Tracing the calls, I see `yt921x_leds_setup` is called from `yt921x_dsa_setup`, which in turn is called from `dsa_register_switch`, which ultimately originates from `yt921x_mdio_probe`. Aha! `yt921x_mdio_probe` is the probe function for the MDIO driver. Since the MDIO driver won't probe successfully without the hardware and its associated `yt921x_chip_detect` function, all the LED setup and control functions are completely unreachable. Even if, by some miracle, we could bypass the probe somehow, the LED control functions rely on writing to MDIO registers. Those writes would just fail or do nothing, rendering any fuzzing attempts completely useless. It's unequivocally a hardware-specific driver, so fuzzing in QEMU is a non-starter. I'm going to return `WorthFuzzing=false` here. It's not worth the effort. |
| 28/2 | 2026/07/27 06:16 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch modifies a vendor-specific DSA switch driver (Motorcomm YT9215) connected via MDIO. The driver requires physical hardware to probe successfully (it reads a specific chip ID during initialization) and is not emulated in standard QEMU. Therefore, the code is unreachable in a virtualized environment.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies a vendor-specific DSA switch driver (Motorcomm YT9215) connected via MDIO. The driver requires physical hardware to probe successfully (it reads a specific chip ID during initialization) and is not emulated in standard QEMU. Therefore, the code is unreachable in a virtualized environment.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|