AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/09 11:36 flow patch-triage
58m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces a driver for the Realtek PSE MCU, which is an I2C/SMBus or UART attached microcontroller that manages PSE (Power Sourcing Equipment) hardware on PoE switches. The driver is instantiated via device tree or ACPI. In a virtualized environment like QEMU or GCE, there are no device tree nodes or ACPI tables for this specific hardware. While I2C devices can sometimes be instantiated via sysfs, the probe function of this driver relies on `device_get_match_data()`, which returns NULL for sysfs-instantiated devices because they lack a firmware node (fwnode). The driver does not have an `id_table` to fall back on, so the probe will always fail with -ENODEV. The UART (serdev) driver cannot be instantiated from userspace at all. Therefore, the core logic of this driver is unreachable in standard virtualized environments.",
  "WorthFuzzing": false
}

1/1 2026/08/09 11:36 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 5c95a968f972e76eed8e19342696d80302331fde\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Aug 9 11:36:00 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml\nnew file mode 100644\nindex 0000000000000..3bb32349c28c4\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml\n@@ -0,0 +1,182 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu-gen1.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: Realtek PSE MCU\n+\n+maintainers:\n+  - Jonas Jelonek \u003cjelonek.jonas@gmail.com\u003e\n+\n+description: |\n+  A microcontroller (MCU) that manages the PSE (Power Sourcing Equipment)\n+  hardware on a range of managed PoE switches. The host CPU talks only to\n+  this MCU - over I2C/SMBus or UART - using a small message-based protocol;\n+  the PSE silicon it drives sits behind the MCU and is never accessed\n+  directly. For example, on the Zyxel GS1900-10HP the SoC reaches the MCU\n+  over UART, and the MCU manages the on-board PSE chip.\n+\n+  This binding describes the MCU together with its Realtek firmware: the\n+  firmware and its host protocol, which are stable across boards. The\n+  microcontroller silicon is a general-purpose part that varies, and the\n+  PSE silicon behind the MCU (Realtek RTL823x/RTL8239* or Broadcom\n+  BCM59xxx) is reported by the MCU and detected at runtime - neither is\n+  named here.\n+\n+  Two protocol generations exist, both Realtek's:\n+    gen1  older boards, where the MCU fronts Broadcom PSE silicon\n+    gen2  the altered protocol used with Realtek's own PSE silicon\n+\n+  On an I2C attachment the framing the MCU firmware expects is part of the\n+  compatible: '-smbus' (reads carry a leading command byte and a repeated\n+  start) or '-i2c' (bare block writes and reads). A UART attachment carries\n+  no framing suffix; the transport is given by the parent 'serial' node.\n+\n+  Each board additionally carries a device-specific compatible that falls\n+  back to one of the protocol compatibles above. Drivers bind on the\n+  protocol compatible; the device-specific string identifies the board and\n+  reserves a place for a future per-board quirk without having to retrofit\n+  device trees already in the field.\n+\n+properties:\n+  compatible:\n+    oneOf:\n+      # UART\n+      - items:\n+          - enum:\n+              - zyxel,gs1900-10hp-a1-pse\n+          - const: realtek,pse-mcu-gen1\n+\n+      # I2C, SMBus framing\n+      - items:\n+          - enum:\n+              - zyxel,gs1920-24hp-v2-pse\n+          - const: realtek,pse-mcu-gen1-smbus\n+\n+      # UART\n+      - items:\n+          - enum:\n+              - zyxel,gs1900-10hp-b1-pse\n+              - zyxel,xmg1915-10ep-pse\n+          - const: realtek,pse-mcu-gen2\n+\n+      # I2C, SMBus framing\n+      - items:\n+          - enum:\n+              - zyxel,xs1930-12hp-pse\n+          - const: realtek,pse-mcu-gen2-smbus\n+\n+      # I2C, raw framing\n+      - items:\n+          - enum:\n+              - linksys,lgs328mpc-v2-pse\n+          - const: realtek,pse-mcu-gen2-i2c\n+\n+  reg:\n+    maxItems: 1\n+\n+  reset-gpios:\n+    description: Reset line of the MCU.\n+    maxItems: 1\n+\n+  disable-ports-gpios:\n+    description:\n+      Hardware gate that forces all ports into admin-disabled state while\n+      asserted.\n+    maxItems: 1\n+\n+required:\n+  - compatible\n+\n+allOf:\n+  - $ref: pse-controller.yaml#\n+  # A '-smbus'/'-i2c' compatible is an I2C attachment: it has 'reg' and\n+  # cannot carry serial bus properties. A bare gen compatible is a UART\n+  # attachment: no 'reg', the transport comes from the parent serial node.\n+  - if:\n+      properties:\n+        compatible:\n+          contains:\n+            enum:\n+              - realtek,pse-mcu-gen1-smbus\n+              - realtek,pse-mcu-gen2-smbus\n+              - realtek,pse-mcu-gen2-i2c\n+    then:\n+      required:\n+        - reg\n+      properties:\n+        current-speed: false\n+        max-speed: false\n+    else:\n+      allOf:\n+        - $ref: /schemas/serial/serial-peripheral-props.yaml#\n+\n+      properties:\n+        reg: false\n+\n+unevaluatedProperties: false\n+\n+examples:\n+  # SMBus-framed I2C attachment\n+  - |\n+    i2c {\n+        #address-cells = \u003c1\u003e;\n+        #size-cells = \u003c0\u003e;\n+\n+        ethernet-pse@20 {\n+            compatible = \"zyxel,xs1930-12hp-pse\", \"realtek,pse-mcu-gen2-smbus\";\n+            reg = \u003c0x20\u003e;\n+\n+            pse-pis {\n+                #address-cells = \u003c1\u003e;\n+                #size-cells = \u003c0\u003e;\n+\n+                pse-pi@0 {\n+                    reg = \u003c0\u003e;\n+                    #pse-cells = \u003c0\u003e;\n+                };\n+            };\n+        };\n+    };\n+\n+  # Raw-I2C-framed attachment\n+  - |\n+    i2c {\n+        #address-cells = \u003c1\u003e;\n+        #size-cells = \u003c0\u003e;\n+\n+        ethernet-pse@20 {\n+            compatible = \"linksys,lgs328mpc-v2-pse\", \"realtek,pse-mcu-gen2-i2c\";\n+            reg = \u003c0x20\u003e;\n+\n+            pse-pis {\n+                #address-cells = \u003c1\u003e;\n+                #size-cells = \u003c0\u003e;\n+\n+                pse-pi@0 {\n+                    reg = \u003c0\u003e;\n+                    #pse-cells = \u003c0\u003e;\n+                };\n+            };\n+        };\n+    };\n+\n+  # UART attachment\n+  - |\n+    serial {\n+        ethernet-pse {\n+            compatible = \"zyxel,gs1900-10hp-a1-pse\", \"realtek,pse-mcu-gen1\";\n+            current-speed = \u003c19200\u003e;\n+\n+            pse-pis {\n+                #address-cells = \u003c1\u003e;\n+                #size-cells = \u003c0\u003e;\n+\n+                pse-pi@0 {\n+                    reg = \u003c0\u003e;\n+                    #pse-cells = \u003c0\u003e;\n+                };\n+            };\n+        };\n+    };\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 08e43bc09735d..26cfcd46b7c53 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -22758,6 +22758,13 @@ S:\tMaintained\n F:\tDocumentation/devicetree/bindings/watchdog/realtek,otto-wdt.yaml\n F:\tdrivers/watchdog/realtek_otto_wdt.c\n \n+REALTEK PSE MCU DRIVER\n+M:\tJonas Jelonek \u003cjelonek.jonas@gmail.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml\n+F:\tdrivers/net/pse-pd/realtek-pse-mcu*\n+\n REALTEK RTL83xx SMI DSA ROUTER CHIPS\n M:\tLinus Walleij \u003clinusw@kernel.org\u003e\n M:\tLuiz Angelo Daros de Luca \u003cluizluca@gmail.com\u003e\ndiff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig\nindex 7ef29657ee5dd..a0f2ae668c67c 100644\n--- a/drivers/net/pse-pd/Kconfig\n+++ b/drivers/net/pse-pd/Kconfig\n@@ -13,6 +13,34 @@ menuconfig PSE_CONTROLLER\n \n if PSE_CONTROLLER\n \n+config PSE_REALTEK_MCU\n+\ttristate\n+\thelp\n+\t  Shared core for the Realtek PSE MCU driver. This is selected\n+\t  automatically by the transport options below.\n+\n+config PSE_REALTEK_MCU_I2C\n+\ttristate \"Realtek PSE MCU driver (I2C transport)\"\n+\tdepends on I2C\n+\tselect PSE_REALTEK_MCU\n+\thelp\n+\t  Driver for the microcontroller (MCU) that fronts the PSE\n+\t  hardware on various Realtek-based managed switches, attached\n+\t  via I2C/SMBus. The MCU exposes a message-based protocol; the actual\n+\t  PSE silicon is not accessed directly. To compile this driver as a\n+\t  module, choose M here: the module will be called realtek-pse-mcu-i2c.\n+\n+config PSE_REALTEK_MCU_UART\n+\ttristate \"Realtek PSE MCU driver (UART transport)\"\n+\tdepends on SERIAL_DEV_BUS\n+\tselect PSE_REALTEK_MCU\n+\thelp\n+\t  Driver for the microcontroller (MCU) that fronts the PSE\n+\t  hardware on various Realtek-based managed switches, attached\n+\t  via UART. The MCU exposes a message-based protocol; the actual PSE\n+\t  silicon is not accessed directly. To compile this driver as a\n+\t  module, choose M here: the module will be called realtek-pse-mcu-uart.\n+\n config PSE_REGULATOR\n \ttristate \"Regulator based PSE controller\"\n \thelp\ndiff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile\nindex cc78f7ea7f5f7..9cca5900fe342 100644\n--- a/drivers/net/pse-pd/Makefile\n+++ b/drivers/net/pse-pd/Makefile\n@@ -3,6 +3,9 @@\n \n obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o\n \n+obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o\n+obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o\n+obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o\n obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o\n obj-$(CONFIG_PSE_PD692X0) += pd692x0.o\n obj-$(CONFIG_PSE_SI3474) += si3474.o\ndiff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c\nnew file mode 100644\nindex 0000000000000..cb53686fcd96b\n--- /dev/null\n+++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c\n@@ -0,0 +1,994 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Driver for the microcontroller (MCU) fronting PSE silicon on various\n+ * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame\n+ * management protocol; this driver covers two generations of the\n+ * protocol via a per-dialect opcode table and response parsers.\n+ *\n+ * Many PoE switch designs put a dedicated microcontroller in front of the\n+ * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or\n+ * UART, and the MCU in turn manages the PSE chips on the board. The MCU\n+ * speaks a small message-based protocol. The PSE chips themselves are not\n+ * accessed directly; everything goes through MCU commands.\n+ *\n+ * This driver targets that architecture for the Realtek-family protocol.\n+ * Two generations are supported: Gen1 being used on older switches where\n+ * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with\n+ * Realtek PSE silicon. The two share frame format and a sum-mod-256\n+ * checksum but diverge on opcode numbers and on a few response layouts;\n+ * this is handled by the per-dialect opcode table and parser hooks.\n+ *\n+ * Out of scope: PSE chips that are interfaced directly from the host\n+ * without a management MCU, MCU designs that speak an unrelated protocol\n+ * family, and \"dumb PSE\" modes where no host control is wired up at all.\n+ *\n+ * This core module implements the protocol, decoding/encoding of MCU\n+ * responses, and the pse_controller_ops integration. Transport modules\n+ * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv\n+ * callbacks.\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n+#include \u003clinux/container_of.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \u003clinux/jiffies.h\u003e\n+#include \u003clinux/minmax.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/pse-pd/pse.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n+\n+#include \"realtek-pse-mcu.h\"\n+\n+#define RTPSE_MCU_DEVICE_ID_RTL8238B\t\t0x0138\n+#define RTPSE_MCU_DEVICE_ID_RTL8239\t\t0x0039\n+#define RTPSE_MCU_DEVICE_ID_RTL8239C\t\t0x0139\n+#define RTPSE_MCU_DEVICE_ID_BCM59111\t\t0xe111\n+#define RTPSE_MCU_DEVICE_ID_BCM59121\t\t0xe121\n+\n+#define RTPSE_MCU_PORT_STS_DISABLED\t\t0x00\n+#define RTPSE_MCU_PORT_STS_SEARCHING\t\t0x01\n+#define RTPSE_MCU_PORT_STS_DELIVERING\t\t0x02\n+#define RTPSE_MCU_PORT_STS_TEST\t\t\t0x03\t/* Gen1-only; reserved on Gen2 */\n+#define RTPSE_MCU_PORT_STS_FAULT\t\t0x04\n+#define RTPSE_MCU_PORT_STS_OTHER_FAULT\t\t0x05\t/* Gen1-only; reserved on Gen2 */\n+#define RTPSE_MCU_PORT_STS_REQUESTING\t\t0x06\n+\n+/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */\n+#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER\t0x02\n+\n+#define RTPSE_MCU_MAX_PORTS\t\t\t48\n+#define RTPSE_MCU_PORT_MAX_PRIORITY\t\t3\n+\n+/* Nominal PSE rail; 802.3at/bt operating range. */\n+#define RTPSE_MCU_PSE_VOLTAGE_UV\t\t54000000\n+\n+enum rtpse_mcu_cmd {\n+\tRTPSE_MCU_CMD_SET_GLOBAL_STATE,\n+\tRTPSE_MCU_CMD_GET_SYSTEM_INFO,\n+\tRTPSE_MCU_CMD_GET_EXT_CONFIG,\n+\n+\tRTPSE_MCU_CMD_PORT_ENABLE,\n+\tRTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE,\n+\tRTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,\n+\tRTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,\n+\tRTPSE_MCU_CMD_PORT_SET_PRIORITY,\n+\tRTPSE_MCU_CMD_PORT_GET_STATUS,\n+\tRTPSE_MCU_CMD_PORT_GET_POWER_STATS,\n+\tRTPSE_MCU_CMD_PORT_GET_CONFIG,\n+\tRTPSE_MCU_CMD_PORT_GET_EXT_CONFIG,\n+\n+\tRTPSE_MCU_NUM_CMDS,\n+};\n+\n+struct rtpse_mcu_opcode {\n+\tu8 op;\n+\tbool valid;\n+};\n+\n+/* Shorthand for the designated-initializer entries in dialect opcode tables. */\n+#define RTPSE_MCU_OP(opc)\t{ .op = (opc), .valid = true }\n+\n+/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */\n+\n+struct rtpse_mcu_info {\n+\tu8 max_ports;\n+\tbool system_enable;\n+\tu16 device_id;\n+\tu8 mcu_type;\n+};\n+\n+struct rtpse_mcu_ext_config {\n+\tu8 num_of_pses;\n+};\n+\n+struct rtpse_mcu_port_status {\n+\tu8 sts1;\n+\tu8 sts2;\n+\tu8 sts3;\n+};\n+\n+struct rtpse_mcu_port_measurement {\n+\tu16 voltage_raw;\t/* 64.45mV/LSB */\n+\tu16 current_raw;\t/* 1mA/LSB */\n+\tu16 temperature_raw;\t/* T(mC) = 1250 * (220 - raw) */\n+\tu16 power_raw;\t\t/* 100mW/LSB */\n+};\n+\n+struct rtpse_mcu_port_config {\n+\tbool enable;\n+};\n+\n+struct rtpse_mcu_port_ext_config {\n+\tu8 max_power;\n+\tu8 priority;\n+};\n+\n+struct rtpse_mcu_dialect {\n+\tstruct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS];\n+\n+\t/*\n+\t * Response parsers for the fields that differ between dialects; each\n+\t * dialect supplies its own. Other responses share one layout and are\n+\t * decoded directly - a dialect that diverges there must add a hook,\n+\t * as a mismatched layout cannot be detected (the checksum still passes).\n+\t */\n+\tvoid (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);\n+\tint (*parse_port_class)(const struct rtpse_mcu_port_status *status);\n+\tconst char *(*mcu_type_str)(unsigned int mcu_type);\n+};\n+\n+struct rtpse_mcu_chip_info {\n+\tconst char *name;\n+\tu32 max_mW_per_port;\n+\tenum rtpse_mcu_cmd pw_set_cmd;\t/* command used by set_pw_limit */\n+\tu32 pw_set_lsb_mW;\t\t/* LSB of pw_set_cmd value, in mW */\n+\tu32 pw_read_lsb_mW;\t\t/* LSB of ext_config.max_power read-back, in mW */\n+};\n+\n+static const struct rtpse_mcu_chip_info rtl8238b_info = {\n+\t.max_mW_per_port = 30000,\n+\t.name = \"RTL8238B\",\n+\t.pw_read_lsb_mW = 200,\n+\t.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,\n+\t.pw_set_lsb_mW = 200,\n+};\n+\n+static const struct rtpse_mcu_chip_info rtl8239_info = {\n+\t.max_mW_per_port = 90000,\n+\t.name = \"RTL8239\",\n+\t.pw_read_lsb_mW = 400,\n+\t.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,\n+\t.pw_set_lsb_mW = 400,\n+};\n+\n+static const struct rtpse_mcu_chip_info rtl8239c_info = {\n+\t.max_mW_per_port = 90000,\n+\t.name = \"RTL8239C\",\n+\t.pw_read_lsb_mW = 400,\n+\t.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,\n+\t.pw_set_lsb_mW = 400,\n+};\n+\n+static const struct rtpse_mcu_chip_info bcm59111_info = {\n+\t.max_mW_per_port = 30000,\n+\t.name = \"BCM59111\",\n+\t.pw_read_lsb_mW = 200,\n+\t.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,\n+\t.pw_set_lsb_mW = 200,\n+};\n+\n+static const struct rtpse_mcu_chip_info bcm59121_info = {\n+\t/*\n+\t * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at\n+\t * and the Gen1 dialect has only the 8-bit/0.2W set command (\u003c=51W);\n+\t * cap at the 30W the hardware actually offers.\n+\t */\n+\t.max_mW_per_port = 30000,\n+\t.name = \"BCM59121\",\n+\t.pw_read_lsb_mW = 200,\n+\t.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,\n+\t.pw_set_lsb_mW = 200,\n+};\n+\n+/* Helpers and basic functions */\n+\n+static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev)\n+{\n+\treturn container_of(pcdev, struct rtpse_mcu_ctrl, pcdev);\n+}\n+\n+static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode)\n+{\n+\tmemset(msg, 0xff, sizeof(*msg));\n+\tmsg-\u003eopcode = opcode;\n+}\n+\n+static u8 rtpse_mcu_checksum(const u8 *buf, size_t len)\n+{\n+\tu8 sum = 0;\n+\n+\twhile (len--)\n+\t\tsum += *buf++;\n+\treturn sum;\n+}\n+\n+static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req,\n+\t\t\t     struct rtpse_mcu_msg *resp)\n+{\n+\tint ret;\n+\n+\tscoped_guard(mutex, \u0026pse-\u003emutex) {\n+\t\t/*\n+\t\t * Rolling seq_num so a late reply can't pass as a later one;\n+\t\t * skip 0 so an all-zero frame never validates as a real reply.\n+\t\t */\n+\t\tif (++pse-\u003eseq == 0)\n+\t\t\tpse-\u003eseq = 1;\n+\t\treq-\u003eseq_num = pse-\u003eseq;\n+\t\treq-\u003echecksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1);\n+\n+\t\tret = pse-\u003etransport-\u003esend(pse, req);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/*\n+\t\t * The MCU needs a fixed base time between accepting a request and\n+\t\t * having a reply ready, independent of transport. Pace that here; the\n+\t\t * transport then finishes recv in its own model - a bounded poll of\n+\t\t * the response register (I2C) or one wait on a pushed frame (UART) -\n+\t\t * so the retry belongs in the transport, not here.\n+\t\t */\n+\t\tmsleep(RTPSE_MCU_RESPONSE_MS);\n+\n+\t\tmemset(resp, 0, sizeof(*resp));\n+\t\tret = pse-\u003etransport-\u003erecv(pse, req, resp);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Explicit MCU error opcodes (observed on the Gen1 dialect; harmless\n+\t * to check for Gen2 too). Catch these before the generic opcode/CRC\n+\t * mismatch path so callers see a meaningful errno.\n+\t */\n+\tswitch (resp-\u003eopcode) {\n+\tcase RTPSE_MCU_OPCODE_INCOMPLETE:\n+\t\treturn -EBADE;\n+\tcase RTPSE_MCU_OPCODE_BAD_CSUM:\n+\t\treturn -EBADMSG;\n+\tcase RTPSE_MCU_OPCODE_NOT_READY:\n+\t\treturn -EAGAIN;\n+\t}\n+\n+\tif (resp-\u003eopcode != req-\u003eopcode ||\n+\t    resp-\u003eseq_num != req-\u003eseq_num ||\n+\t    resp-\u003echecksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1))\n+\t\treturn -EBADMSG;\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode,\n+\t\t\t\tstruct rtpse_mcu_msg *resp)\n+{\n+\tstruct rtpse_mcu_msg req;\n+\tint ret;\n+\n+\trtpse_mcu_msg_init(\u0026req, opcode);\n+\treq.payload[0] = port;\n+\n+\tret = rtpse_mcu_do_xfer(pse, \u0026req, resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (resp-\u003epayload[0] != port)\n+\t\treturn -EIO;\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg)\n+{\n+\tstruct rtpse_mcu_msg req, resp;\n+\tint ret;\n+\n+\trtpse_mcu_msg_init(\u0026req, opcode);\n+\treq.payload[0] = port;\n+\treq.payload[1] = arg;\n+\n+\tret = rtpse_mcu_do_xfer(pse, \u0026req, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (resp.payload[0] != port || resp.payload[1] != 0)\n+\t\treturn -EIO;\n+\n+\treturn 0;\n+}\n+\n+/* Global operations */\n+\n+static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)\n+{\n+\tstruct rtpse_mcu_msg req, resp;\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\trtpse_mcu_msg_init(\u0026req, opc-\u003eop);\n+\tret = rtpse_mcu_do_xfer(pse, \u0026req, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tpse-\u003edialect-\u003eparse_system_info(resp.payload, info);\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config)\n+{\n+\tstruct rtpse_mcu_msg req, resp;\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_GET_EXT_CONFIG];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\trtpse_mcu_msg_init(\u0026req, opc-\u003eop);\n+\tret = rtpse_mcu_do_xfer(pse, \u0026req, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tconfig-\u003enum_of_pses = resp.payload[6];\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable)\n+{\n+\tstruct rtpse_mcu_msg req, resp;\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\trtpse_mcu_msg_init(\u0026req, opc-\u003eop);\n+\treq.payload[0] = enable ? 0x1 : 0x0;\n+\n+\tret = rtpse_mcu_do_xfer(pse, \u0026req, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn (resp.payload[0] == 0x0) ? 0 : -EIO;\n+}\n+\n+/* Port operations */\n+\n+static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port,\n+\t\t\t\t     struct rtpse_mcu_port_status *status)\n+{\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tstruct rtpse_mcu_msg resp;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_GET_STATUS];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tret = rtpse_mcu_port_query(pse, port, opc-\u003eop, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tstatus-\u003ests1 = resp.payload[1];\n+\tstatus-\u003ests2 = resp.payload[2];\n+\tstatus-\u003ests3 = resp.payload[3];\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port,\n+\t\t\t\t\t  struct rtpse_mcu_port_measurement *measurement)\n+{\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tstruct rtpse_mcu_msg resp;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tret = rtpse_mcu_port_query(pse, port, opc-\u003eop, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmeasurement-\u003evoltage_raw = get_unaligned_be16(\u0026resp.payload[1]);\n+\tmeasurement-\u003ecurrent_raw = get_unaligned_be16(\u0026resp.payload[3]);\n+\tmeasurement-\u003etemperature_raw = get_unaligned_be16(\u0026resp.payload[5]);\n+\tmeasurement-\u003epower_raw = get_unaligned_be16(\u0026resp.payload[7]);\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port,\n+\t\t\t\t     struct rtpse_mcu_port_config *config)\n+{\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tstruct rtpse_mcu_msg resp;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_GET_CONFIG];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tret = rtpse_mcu_port_query(pse, port, opc-\u003eop, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tconfig-\u003eenable = (resp.payload[1] == 1);\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port,\n+\t\t\t\t\t struct rtpse_mcu_port_ext_config *config)\n+{\n+\tconst struct rtpse_mcu_opcode *opc;\n+\tstruct rtpse_mcu_msg resp;\n+\tint ret;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tret = rtpse_mcu_port_query(pse, port, opc-\u003eop, \u0026resp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tconfig-\u003emax_power = resp.payload[3];\n+\tconfig-\u003epriority = resp.payload[4];\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable)\n+{\n+\tconst struct rtpse_mcu_opcode *opc;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_ENABLE];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn rtpse_mcu_port_cmd(pse, port, opc-\u003eop, enable ? 0x1 : 0x0);\n+}\n+\n+/* PSE controller ops */\n+\n+static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id,\n+\t\t\t\t\t  struct pse_admin_state *admin_state)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_config config;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_config(pse, id, \u0026config);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tadmin_state-\u003ec33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED :\n+\t\t\t\t\t\t       ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id,\n+\t\t\t\t\tstruct pse_pw_status *pw_status)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_status status;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_status(pse, id, \u0026status);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tswitch (status.sts1) {\n+\tcase RTPSE_MCU_PORT_STS_DISABLED:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;\n+\t\tbreak;\n+\tcase RTPSE_MCU_PORT_STS_SEARCHING:\n+\tcase RTPSE_MCU_PORT_STS_REQUESTING:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;\n+\t\tbreak;\n+\tcase RTPSE_MCU_PORT_STS_DELIVERING:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;\n+\t\tbreak;\n+\tcase RTPSE_MCU_PORT_STS_TEST:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST;\n+\t\tbreak;\n+\tcase RTPSE_MCU_PORT_STS_FAULT:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;\n+\t\tbreak;\n+\tcase RTPSE_MCU_PORT_STS_OTHER_FAULT:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT;\n+\t\tbreak;\n+\tdefault:\n+\t\tpw_status-\u003ec33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_status status;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_status(pse, id, \u0026status);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * As per datasheet, the classification result is only valid when in\n+\t * one of those operational modes, otherwise not.\n+\t */\n+\tswitch (status.sts1) {\n+\tcase RTPSE_MCU_PORT_STS_DISABLED:\n+\tcase RTPSE_MCU_PORT_STS_SEARCHING:\n+\tcase RTPSE_MCU_PORT_STS_DELIVERING:\n+\tcase RTPSE_MCU_PORT_STS_REQUESTING:\n+\t\treturn pse-\u003edialect-\u003eparse_port_class(\u0026status);\n+\tdefault:\n+\t\t/*\n+\t\t * No class to report, return 0 instead. This is indistinguishable\n+\t\t * from a real class-0 PD but userspace disambiguates via the\n+\t\t * power status.\n+\t\t */\n+\t\treturn 0;\n+\t}\n+}\n+\n+static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_measurement measurement;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_measurement(pse, id, \u0026measurement);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* 100mW per LSB */\n+\treturn measurement.power_raw * 100U;\n+}\n+\n+static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_measurement measurement;\n+\tint ret;\n+\tu32 uV;\n+\n+\tret = rtpse_mcu_port_get_measurement(pse, id, \u0026measurement);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* 64.45mV per LSB */\n+\tuV = measurement.voltage_raw * 64450U;\n+\n+\t/*\n+\t * Idle ports measure 0V, which the core rejects when turning a power\n+\t * limit into a current limit. Fall back to the nominal rail so a limit\n+\t * can be set before a PD is attached.\n+\t */\n+\tif (!uV)\n+\t\treturn RTPSE_MCU_PSE_VOLTAGE_UV;\n+\n+\treturn min_t(u32, uV, INT_MAX);\n+}\n+\n+static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id)\n+{\n+\treturn rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true);\n+}\n+\n+static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id)\n+{\n+\treturn rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false);\n+}\n+\n+static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_ext_config config;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_ext_config(pse, id, \u0026config);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn config.max_power * pse-\u003echip-\u003epw_read_lsb_mW;\n+}\n+\n+static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW)\n+{\n+\tconst struct rtpse_mcu_opcode *type_opc, *val_opc;\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tconst struct rtpse_mcu_chip_info *chip = pse-\u003echip;\n+\tu8 prg_val;\n+\tint ret;\n+\n+\tif (max_mW \u003c 0 || max_mW \u003e chip-\u003emax_mW_per_port)\n+\t\treturn -ERANGE;\n+\n+\ttype_opc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE];\n+\tval_opc = \u0026pse-\u003edialect-\u003eopcode[chip-\u003epw_set_cmd];\n+\t/* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */\n+\tif (!type_opc-\u003evalid || !val_opc-\u003evalid || !chip-\u003epw_set_lsb_mW)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/*\n+\t * Switch the port to user-defined limit mode first, then program the\n+\t * limit value. If the second cmd fails, the port is left in\n+\t * user-defined mode but with the previous limit value; the next\n+\t * successful set_pw_limit call recovers it.\n+\t */\n+\tret = rtpse_mcu_port_cmd(pse, id, type_opc-\u003eop, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Round up so a sub-LSB request maps to one LSB, not silently to 0;\n+\t * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot.\n+\t */\n+\tprg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip-\u003epw_set_lsb_mW), U8_MAX);\n+\n+\treturn rtpse_mcu_port_cmd(pse, id, val_opc-\u003eop, prg_val);\n+}\n+\n+static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,\n+\t\t\t\t\t      struct pse_pw_limit_ranges *out)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct ethtool_c33_pse_pw_limit_range *range;\n+\n+\trange = kzalloc_obj(*range);\n+\tif (!range)\n+\t\treturn -ENOMEM;\n+\n+\trange[0].min = 0;\n+\trange[0].max = pse-\u003echip-\u003emax_mW_per_port;\n+\n+\tout-\u003ec33_pw_limit_ranges = range;\n+\treturn 1;\n+}\n+\n+static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tstruct rtpse_mcu_port_ext_config config;\n+\tint ret;\n+\n+\tret = rtpse_mcu_port_get_ext_config(pse, id, \u0026config);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn config.priority;\n+}\n+\n+static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);\n+\tconst struct rtpse_mcu_opcode *opc;\n+\n+\tif (prio \u003e RTPSE_MCU_PORT_MAX_PRIORITY)\n+\t\treturn -ERANGE;\n+\n+\topc = \u0026pse-\u003edialect-\u003eopcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY];\n+\tif (!opc-\u003evalid)\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn rtpse_mcu_port_cmd(pse, id, opc-\u003eop, prio);\n+}\n+\n+static const struct pse_controller_ops rtpse_mcu_ops = {\n+\t.pi_get_admin_state = rtpse_mcu_port_get_admin_state,\n+\t.pi_get_pw_status = rtpse_mcu_port_get_pw_status,\n+\t.pi_get_pw_class = rtpse_mcu_port_get_pw_class,\n+\t.pi_get_actual_pw = rtpse_mcu_port_get_actual_pw,\n+\t.pi_enable = rtpse_mcu_port_enable,\n+\t.pi_disable = rtpse_mcu_port_disable,\n+\t.pi_get_voltage = rtpse_mcu_port_get_voltage,\n+\t.pi_get_pw_limit = rtpse_mcu_port_get_pw_limit,\n+\t.pi_set_pw_limit = rtpse_mcu_port_set_pw_limit,\n+\t.pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges,\n+\t.pi_get_prio = rtpse_mcu_port_get_prio,\n+\t.pi_set_prio = rtpse_mcu_port_set_prio,\n+};\n+\n+static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)\n+{\n+\tstruct rtpse_mcu_ext_config ext_config;\n+\tunsigned long deadline;\n+\tint ret;\n+\n+\t/*\n+\t * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address\n+\t * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a\n+\t * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a\n+\t * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail\n+\t * immediately.\n+\t */\n+\tdeadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS);\n+\tdo {\n+\t\tret = rtpse_mcu_get_info(pse, info);\n+\t\tif (ret != -ETIMEDOUT \u0026\u0026 ret != -ENXIO \u0026\u0026 ret != -EREMOTEIO \u0026\u0026\n+\t\t    ret != -EAGAIN \u0026\u0026 ret != -EBADMSG \u0026\u0026 ret != -EBADE)\n+\t\t\tbreak;\n+\t\tmsleep(RTPSE_MCU_BOOT_RETRY_MS);\n+\t} while (time_before(jiffies, deadline));\n+\tif (ret)\n+\t\treturn dev_err_probe(pse-\u003edev, ret, \"failed to read MCU info\\n\");\n+\n+\tswitch (info-\u003edevice_id) {\n+\tcase RTPSE_MCU_DEVICE_ID_RTL8238B:\n+\t\tpse-\u003echip = \u0026rtl8238b_info;\n+\t\tbreak;\n+\tcase RTPSE_MCU_DEVICE_ID_RTL8239:\n+\t\tpse-\u003echip = \u0026rtl8239_info;\n+\t\tbreak;\n+\tcase RTPSE_MCU_DEVICE_ID_RTL8239C:\n+\t\tpse-\u003echip = \u0026rtl8239c_info;\n+\t\tbreak;\n+\tcase RTPSE_MCU_DEVICE_ID_BCM59111:\n+\t\tpse-\u003echip = \u0026bcm59111_info;\n+\t\tbreak;\n+\tcase RTPSE_MCU_DEVICE_ID_BCM59121:\n+\t\tpse-\u003echip = \u0026bcm59121_info;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn dev_err_probe(pse-\u003edev, -EINVAL, \"unknown PSE id 0x%x\\n\",\n+\t\t\t\t     info-\u003edevice_id);\n+\t}\n+\n+\tif (!info-\u003emax_ports || info-\u003emax_ports \u003e RTPSE_MCU_MAX_PORTS)\n+\t\treturn dev_err_probe(pse-\u003edev, -EINVAL,\n+\t\t\t\t     \"MCU reports invalid port count %u\\n\", info-\u003emax_ports);\n+\n+\tret = rtpse_mcu_get_ext_config(pse, \u0026ext_config);\n+\tif (ret)\n+\t\treturn dev_err_probe(pse-\u003edev, ret, \"failed to read MCU ext config\\n\");\n+\n+\tdev_info(pse-\u003edev, \"%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\\n\",\n+\t\t pse-\u003edialect-\u003emcu_type_str(info-\u003emcu_type), pse-\u003echip-\u003ename,\n+\t\t info-\u003edevice_id, info-\u003emax_ports, ext_config.num_of_pses);\n+\treturn 0;\n+}\n+\n+static void rtpse_mcu_global_disable(void *data)\n+{\n+\tstruct rtpse_mcu_ctrl *pse = data;\n+\n+\trtpse_mcu_set_global_state(pse, false);\n+}\n+\n+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)\n+{\n+\tconst struct rtpse_mcu_match_data *match;\n+\tstruct rtpse_mcu_info info;\n+\tstruct gpio_desc *gpiod;\n+\tint ret;\n+\n+\tBUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE);\n+\n+\tret = devm_mutex_init(pse-\u003edev, \u0026pse-\u003emutex);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmatch = device_get_match_data(pse-\u003edev);\n+\tif (!match)\n+\t\treturn dev_err_probe(pse-\u003edev, -ENODEV, \"missing match data\\n\");\n+\tpse-\u003edialect = match-\u003edialect;\n+\n+\t/*\n+\t * Catch a dialect that forgot to set one of the required hooks at\n+\t * probe time, rather than NULL-deref'ing later from a fast path.\n+\t */\n+\tif (!pse-\u003edialect ||\n+\t    !pse-\u003edialect-\u003eparse_system_info ||\n+\t    !pse-\u003edialect-\u003eparse_port_class ||\n+\t    !pse-\u003edialect-\u003emcu_type_str)\n+\t\treturn dev_err_probe(pse-\u003edev, -EINVAL,\n+\t\t\t\t     \"dialect for chip is incomplete\\n\");\n+\n+\t/*\n+\t * Release the MCU from reset before the first transaction; the\n+\t * boot-retry loop in discover() waits for it to answer.\n+\t */\n+\tgpiod = devm_gpiod_get_optional(pse-\u003edev, \"reset\", GPIOD_OUT_LOW);\n+\tif (IS_ERR(gpiod))\n+\t\treturn dev_err_probe(pse-\u003edev, PTR_ERR(gpiod),\n+\t\t\t\t     \"failed to get reset gpio\\n\");\n+\n+\tret = rtpse_mcu_discover(pse, \u0026info);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Some boards gate all ports through a hardware line; deassert it only\n+\t * after the MCU is confirmed, so a discover failure never ungates the\n+\t * ports. It is then left to the MCU - not re-gated on unbind - so a\n+\t * driver reload doesn't black out PoE.\n+\t */\n+\tgpiod = devm_gpiod_get_optional(pse-\u003edev, \"disable-ports\", GPIOD_OUT_LOW);\n+\tif (IS_ERR(gpiod))\n+\t\treturn dev_err_probe(pse-\u003edev, PTR_ERR(gpiod),\n+\t\t\t\t     \"failed to get disable-ports gpio\\n\");\n+\n+\tif (!info.system_enable) {\n+\t\tret = rtpse_mcu_set_global_state(pse, true);\n+\t\t/* Dialects without a global-state concept (e.g. Gen1) return\n+\t\t * -EOPNOTSUPP; treat that as \"no separate enable required\".\n+\t\t */\n+\t\tif (ret \u0026\u0026 ret != -EOPNOTSUPP)\n+\t\t\treturn dev_err_probe(pse-\u003edev, ret,\n+\t\t\t\t\t     \"failed to enable PSE system\\n\");\n+\t\tif (!ret) {\n+\t\t\tret = devm_add_action_or_reset(pse-\u003edev,\n+\t\t\t\t\t\t       rtpse_mcu_global_disable, pse);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Depending on the MCU firmware configuration (which might be different\n+\t * for every board), it isn't known whether the PoE subsystem is active or\n+\t * inactive by default. At this stage, the PSE chips might already deliver\n+\t * power to PDs without any explicit enable.\n+\t */\n+\n+\t/* pcdev.owner is set by the transport, so the registered controller\n+\t * pins the transport module that owns the live device, not the core.\n+\t */\n+\tpse-\u003epcdev.ops      = \u0026rtpse_mcu_ops;\n+\tpse-\u003epcdev.dev      = pse-\u003edev;\n+\tpse-\u003epcdev.types    = ETHTOOL_PSE_C33;\n+\tpse-\u003epcdev.nr_lines = info.max_ports;\n+\tpse-\u003epcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY;\n+\tpse-\u003epcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;\n+\n+\treturn devm_pse_controller_register(pse-\u003edev, \u0026pse-\u003epcdev);\n+}\n+EXPORT_SYMBOL_GPL(rtpse_mcu_register);\n+\n+static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)\n+{\n+\tinfo-\u003emax_ports = payload[1];\n+\tinfo-\u003esystem_enable = (payload[2] == 0x1);\n+\tinfo-\u003edevice_id = get_unaligned_be16(\u0026payload[3]);\n+\tinfo-\u003emcu_type = payload[6];\n+}\n+\n+static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status)\n+{\n+\t/* Class lives in the upper nibble of sts2. */\n+\treturn FIELD_GET(GENMASK(7, 4), status-\u003ests2);\n+}\n+\n+static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type)\n+{\n+\tswitch (mcu_type) {\n+\tcase 0x00:\treturn \"GigaDevice GD32F310\";\n+\tcase 0x01:\treturn \"GigaDevice GD32F230\";\n+\tcase 0x02:\treturn \"GigaDevice GD32F303\";\n+\tcase 0x03:\treturn \"GigaDevice GD32F103\";\n+\tcase 0x04:\treturn \"GigaDevice GD32E103\";\n+\tcase 0x10:\treturn \"Nuvoton M0516\";\n+\tcase 0x11:\treturn \"Nuvoton M0564\";\n+\tcase 0x12:\treturn \"Nuvoton NUC029\";\n+\tdefault:\treturn \"unknown\";\n+\t}\n+}\n+\n+static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)\n+{\n+\tinfo-\u003emax_ports = payload[1];\n+\t/* Gen1 has no explicit system_enable byte; the closest analog is the\n+\t * \"remote enable\" bit in the system-status flags at payload[7].\n+\t */\n+\tinfo-\u003esystem_enable = !!(payload[7] \u0026 BIT(2));\n+\tinfo-\u003edevice_id = get_unaligned_be16(\u0026payload[3]);\n+\tinfo-\u003emcu_type = payload[6];\n+}\n+\n+static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status)\n+{\n+\t/* Gen1 puts the detected class in payload[3] (== sts3) directly.\n+\t * Mask to the low nibble; class is 0..8 and any high bits would be\n+\t * noise.\n+\t */\n+\treturn status-\u003ests3 \u0026 0x0f;\n+}\n+\n+static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type)\n+{\n+\tswitch (mcu_type) {\n+\tcase 0x00:\treturn \"ST Micro ST32F100\";\n+\tcase 0x01:\treturn \"Nuvoton M05xx LAN\";\n+\tcase 0x02:\treturn \"ST Micro STF030C8\";\n+\tcase 0x03:\treturn \"Nuvoton M058SAN\";\n+\tcase 0x04:\treturn \"Nuvoton NUC122\";\n+\tdefault:\treturn \"unknown\";\n+\t}\n+}\n+\n+/* Map each logical command the core issues to its per-dialect opcode. */\n+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = {\n+\t.parse_system_info = rtpse_mcu_gen2_parse_system_info,\n+\t.parse_port_class  = rtpse_mcu_gen2_parse_port_class,\n+\t.mcu_type_str      = rtpse_mcu_gen2_mcu_type_str,\n+\t.opcode = {\n+\t\t[RTPSE_MCU_CMD_SET_GLOBAL_STATE]\t= RTPSE_MCU_OP(0x00),\n+\t\t[RTPSE_MCU_CMD_GET_SYSTEM_INFO]\t\t= RTPSE_MCU_OP(0x40),\n+\t\t[RTPSE_MCU_CMD_GET_EXT_CONFIG]\t\t= RTPSE_MCU_OP(0x4a),\n+\n+\t\t[RTPSE_MCU_CMD_PORT_ENABLE]\t\t= RTPSE_MCU_OP(0x01),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]\t= RTPSE_MCU_OP(0x13),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_PRIORITY]\t= RTPSE_MCU_OP(0x15),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_STATUS]\t\t= RTPSE_MCU_OP(0x42),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]\t= RTPSE_MCU_OP(0x44),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_CONFIG]\t\t= RTPSE_MCU_OP(0x48),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]\t= RTPSE_MCU_OP(0x49),\n+\t},\n+};\n+\n+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = {\n+\t.parse_system_info = rtpse_mcu_gen1_parse_system_info,\n+\t.parse_port_class  = rtpse_mcu_gen1_parse_port_class,\n+\t.mcu_type_str      = rtpse_mcu_gen1_mcu_type_str,\n+\t.opcode = {\n+\t\t[RTPSE_MCU_CMD_GET_SYSTEM_INFO]\t\t= RTPSE_MCU_OP(0x20),\n+\t\t[RTPSE_MCU_CMD_GET_EXT_CONFIG]\t\t= RTPSE_MCU_OP(0x2b),\n+\n+\t\t[RTPSE_MCU_CMD_PORT_ENABLE]\t\t= RTPSE_MCU_OP(0x00),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]\t= RTPSE_MCU_OP(0x16),\n+\t\t[RTPSE_MCU_CMD_PORT_SET_PRIORITY]\t= RTPSE_MCU_OP(0x1a),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_STATUS]\t\t= RTPSE_MCU_OP(0x21),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]\t= RTPSE_MCU_OP(0x30),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_CONFIG]\t\t= RTPSE_MCU_OP(0x25),\n+\t\t[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]\t= RTPSE_MCU_OP(0x26),\n+\t},\n+};\n+\n+const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = {\n+\t.dialect = \u0026rtpse_mcu_dialect_gen1,\n+};\n+EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data);\n+\n+const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = {\n+\t.dialect = \u0026rtpse_mcu_dialect_gen2,\n+};\n+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data);\n+\n+/* Same dialect as gen2, but the MCU expects raw-I2C framing. */\n+const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = {\n+\t.dialect = \u0026rtpse_mcu_dialect_gen2,\n+\t.native_i2c = true,\n+};\n+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data);\n+\n+MODULE_AUTHOR(\"Jonas Jelonek \u003cjelonek.jonas@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"Realtek PSE MCU driver (core)\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c\nnew file mode 100644\nindex 0000000000000..b6769ac1d4707\n--- /dev/null\n+++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c\n@@ -0,0 +1,148 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/pse-pd/pse.h\u003e\n+\n+#include \"realtek-pse-mcu.h\"\n+\n+/*\n+ * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so\n+ * the response is normally ready on the very first read. For commands the\n+ * MCU produces more slowly, keep polling at the typical response cadence\n+ * up to the worst-case ceiling.\n+ */\n+#define RTPSE_MCU_I2C_RETRY_MS\tRTPSE_MCU_RESPONSE_MS\n+#define RTPSE_MCU_I2C_MAX_TRIES\t(RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)\n+\n+static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)\n+{\n+\tstruct i2c_client *client = to_i2c_client(pse-\u003edev);\n+\n+\t/* Send opcode as SMBus command byte; remaining 11 bytes as block data */\n+\treturn i2c_smbus_write_i2c_block_data(client, req-\u003eopcode, RTPSE_MCU_MSG_SIZE - 1,\n+\t\t\t\t\t      (const u8 *)req + 1);\n+}\n+\n+static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,\n+\t\t\t\t    struct rtpse_mcu_msg *resp)\n+{\n+\tstruct i2c_client *client = to_i2c_client(pse-\u003edev);\n+\tint tries, ret;\n+\n+\tfor (tries = 0; tries \u003c RTPSE_MCU_I2C_MAX_TRIES; tries++) {\n+\t\tif (tries \u003e 0)\n+\t\t\tmsleep(RTPSE_MCU_I2C_RETRY_MS);\n+\n+\t\t/* MCU needs 0x00 as command byte for read */\n+\t\tret = i2c_smbus_read_i2c_block_data(client, 0x00,\n+\t\t\t\t\t\t    RTPSE_MCU_MSG_SIZE,\n+\t\t\t\t\t\t    (u8 *)resp);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif (ret == RTPSE_MCU_MSG_SIZE \u0026\u0026 rtpse_mcu_resp_is_final(req, resp))\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn -ETIMEDOUT;\n+}\n+\n+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {\n+\t.send = rtpse_mcu_i2c_smbus_send,\n+\t.recv = rtpse_mcu_i2c_smbus_recv,\n+};\n+\n+static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)\n+{\n+\tstruct i2c_client *client = to_i2c_client(pse-\u003edev);\n+\tint ret;\n+\n+\tret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\treturn ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;\n+}\n+\n+static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,\n+\t\t\t\t     struct rtpse_mcu_msg *resp)\n+{\n+\tstruct i2c_client *client = to_i2c_client(pse-\u003edev);\n+\tint tries, ret;\n+\n+\tfor (tries = 0; tries \u003c RTPSE_MCU_I2C_MAX_TRIES; tries++) {\n+\t\tif (tries \u003e 0)\n+\t\t\tmsleep(RTPSE_MCU_I2C_RETRY_MS);\n+\n+\t\tret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t\tif (ret == RTPSE_MCU_MSG_SIZE \u0026\u0026 rtpse_mcu_resp_is_final(req, resp))\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn -ETIMEDOUT;\n+}\n+\n+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {\n+\t.send = rtpse_mcu_i2c_native_send,\n+\t.recv = rtpse_mcu_i2c_native_recv,\n+};\n+\n+static int rtpse_mcu_i2c_probe(struct i2c_client *client)\n+{\n+\tstruct device *dev = \u0026client-\u003edev;\n+\tconst struct rtpse_mcu_match_data *match;\n+\tstruct rtpse_mcu_ctrl *pse;\n+\tbool use_native;\n+\n+\tmatch = device_get_match_data(dev);\n+\tif (!match)\n+\t\treturn dev_err_probe(dev, -ENODEV, \"missing match data\\n\");\n+\n+\t/* The framing (raw I2C vs SMBus) is carried by the match data. */\n+\tuse_native = match-\u003enative_i2c;\n+\tif (use_native) {\n+\t\tif (!i2c_check_functionality(client-\u003eadapter, I2C_FUNC_I2C))\n+\t\t\treturn dev_err_probe(dev, -EOPNOTSUPP,\n+\t\t\t\t\"plain-I2C MCU protocol requires I2C-capable adapter\\n\");\n+\t} else {\n+\t\tif (!i2c_check_functionality(client-\u003eadapter,\n+\t\t\t\t\t     I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |\n+\t\t\t\t\t     I2C_FUNC_SMBUS_READ_I2C_BLOCK))\n+\t\t\treturn dev_err_probe(dev, -EOPNOTSUPP,\n+\t\t\t\t\"SMBus MCU protocol requires SMBus I2C-block support\\n\");\n+\t}\n+\n+\tpse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);\n+\tif (!pse)\n+\t\treturn -ENOMEM;\n+\n+\tpse-\u003edev = dev;\n+\tpse-\u003epcdev.owner = THIS_MODULE;\n+\tpse-\u003etransport = use_native ? \u0026rtpse_mcu_i2c_native_ops : \u0026rtpse_mcu_i2c_smbus_ops;\n+\n+\treturn rtpse_mcu_register(pse);\n+}\n+\n+static const struct of_device_id rtpse_mcu_i2c_of_match[] = {\n+\t{ .compatible = \"realtek,pse-mcu-gen1-smbus\", .data = \u0026rtpse_mcu_gen1_data },\n+\t{ .compatible = \"realtek,pse-mcu-gen2-smbus\", .data = \u0026rtpse_mcu_gen2_data },\n+\t{ .compatible = \"realtek,pse-mcu-gen2-i2c\", .data = \u0026rtpse_mcu_gen2_i2c_data },\n+\t{ /* sentinel */ }\n+};\n+MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);\n+\n+static struct i2c_driver rtpse_mcu_i2c_driver = {\n+\t.driver = {\n+\t\t.name\t\t= \"realtek-pse-mcu-i2c\",\n+\t\t.of_match_table\t= rtpse_mcu_i2c_of_match,\n+\t},\n+\t.probe\t\t= rtpse_mcu_i2c_probe,\n+};\n+module_i2c_driver(rtpse_mcu_i2c_driver);\n+\n+MODULE_AUTHOR(\"Jonas Jelonek \u003cjelonek.jonas@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"Realtek PSE MCU driver (I2C transport)\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c\nnew file mode 100644\nindex 0000000000000..af8bd8ace00e9\n--- /dev/null\n+++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c\n@@ -0,0 +1,176 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include \u003clinux/cleanup.h\u003e\n+#include \u003clinux/completion.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/pse-pd/pse.h\u003e\n+#include \u003clinux/serdev.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \"realtek-pse-mcu.h\"\n+\n+#define RTPSE_MCU_UART_BAUD_DEFAULT\t19200\n+#define RTPSE_MCU_UART_TX_TIMEOUT\tmsecs_to_jiffies(100)\n+#define RTPSE_MCU_UART_RX_TIMEOUT\tmsecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS)\n+\n+struct rtpse_mcu_uart {\n+\tstruct rtpse_mcu_ctrl pse;\n+\tstruct serdev_device *serdev;\n+\tstruct completion rx_done;\n+\tspinlock_t rx_lock;\t\t/* protects rx_buf and rx_len */\n+\tsize_t rx_len;\n+\tu8 rx_buf[RTPSE_MCU_MSG_SIZE];\n+};\n+\n+#define to_rtpse_mcu_uart(p)  container_of(p, struct rtpse_mcu_uart, pse)\n+\n+/*\n+ * No framing is done here: a glitched frame costs one transaction, then\n+ * the next _send re-frames from rx_len 0. Resync works by returning count\n+ * (not take), dropping any overflow so serdev keeps no leftover to bleed\n+ * into the next frame.\n+ */\n+static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev,\n+\t\t\t\t     const u8 *buf, size_t count)\n+{\n+\tstruct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev);\n+\tsize_t take;\n+\n+\tscoped_guard(spinlock_irqsave, \u0026ctx-\u003erx_lock) {\n+\t\ttake = min(count, sizeof(ctx-\u003erx_buf) - ctx-\u003erx_len);\n+\t\tif (take) {\n+\t\t\tmemcpy(ctx-\u003erx_buf + ctx-\u003erx_len, buf, take);\n+\t\t\tctx-\u003erx_len += take;\n+\t\t\tif (ctx-\u003erx_len == sizeof(ctx-\u003erx_buf))\n+\t\t\t\tcomplete(\u0026ctx-\u003erx_done);\n+\t\t}\n+\t}\n+\n+\t/* consume all to avoid desync/misalignment */\n+\treturn count;\n+}\n+\n+static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = {\n+\t.receive_buf = rtpse_mcu_uart_receive,\n+\t.write_wakeup = serdev_device_write_wakeup,\n+};\n+\n+static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)\n+{\n+\tstruct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);\n+\tint written;\n+\n+\t/* clear any leftover rx state before transmitting */\n+\tscoped_guard(spinlock_irqsave, \u0026ctx-\u003erx_lock) {\n+\t\treinit_completion(\u0026ctx-\u003erx_done);\n+\t\tctx-\u003erx_len = 0;\n+\t}\n+\n+\twritten = serdev_device_write(ctx-\u003eserdev, (const u8 *)req, sizeof(*req),\n+\t\t\t\t      RTPSE_MCU_UART_TX_TIMEOUT);\n+\tif (written \u003c 0)\n+\t\treturn written;\n+\tif (written != sizeof(*req))\n+\t\treturn -EIO;\n+\n+\treturn 0;\n+}\n+\n+static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse,\n+\t\t\t       const struct rtpse_mcu_msg *req,\n+\t\t\t       struct rtpse_mcu_msg *resp)\n+{\n+\tstruct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);\n+\tunsigned long timeout = RTPSE_MCU_UART_RX_TIMEOUT;\n+\tbool final;\n+\n+\t/* Wait past a non-final frame (transient NOT_READY / stale) within budget. */\n+\tdo {\n+\t\ttimeout = wait_for_completion_timeout(\u0026ctx-\u003erx_done, timeout);\n+\t\tif (!timeout)\n+\t\t\treturn -ETIMEDOUT;\n+\n+\t\tscoped_guard(spinlock_irqsave, \u0026ctx-\u003erx_lock) {\n+\t\t\tif (ctx-\u003erx_len != sizeof(*resp))\n+\t\t\t\treturn -EIO;\n+\n+\t\t\tmemcpy(resp, ctx-\u003erx_buf, sizeof(*resp));\n+\t\t\tfinal = rtpse_mcu_resp_is_final(req, resp);\n+\t\t\tif (!final) {\n+\t\t\t\treinit_completion(\u0026ctx-\u003erx_done);\n+\t\t\t\tctx-\u003erx_len = 0;\n+\t\t\t}\n+\t\t}\n+\t} while (!final);\n+\n+\treturn 0;\n+}\n+\n+static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = {\n+\t.send = rtpse_mcu_uart_send,\n+\t.recv = rtpse_mcu_uart_recv,\n+};\n+\n+static int rtpse_mcu_uart_probe(struct serdev_device *serdev)\n+{\n+\tu32 speed = RTPSE_MCU_UART_BAUD_DEFAULT;\n+\tstruct device *dev = \u0026serdev-\u003edev;\n+\tstruct rtpse_mcu_uart *ctx;\n+\tunsigned int baud;\n+\tint ret;\n+\n+\tctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);\n+\tif (!ctx)\n+\t\treturn -ENOMEM;\n+\n+\tctx-\u003eserdev = serdev;\n+\tctx-\u003epse.dev = dev;\n+\tctx-\u003epse.pcdev.owner = THIS_MODULE;\n+\tctx-\u003epse.transport = \u0026rtpse_mcu_uart_transport_ops;\n+\tinit_completion(\u0026ctx-\u003erx_done);\n+\tspin_lock_init(\u0026ctx-\u003erx_lock);\n+\n+\tserdev_device_set_drvdata(serdev, ctx);\n+\tserdev_device_set_client_ops(serdev, \u0026rtpse_mcu_uart_serdev_ops);\n+\n+\tret = devm_serdev_device_open(dev, serdev);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to open serdev\\n\");\n+\n+\tfwnode_property_read_u32(dev_fwnode(dev), \"current-speed\", \u0026speed);\n+\n+\tbaud = serdev_device_set_baudrate(serdev, speed);\n+\tif (baud != speed)\n+\t\tdev_warn(dev, \"could not set baudrate %u, controller uses %u\\n\",\n+\t\t\t speed, baud);\n+\n+\tserdev_device_set_flow_control(serdev, false);\n+\n+\tret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);\n+\tif (ret)\n+\t\tdev_warn(dev, \"could not set parity to none: %d\\n\", ret);\n+\n+\treturn rtpse_mcu_register(\u0026ctx-\u003epse);\n+}\n+\n+static const struct of_device_id rtpse_mcu_uart_of_match[] = {\n+\t{ .compatible = \"realtek,pse-mcu-gen1\", .data = \u0026rtpse_mcu_gen1_data },\n+\t{ .compatible = \"realtek,pse-mcu-gen2\", .data = \u0026rtpse_mcu_gen2_data },\n+\t{ /* sentinel */ }\n+};\n+MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match);\n+\n+static struct serdev_device_driver rtpse_mcu_uart_driver = {\n+\t.driver = {\n+\t\t.name = \"realtek-pse-mcu-uart\",\n+\t\t.of_match_table = rtpse_mcu_uart_of_match,\n+\t},\n+\t.probe  = rtpse_mcu_uart_probe,\n+};\n+module_serdev_device_driver(rtpse_mcu_uart_driver);\n+\n+MODULE_AUTHOR(\"Jonas Jelonek \u003cjelonek.jonas@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"Realtek PSE MCU driver (UART transport)\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h\nnew file mode 100644\nindex 0000000000000..52f9442d7117c\n--- /dev/null\n+++ b/drivers/net/pse-pd/realtek-pse-mcu.h\n@@ -0,0 +1,93 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+\n+#ifndef _REALTEK_PSE_MCU_H\n+#define _REALTEK_PSE_MCU_H\n+\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/pse-pd/pse.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+/*\n+ * Time the MCU itself needs between accepting a request and having a\n+ * response ready. These are properties of the MCU firmware, not of the\n+ * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS\n+ * and both transports size their per-transaction recv ceiling from\n+ * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as\n+ * needing up to ~1s to produce a reply.\n+ */\n+#define RTPSE_MCU_RESPONSE_MS\t\t\t25\n+#define RTPSE_MCU_RESPONSE_MAX_MS\t\t1000\n+\n+/*\n+ * Total time to keep retrying the first MCU read at probe, and the pause\n+ * between attempts. Right after reset-gpios is deasserted the MCU may not\n+ * answer on the bus yet; give it a bounded window to come up before\n+ * declaring the probe failed.\n+ */\n+#define RTPSE_MCU_BOOT_TIMEOUT_MS\t\t3000\n+#define RTPSE_MCU_BOOT_RETRY_MS\t\t\t100\n+\n+#define RTPSE_MCU_MSG_SIZE\t\t\t12\n+\n+struct rtpse_mcu_msg {\n+\tu8 opcode;\n+\tu8 seq_num;\n+\tu8 payload[9];\n+\tu8 checksum;\n+} __packed;\n+\n+/*\n+ * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them).\n+ * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient.\n+ */\n+#define RTPSE_MCU_OPCODE_INCOMPLETE\t\t0xfd\t/* -EBADE   */\n+#define RTPSE_MCU_OPCODE_BAD_CSUM\t\t0xfe\t/* -EBADMSG */\n+#define RTPSE_MCU_OPCODE_NOT_READY\t\t0xff\t/* -EAGAIN  */\n+\n+/*\n+ * A polling transport can stop here: the reply to this request (opcode and\n+ * seq_num), or a terminal error. The seq_num rejects a stale reply left by an\n+ * abandoned transaction, instead of desyncing by one frame.\n+ */\n+static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req,\n+\t\t\t\t\t   const struct rtpse_mcu_msg *resp)\n+{\n+\treturn (resp-\u003eopcode == req-\u003eopcode \u0026\u0026 resp-\u003eseq_num == req-\u003eseq_num) ||\n+\t       resp-\u003eopcode == RTPSE_MCU_OPCODE_INCOMPLETE ||\n+\t       resp-\u003eopcode == RTPSE_MCU_OPCODE_BAD_CSUM;\n+}\n+\n+/* Opaque to transports; defined in realtek-pse-mcu-core.c. */\n+struct rtpse_mcu_dialect;\n+struct rtpse_mcu_chip_info;\n+struct rtpse_mcu_ctrl;\n+\n+/* Per-compatible match data (the of_match .data). */\n+struct rtpse_mcu_match_data {\n+\tconst struct rtpse_mcu_dialect *dialect;\n+\tbool native_i2c;\t/* raw-I2C framing (vs SMBus); I2C transport only */\n+};\n+\n+struct rtpse_mcu_transport_ops {\n+\tint (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req);\n+\tint (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,\n+\t\t    struct rtpse_mcu_msg *resp);\n+};\n+\n+struct rtpse_mcu_ctrl {\n+\tstruct device *dev;\n+\tstruct pse_controller_dev pcdev;\n+\tstruct mutex mutex;\t/* serializes MCU request/response transactions */\n+\tconst struct rtpse_mcu_dialect *dialect;\n+\tconst struct rtpse_mcu_chip_info *chip;\n+\tconst struct rtpse_mcu_transport_ops *transport;\n+\tu8 seq;\t\t\t/* rolling request seq_num, echoed by the MCU */\n+};\n+\n+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse);\n+\n+extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data;\n+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data;\n+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data;\n+\n+#endif\n"
}

2/1 2026/08/09 11:36 agent patch-evaluator
58m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a driver for the Realtek PSE MCU, which is an I2C/SMBus or UART attached microcontroller that manages PSE (Power Sourcing Equipment) hardware on PoE switches. The driver is instantiated via device tree or ACPI. In a virtualized environment like QEMU or GCE, there are no device tree nodes or ACPI tables for this specific hardware. While I2C devices can sometimes be instantiated via sysfs, the probe function of this driver relies on `device_get_match_data()`, which returns NULL for sysfs-instantiated devices because they lack a firmware node (fwnode). The driver does not have an `id_table` to fall back on, so the probe will always fail with -ENODEV. The UART (serdev) driver cannot be instantiated from userspace at all. Therefore, the core logic of this driver is unreachable in standard virtualized environments.",
  "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 5c95a968f972e76eed8e19342696d80302331fde
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Aug 9 11:36:00 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml
new file mode 100644
index 0000000000000..3bb32349c28c4
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml
@@ -0,0 +1,182 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu-gen1.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek PSE MCU
+
+maintainers:
+  - Jonas Jelonek <jelonek.jonas@gmail.com>
+
+description: |
+  A microcontroller (MCU) that manages the PSE (Power Sourcing Equipment)
+  hardware on a range of managed PoE switches. The host CPU talks only to
+  this MCU - over I2C/SMBus or UART - using a small message-based protocol;
+  the PSE silicon it drives sits behind the MCU and is never accessed
+  directly. For example, on the Zyxel GS1900-10HP the SoC reaches the MCU
+  over UART, and the MCU manages the on-board PSE chip.
+
+  This binding describes the MCU together with its Realtek firmware: the
+  firmware and its host protocol, which are stable across boards. The
+  microcontroller silicon is a general-purpose part that varies, and the
+  PSE silicon behind the MCU (Realtek RTL823x/RTL8239* or Broadcom
+  BCM59xxx) is reported by the MCU and detected at runtime - neither is
+  named here.
+
+  Two protocol generations exist, both Realtek's:
+    gen1  older boards, where the MCU fronts Broadcom PSE silicon
+    gen2  the altered protocol used with Realtek's own PSE silicon
+
+  On an I2C attachment the framing the MCU firmware expects is part of the
+  compatible: '-smbus' (reads carry a leading command byte and a repeated
+  start) or '-i2c' (bare block writes and reads). A UART attachment carries
+  no framing suffix; the transport is given by the parent 'serial' node.
+
+  Each board additionally carries a device-specific compatible that falls
+  back to one of the protocol compatibles above. Drivers bind on the
+  protocol compatible; the device-specific string identifies the board and
+  reserves a place for a future per-board quirk without having to retrofit
+  device trees already in the field.
+
+properties:
+  compatible:
+    oneOf:
+      # UART
+      - items:
+          - enum:
+              - zyxel,gs1900-10hp-a1-pse
+          - const: realtek,pse-mcu-gen1
+
+      # I2C, SMBus framing
+      - items:
+          - enum:
+              - zyxel,gs1920-24hp-v2-pse
+          - const: realtek,pse-mcu-gen1-smbus
+
+      # UART
+      - items:
+          - enum:
+              - zyxel,gs1900-10hp-b1-pse
+              - zyxel,xmg1915-10ep-pse
+          - const: realtek,pse-mcu-gen2
+
+      # I2C, SMBus framing
+      - items:
+          - enum:
+              - zyxel,xs1930-12hp-pse
+          - const: realtek,pse-mcu-gen2-smbus
+
+      # I2C, raw framing
+      - items:
+          - enum:
+              - linksys,lgs328mpc-v2-pse
+          - const: realtek,pse-mcu-gen2-i2c
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    description: Reset line of the MCU.
+    maxItems: 1
+
+  disable-ports-gpios:
+    description:
+      Hardware gate that forces all ports into admin-disabled state while
+      asserted.
+    maxItems: 1
+
+required:
+  - compatible
+
+allOf:
+  - $ref: pse-controller.yaml#
+  # A '-smbus'/'-i2c' compatible is an I2C attachment: it has 'reg' and
+  # cannot carry serial bus properties. A bare gen compatible is a UART
+  # attachment: no 'reg', the transport comes from the parent serial node.
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - realtek,pse-mcu-gen1-smbus
+              - realtek,pse-mcu-gen2-smbus
+              - realtek,pse-mcu-gen2-i2c
+    then:
+      required:
+        - reg
+      properties:
+        current-speed: false
+        max-speed: false
+    else:
+      allOf:
+        - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+      properties:
+        reg: false
+
+unevaluatedProperties: false
+
+examples:
+  # SMBus-framed I2C attachment
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-pse@20 {
+            compatible = "zyxel,xs1930-12hp-pse", "realtek,pse-mcu-gen2-smbus";
+            reg = <0x20>;
+
+            pse-pis {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                pse-pi@0 {
+                    reg = <0>;
+                    #pse-cells = <0>;
+                };
+            };
+        };
+    };
+
+  # Raw-I2C-framed attachment
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-pse@20 {
+            compatible = "linksys,lgs328mpc-v2-pse", "realtek,pse-mcu-gen2-i2c";
+            reg = <0x20>;
+
+            pse-pis {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                pse-pi@0 {
+                    reg = <0>;
+                    #pse-cells = <0>;
+                };
+            };
+        };
+    };
+
+  # UART attachment
+  - |
+    serial {
+        ethernet-pse {
+            compatible = "zyxel,gs1900-10hp-a1-pse", "realtek,pse-mcu-gen1";
+            current-speed = <19200>;
+
+            pse-pis {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                pse-pi@0 {
+                    reg = <0>;
+                    #pse-cells = <0>;
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 08e43bc09735d..26cfcd46b7c53 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22758,6 +22758,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/watchdog/realtek,otto-wdt.yaml
 F:	drivers/watchdog/realtek_otto_wdt.c
 
+REALTEK PSE MCU DRIVER
+M:	Jonas Jelonek <jelonek.jonas@gmail.com>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml
+F:	drivers/net/pse-pd/realtek-pse-mcu*
+
 REALTEK RTL83xx SMI DSA ROUTER CHIPS
 M:	Linus Walleij <linusw@kernel.org>
 M:	Luiz Angelo Daros de Luca <luizluca@gmail.com>
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 7ef29657ee5dd..a0f2ae668c67c 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -13,6 +13,34 @@ menuconfig PSE_CONTROLLER
 
 if PSE_CONTROLLER
 
+config PSE_REALTEK_MCU
+	tristate
+	help
+	  Shared core for the Realtek PSE MCU driver. This is selected
+	  automatically by the transport options below.
+
+config PSE_REALTEK_MCU_I2C
+	tristate "Realtek PSE MCU driver (I2C transport)"
+	depends on I2C
+	select PSE_REALTEK_MCU
+	help
+	  Driver for the microcontroller (MCU) that fronts the PSE
+	  hardware on various Realtek-based managed switches, attached
+	  via I2C/SMBus. The MCU exposes a message-based protocol; the actual
+	  PSE silicon is not accessed directly. To compile this driver as a
+	  module, choose M here: the module will be called realtek-pse-mcu-i2c.
+
+config PSE_REALTEK_MCU_UART
+	tristate "Realtek PSE MCU driver (UART transport)"
+	depends on SERIAL_DEV_BUS
+	select PSE_REALTEK_MCU
+	help
+	  Driver for the microcontroller (MCU) that fronts the PSE
+	  hardware on various Realtek-based managed switches, attached
+	  via UART. The MCU exposes a message-based protocol; the actual PSE
+	  silicon is not accessed directly. To compile this driver as a
+	  module, choose M here: the module will be called realtek-pse-mcu-uart.
+
 config PSE_REGULATOR
 	tristate "Regulator based PSE controller"
 	help
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index cc78f7ea7f5f7..9cca5900fe342 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -3,6 +3,9 @@
 
 obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
 
+obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
+obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
+obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o
 obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
 obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
 obj-$(CONFIG_PSE_SI3474) += si3474.o
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c
new file mode 100644
index 0000000000000..cb53686fcd96b
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c
@@ -0,0 +1,994 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for the microcontroller (MCU) fronting PSE silicon on various
+ * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame
+ * management protocol; this driver covers two generations of the
+ * protocol via a per-dialect opcode table and response parsers.
+ *
+ * Many PoE switch designs put a dedicated microcontroller in front of the
+ * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or
+ * UART, and the MCU in turn manages the PSE chips on the board. The MCU
+ * speaks a small message-based protocol. The PSE chips themselves are not
+ * accessed directly; everything goes through MCU commands.
+ *
+ * This driver targets that architecture for the Realtek-family protocol.
+ * Two generations are supported: Gen1 being used on older switches where
+ * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with
+ * Realtek PSE silicon. The two share frame format and a sum-mod-256
+ * checksum but diverge on opcode numbers and on a few response layouts;
+ * this is handled by the per-dialect opcode table and parser hooks.
+ *
+ * Out of scope: PSE chips that are interfaced directly from the host
+ * without a management MCU, MCU designs that speak an unrelated protocol
+ * family, and "dumb PSE" modes where no host control is wired up at all.
+ *
+ * This core module implements the protocol, decoding/encoding of MCU
+ * responses, and the pse_controller_ops integration. Transport modules
+ * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv
+ * callbacks.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/unaligned.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_DEVICE_ID_RTL8238B		0x0138
+#define RTPSE_MCU_DEVICE_ID_RTL8239		0x0039
+#define RTPSE_MCU_DEVICE_ID_RTL8239C		0x0139
+#define RTPSE_MCU_DEVICE_ID_BCM59111		0xe111
+#define RTPSE_MCU_DEVICE_ID_BCM59121		0xe121
+
+#define RTPSE_MCU_PORT_STS_DISABLED		0x00
+#define RTPSE_MCU_PORT_STS_SEARCHING		0x01
+#define RTPSE_MCU_PORT_STS_DELIVERING		0x02
+#define RTPSE_MCU_PORT_STS_TEST			0x03	/* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_FAULT		0x04
+#define RTPSE_MCU_PORT_STS_OTHER_FAULT		0x05	/* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_REQUESTING		0x06
+
+/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */
+#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER	0x02
+
+#define RTPSE_MCU_MAX_PORTS			48
+#define RTPSE_MCU_PORT_MAX_PRIORITY		3
+
+/* Nominal PSE rail; 802.3at/bt operating range. */
+#define RTPSE_MCU_PSE_VOLTAGE_UV		54000000
+
+enum rtpse_mcu_cmd {
+	RTPSE_MCU_CMD_SET_GLOBAL_STATE,
+	RTPSE_MCU_CMD_GET_SYSTEM_INFO,
+	RTPSE_MCU_CMD_GET_EXT_CONFIG,
+
+	RTPSE_MCU_CMD_PORT_ENABLE,
+	RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE,
+	RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+	RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+	RTPSE_MCU_CMD_PORT_SET_PRIORITY,
+	RTPSE_MCU_CMD_PORT_GET_STATUS,
+	RTPSE_MCU_CMD_PORT_GET_POWER_STATS,
+	RTPSE_MCU_CMD_PORT_GET_CONFIG,
+	RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG,
+
+	RTPSE_MCU_NUM_CMDS,
+};
+
+struct rtpse_mcu_opcode {
+	u8 op;
+	bool valid;
+};
+
+/* Shorthand for the designated-initializer entries in dialect opcode tables. */
+#define RTPSE_MCU_OP(opc)	{ .op = (opc), .valid = true }
+
+/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */
+
+struct rtpse_mcu_info {
+	u8 max_ports;
+	bool system_enable;
+	u16 device_id;
+	u8 mcu_type;
+};
+
+struct rtpse_mcu_ext_config {
+	u8 num_of_pses;
+};
+
+struct rtpse_mcu_port_status {
+	u8 sts1;
+	u8 sts2;
+	u8 sts3;
+};
+
+struct rtpse_mcu_port_measurement {
+	u16 voltage_raw;	/* 64.45mV/LSB */
+	u16 current_raw;	/* 1mA/LSB */
+	u16 temperature_raw;	/* T(mC) = 1250 * (220 - raw) */
+	u16 power_raw;		/* 100mW/LSB */
+};
+
+struct rtpse_mcu_port_config {
+	bool enable;
+};
+
+struct rtpse_mcu_port_ext_config {
+	u8 max_power;
+	u8 priority;
+};
+
+struct rtpse_mcu_dialect {
+	struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS];
+
+	/*
+	 * Response parsers for the fields that differ between dialects; each
+	 * dialect supplies its own. Other responses share one layout and are
+	 * decoded directly - a dialect that diverges there must add a hook,
+	 * as a mismatched layout cannot be detected (the checksum still passes).
+	 */
+	void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);
+	int (*parse_port_class)(const struct rtpse_mcu_port_status *status);
+	const char *(*mcu_type_str)(unsigned int mcu_type);
+};
+
+struct rtpse_mcu_chip_info {
+	const char *name;
+	u32 max_mW_per_port;
+	enum rtpse_mcu_cmd pw_set_cmd;	/* command used by set_pw_limit */
+	u32 pw_set_lsb_mW;		/* LSB of pw_set_cmd value, in mW */
+	u32 pw_read_lsb_mW;		/* LSB of ext_config.max_power read-back, in mW */
+};
+
+static const struct rtpse_mcu_chip_info rtl8238b_info = {
+	.max_mW_per_port = 30000,
+	.name = "RTL8238B",
+	.pw_read_lsb_mW = 200,
+	.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+	.pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239_info = {
+	.max_mW_per_port = 90000,
+	.name = "RTL8239",
+	.pw_read_lsb_mW = 400,
+	.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+	.pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239c_info = {
+	.max_mW_per_port = 90000,
+	.name = "RTL8239C",
+	.pw_read_lsb_mW = 400,
+	.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+	.pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info bcm59111_info = {
+	.max_mW_per_port = 30000,
+	.name = "BCM59111",
+	.pw_read_lsb_mW = 200,
+	.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+	.pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info bcm59121_info = {
+	/*
+	 * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at
+	 * and the Gen1 dialect has only the 8-bit/0.2W set command (<=51W);
+	 * cap at the 30W the hardware actually offers.
+	 */
+	.max_mW_per_port = 30000,
+	.name = "BCM59121",
+	.pw_read_lsb_mW = 200,
+	.pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+	.pw_set_lsb_mW = 200,
+};
+
+/* Helpers and basic functions */
+
+static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev)
+{
+	return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev);
+}
+
+static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode)
+{
+	memset(msg, 0xff, sizeof(*msg));
+	msg->opcode = opcode;
+}
+
+static u8 rtpse_mcu_checksum(const u8 *buf, size_t len)
+{
+	u8 sum = 0;
+
+	while (len--)
+		sum += *buf++;
+	return sum;
+}
+
+static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req,
+			     struct rtpse_mcu_msg *resp)
+{
+	int ret;
+
+	scoped_guard(mutex, &pse->mutex) {
+		/*
+		 * Rolling seq_num so a late reply can't pass as a later one;
+		 * skip 0 so an all-zero frame never validates as a real reply.
+		 */
+		if (++pse->seq == 0)
+			pse->seq = 1;
+		req->seq_num = pse->seq;
+		req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1);
+
+		ret = pse->transport->send(pse, req);
+		if (ret)
+			return ret;
+
+		/*
+		 * The MCU needs a fixed base time between accepting a request and
+		 * having a reply ready, independent of transport. Pace that here; the
+		 * transport then finishes recv in its own model - a bounded poll of
+		 * the response register (I2C) or one wait on a pushed frame (UART) -
+		 * so the retry belongs in the transport, not here.
+		 */
+		msleep(RTPSE_MCU_RESPONSE_MS);
+
+		memset(resp, 0, sizeof(*resp));
+		ret = pse->transport->recv(pse, req, resp);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * Explicit MCU error opcodes (observed on the Gen1 dialect; harmless
+	 * to check for Gen2 too). Catch these before the generic opcode/CRC
+	 * mismatch path so callers see a meaningful errno.
+	 */
+	switch (resp->opcode) {
+	case RTPSE_MCU_OPCODE_INCOMPLETE:
+		return -EBADE;
+	case RTPSE_MCU_OPCODE_BAD_CSUM:
+		return -EBADMSG;
+	case RTPSE_MCU_OPCODE_NOT_READY:
+		return -EAGAIN;
+	}
+
+	if (resp->opcode != req->opcode ||
+	    resp->seq_num != req->seq_num ||
+	    resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1))
+		return -EBADMSG;
+
+	return 0;
+}
+
+static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode,
+				struct rtpse_mcu_msg *resp)
+{
+	struct rtpse_mcu_msg req;
+	int ret;
+
+	rtpse_mcu_msg_init(&req, opcode);
+	req.payload[0] = port;
+
+	ret = rtpse_mcu_do_xfer(pse, &req, resp);
+	if (ret)
+		return ret;
+
+	if (resp->payload[0] != port)
+		return -EIO;
+
+	return 0;
+}
+
+static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg)
+{
+	struct rtpse_mcu_msg req, resp;
+	int ret;
+
+	rtpse_mcu_msg_init(&req, opcode);
+	req.payload[0] = port;
+	req.payload[1] = arg;
+
+	ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+	if (ret)
+		return ret;
+
+	if (resp.payload[0] != port || resp.payload[1] != 0)
+		return -EIO;
+
+	return 0;
+}
+
+/* Global operations */
+
+static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+	struct rtpse_mcu_msg req, resp;
+	const struct rtpse_mcu_opcode *opc;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	rtpse_mcu_msg_init(&req, opc->op);
+	ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+	if (ret)
+		return ret;
+
+	pse->dialect->parse_system_info(resp.payload, info);
+	return 0;
+}
+
+static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config)
+{
+	struct rtpse_mcu_msg req, resp;
+	const struct rtpse_mcu_opcode *opc;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	rtpse_mcu_msg_init(&req, opc->op);
+	ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+	if (ret)
+		return ret;
+
+	config->num_of_pses = resp.payload[6];
+
+	return 0;
+}
+
+static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable)
+{
+	struct rtpse_mcu_msg req, resp;
+	const struct rtpse_mcu_opcode *opc;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	rtpse_mcu_msg_init(&req, opc->op);
+	req.payload[0] = enable ? 0x1 : 0x0;
+
+	ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+	if (ret)
+		return ret;
+
+	return (resp.payload[0] == 0x0) ? 0 : -EIO;
+}
+
+/* Port operations */
+
+static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port,
+				     struct rtpse_mcu_port_status *status)
+{
+	const struct rtpse_mcu_opcode *opc;
+	struct rtpse_mcu_msg resp;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+	if (ret)
+		return ret;
+
+	status->sts1 = resp.payload[1];
+	status->sts2 = resp.payload[2];
+	status->sts3 = resp.payload[3];
+
+	return 0;
+}
+
+static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port,
+					  struct rtpse_mcu_port_measurement *measurement)
+{
+	const struct rtpse_mcu_opcode *opc;
+	struct rtpse_mcu_msg resp;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+	if (ret)
+		return ret;
+
+	measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]);
+	measurement->current_raw = get_unaligned_be16(&resp.payload[3]);
+	measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]);
+	measurement->power_raw = get_unaligned_be16(&resp.payload[7]);
+
+	return 0;
+}
+
+static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+				     struct rtpse_mcu_port_config *config)
+{
+	const struct rtpse_mcu_opcode *opc;
+	struct rtpse_mcu_msg resp;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+	if (ret)
+		return ret;
+
+	config->enable = (resp.payload[1] == 1);
+
+	return 0;
+}
+
+static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+					 struct rtpse_mcu_port_ext_config *config)
+{
+	const struct rtpse_mcu_opcode *opc;
+	struct rtpse_mcu_msg resp;
+	int ret;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+	if (ret)
+		return ret;
+
+	config->max_power = resp.payload[3];
+	config->priority = resp.payload[4];
+
+	return 0;
+}
+
+static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable)
+{
+	const struct rtpse_mcu_opcode *opc;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0);
+}
+
+/* PSE controller ops */
+
+static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id,
+					  struct pse_admin_state *admin_state)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_config config;
+	int ret;
+
+	ret = rtpse_mcu_port_get_config(pse, id, &config);
+	if (ret)
+		return ret;
+
+	admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED :
+						       ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+	return 0;
+}
+
+static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id,
+					struct pse_pw_status *pw_status)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_status status;
+	int ret;
+
+	ret = rtpse_mcu_port_get_status(pse, id, &status);
+	if (ret)
+		return ret;
+
+	switch (status.sts1) {
+	case RTPSE_MCU_PORT_STS_DISABLED:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+		break;
+	case RTPSE_MCU_PORT_STS_SEARCHING:
+	case RTPSE_MCU_PORT_STS_REQUESTING:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+		break;
+	case RTPSE_MCU_PORT_STS_DELIVERING:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+		break;
+	case RTPSE_MCU_PORT_STS_TEST:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST;
+		break;
+	case RTPSE_MCU_PORT_STS_FAULT:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+		break;
+	case RTPSE_MCU_PORT_STS_OTHER_FAULT:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT;
+		break;
+	default:
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_status status;
+	int ret;
+
+	ret = rtpse_mcu_port_get_status(pse, id, &status);
+	if (ret)
+		return ret;
+
+	/*
+	 * As per datasheet, the classification result is only valid when in
+	 * one of those operational modes, otherwise not.
+	 */
+	switch (status.sts1) {
+	case RTPSE_MCU_PORT_STS_DISABLED:
+	case RTPSE_MCU_PORT_STS_SEARCHING:
+	case RTPSE_MCU_PORT_STS_DELIVERING:
+	case RTPSE_MCU_PORT_STS_REQUESTING:
+		return pse->dialect->parse_port_class(&status);
+	default:
+		/*
+		 * No class to report, return 0 instead. This is indistinguishable
+		 * from a real class-0 PD but userspace disambiguates via the
+		 * power status.
+		 */
+		return 0;
+	}
+}
+
+static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_measurement measurement;
+	int ret;
+
+	ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+	if (ret)
+		return ret;
+
+	/* 100mW per LSB */
+	return measurement.power_raw * 100U;
+}
+
+static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_measurement measurement;
+	int ret;
+	u32 uV;
+
+	ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+	if (ret)
+		return ret;
+
+	/* 64.45mV per LSB */
+	uV = measurement.voltage_raw * 64450U;
+
+	/*
+	 * Idle ports measure 0V, which the core rejects when turning a power
+	 * limit into a current limit. Fall back to the nominal rail so a limit
+	 * can be set before a PD is attached.
+	 */
+	if (!uV)
+		return RTPSE_MCU_PSE_VOLTAGE_UV;
+
+	return min_t(u32, uV, INT_MAX);
+}
+
+static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id)
+{
+	return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true);
+}
+
+static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id)
+{
+	return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false);
+}
+
+static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_ext_config config;
+	int ret;
+
+	ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+	if (ret)
+		return ret;
+
+	return config.max_power * pse->chip->pw_read_lsb_mW;
+}
+
+static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW)
+{
+	const struct rtpse_mcu_opcode *type_opc, *val_opc;
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	const struct rtpse_mcu_chip_info *chip = pse->chip;
+	u8 prg_val;
+	int ret;
+
+	if (max_mW < 0 || max_mW > chip->max_mW_per_port)
+		return -ERANGE;
+
+	type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE];
+	val_opc = &pse->dialect->opcode[chip->pw_set_cmd];
+	/* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */
+	if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW)
+		return -EOPNOTSUPP;
+
+	/*
+	 * Switch the port to user-defined limit mode first, then program the
+	 * limit value. If the second cmd fails, the port is left in
+	 * user-defined mode but with the previous limit value; the next
+	 * successful set_pw_limit call recovers it.
+	 */
+	ret = rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER);
+	if (ret)
+		return ret;
+
+	/*
+	 * Round up so a sub-LSB request maps to one LSB, not silently to 0;
+	 * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot.
+	 */
+	prg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip->pw_set_lsb_mW), U8_MAX);
+
+	return rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val);
+}
+
+static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+					      struct pse_pw_limit_ranges *out)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct ethtool_c33_pse_pw_limit_range *range;
+
+	range = kzalloc_obj(*range);
+	if (!range)
+		return -ENOMEM;
+
+	range[0].min = 0;
+	range[0].max = pse->chip->max_mW_per_port;
+
+	out->c33_pw_limit_ranges = range;
+	return 1;
+}
+
+static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	struct rtpse_mcu_port_ext_config config;
+	int ret;
+
+	ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+	if (ret)
+		return ret;
+
+	return config.priority;
+}
+
+static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio)
+{
+	struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+	const struct rtpse_mcu_opcode *opc;
+
+	if (prio > RTPSE_MCU_PORT_MAX_PRIORITY)
+		return -ERANGE;
+
+	opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY];
+	if (!opc->valid)
+		return -EOPNOTSUPP;
+
+	return rtpse_mcu_port_cmd(pse, id, opc->op, prio);
+}
+
+static const struct pse_controller_ops rtpse_mcu_ops = {
+	.pi_get_admin_state = rtpse_mcu_port_get_admin_state,
+	.pi_get_pw_status = rtpse_mcu_port_get_pw_status,
+	.pi_get_pw_class = rtpse_mcu_port_get_pw_class,
+	.pi_get_actual_pw = rtpse_mcu_port_get_actual_pw,
+	.pi_enable = rtpse_mcu_port_enable,
+	.pi_disable = rtpse_mcu_port_disable,
+	.pi_get_voltage = rtpse_mcu_port_get_voltage,
+	.pi_get_pw_limit = rtpse_mcu_port_get_pw_limit,
+	.pi_set_pw_limit = rtpse_mcu_port_set_pw_limit,
+	.pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges,
+	.pi_get_prio = rtpse_mcu_port_get_prio,
+	.pi_set_prio = rtpse_mcu_port_set_prio,
+};
+
+static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+	struct rtpse_mcu_ext_config ext_config;
+	unsigned long deadline;
+	int ret;
+
+	/*
+	 * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address
+	 * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a
+	 * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a
+	 * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail
+	 * immediately.
+	 */
+	deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS);
+	do {
+		ret = rtpse_mcu_get_info(pse, info);
+		if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO &&
+		    ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE)
+			break;
+		msleep(RTPSE_MCU_BOOT_RETRY_MS);
+	} while (time_before(jiffies, deadline));
+	if (ret)
+		return dev_err_probe(pse->dev, ret, "failed to read MCU info\n");
+
+	switch (info->device_id) {
+	case RTPSE_MCU_DEVICE_ID_RTL8238B:
+		pse->chip = &rtl8238b_info;
+		break;
+	case RTPSE_MCU_DEVICE_ID_RTL8239:
+		pse->chip = &rtl8239_info;
+		break;
+	case RTPSE_MCU_DEVICE_ID_RTL8239C:
+		pse->chip = &rtl8239c_info;
+		break;
+	case RTPSE_MCU_DEVICE_ID_BCM59111:
+		pse->chip = &bcm59111_info;
+		break;
+	case RTPSE_MCU_DEVICE_ID_BCM59121:
+		pse->chip = &bcm59121_info;
+		break;
+	default:
+		return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n",
+				     info->device_id);
+	}
+
+	if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS)
+		return dev_err_probe(pse->dev, -EINVAL,
+				     "MCU reports invalid port count %u\n", info->max_ports);
+
+	ret = rtpse_mcu_get_ext_config(pse, &ext_config);
+	if (ret)
+		return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n");
+
+	dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n",
+		 pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name,
+		 info->device_id, info->max_ports, ext_config.num_of_pses);
+	return 0;
+}
+
+static void rtpse_mcu_global_disable(void *data)
+{
+	struct rtpse_mcu_ctrl *pse = data;
+
+	rtpse_mcu_set_global_state(pse, false);
+}
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
+{
+	const struct rtpse_mcu_match_data *match;
+	struct rtpse_mcu_info info;
+	struct gpio_desc *gpiod;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE);
+
+	ret = devm_mutex_init(pse->dev, &pse->mutex);
+	if (ret)
+		return ret;
+
+	match = device_get_match_data(pse->dev);
+	if (!match)
+		return dev_err_probe(pse->dev, -ENODEV, "missing match data\n");
+	pse->dialect = match->dialect;
+
+	/*
+	 * Catch a dialect that forgot to set one of the required hooks at
+	 * probe time, rather than NULL-deref'ing later from a fast path.
+	 */
+	if (!pse->dialect ||
+	    !pse->dialect->parse_system_info ||
+	    !pse->dialect->parse_port_class ||
+	    !pse->dialect->mcu_type_str)
+		return dev_err_probe(pse->dev, -EINVAL,
+				     "dialect for chip is incomplete\n");
+
+	/*
+	 * Release the MCU from reset before the first transaction; the
+	 * boot-retry loop in discover() waits for it to answer.
+	 */
+	gpiod = devm_gpiod_get_optional(pse->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(gpiod))
+		return dev_err_probe(pse->dev, PTR_ERR(gpiod),
+				     "failed to get reset gpio\n");
+
+	ret = rtpse_mcu_discover(pse, &info);
+	if (ret)
+		return ret;
+
+	/*
+	 * Some boards gate all ports through a hardware line; deassert it only
+	 * after the MCU is confirmed, so a discover failure never ungates the
+	 * ports. It is then left to the MCU - not re-gated on unbind - so a
+	 * driver reload doesn't black out PoE.
+	 */
+	gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW);
+	if (IS_ERR(gpiod))
+		return dev_err_probe(pse->dev, PTR_ERR(gpiod),
+				     "failed to get disable-ports gpio\n");
+
+	if (!info.system_enable) {
+		ret = rtpse_mcu_set_global_state(pse, true);
+		/* Dialects without a global-state concept (e.g. Gen1) return
+		 * -EOPNOTSUPP; treat that as "no separate enable required".
+		 */
+		if (ret && ret != -EOPNOTSUPP)
+			return dev_err_probe(pse->dev, ret,
+					     "failed to enable PSE system\n");
+		if (!ret) {
+			ret = devm_add_action_or_reset(pse->dev,
+						       rtpse_mcu_global_disable, pse);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/*
+	 * Depending on the MCU firmware configuration (which might be different
+	 * for every board), it isn't known whether the PoE subsystem is active or
+	 * inactive by default. At this stage, the PSE chips might already deliver
+	 * power to PDs without any explicit enable.
+	 */
+
+	/* pcdev.owner is set by the transport, so the registered controller
+	 * pins the transport module that owns the live device, not the core.
+	 */
+	pse->pcdev.ops      = &rtpse_mcu_ops;
+	pse->pcdev.dev      = pse->dev;
+	pse->pcdev.types    = ETHTOOL_PSE_C33;
+	pse->pcdev.nr_lines = info.max_ports;
+	pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY;
+	pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
+
+	return devm_pse_controller_register(pse->dev, &pse->pcdev);
+}
+EXPORT_SYMBOL_GPL(rtpse_mcu_register);
+
+static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+	info->max_ports = payload[1];
+	info->system_enable = (payload[2] == 0x1);
+	info->device_id = get_unaligned_be16(&payload[3]);
+	info->mcu_type = payload[6];
+}
+
+static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+	/* Class lives in the upper nibble of sts2. */
+	return FIELD_GET(GENMASK(7, 4), status->sts2);
+}
+
+static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type)
+{
+	switch (mcu_type) {
+	case 0x00:	return "GigaDevice GD32F310";
+	case 0x01:	return "GigaDevice GD32F230";
+	case 0x02:	return "GigaDevice GD32F303";
+	case 0x03:	return "GigaDevice GD32F103";
+	case 0x04:	return "GigaDevice GD32E103";
+	case 0x10:	return "Nuvoton M0516";
+	case 0x11:	return "Nuvoton M0564";
+	case 0x12:	return "Nuvoton NUC029";
+	default:	return "unknown";
+	}
+}
+
+static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+	info->max_ports = payload[1];
+	/* Gen1 has no explicit system_enable byte; the closest analog is the
+	 * "remote enable" bit in the system-status flags at payload[7].
+	 */
+	info->system_enable = !!(payload[7] & BIT(2));
+	info->device_id = get_unaligned_be16(&payload[3]);
+	info->mcu_type = payload[6];
+}
+
+static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+	/* Gen1 puts the detected class in payload[3] (== sts3) directly.
+	 * Mask to the low nibble; class is 0..8 and any high bits would be
+	 * noise.
+	 */
+	return status->sts3 & 0x0f;
+}
+
+static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type)
+{
+	switch (mcu_type) {
+	case 0x00:	return "ST Micro ST32F100";
+	case 0x01:	return "Nuvoton M05xx LAN";
+	case 0x02:	return "ST Micro STF030C8";
+	case 0x03:	return "Nuvoton M058SAN";
+	case 0x04:	return "Nuvoton NUC122";
+	default:	return "unknown";
+	}
+}
+
+/* Map each logical command the core issues to its per-dialect opcode. */
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = {
+	.parse_system_info = rtpse_mcu_gen2_parse_system_info,
+	.parse_port_class  = rtpse_mcu_gen2_parse_port_class,
+	.mcu_type_str      = rtpse_mcu_gen2_mcu_type_str,
+	.opcode = {
+		[RTPSE_MCU_CMD_SET_GLOBAL_STATE]	= RTPSE_MCU_OP(0x00),
+		[RTPSE_MCU_CMD_GET_SYSTEM_INFO]		= RTPSE_MCU_OP(0x40),
+		[RTPSE_MCU_CMD_GET_EXT_CONFIG]		= RTPSE_MCU_OP(0x4a),
+
+		[RTPSE_MCU_CMD_PORT_ENABLE]		= RTPSE_MCU_OP(0x01),
+		[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12),
+		[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]	= RTPSE_MCU_OP(0x13),
+		[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14),
+		[RTPSE_MCU_CMD_PORT_SET_PRIORITY]	= RTPSE_MCU_OP(0x15),
+		[RTPSE_MCU_CMD_PORT_GET_STATUS]		= RTPSE_MCU_OP(0x42),
+		[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]	= RTPSE_MCU_OP(0x44),
+		[RTPSE_MCU_CMD_PORT_GET_CONFIG]		= RTPSE_MCU_OP(0x48),
+		[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]	= RTPSE_MCU_OP(0x49),
+	},
+};
+
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = {
+	.parse_system_info = rtpse_mcu_gen1_parse_system_info,
+	.parse_port_class  = rtpse_mcu_gen1_parse_port_class,
+	.mcu_type_str      = rtpse_mcu_gen1_mcu_type_str,
+	.opcode = {
+		[RTPSE_MCU_CMD_GET_SYSTEM_INFO]		= RTPSE_MCU_OP(0x20),
+		[RTPSE_MCU_CMD_GET_EXT_CONFIG]		= RTPSE_MCU_OP(0x2b),
+
+		[RTPSE_MCU_CMD_PORT_ENABLE]		= RTPSE_MCU_OP(0x00),
+		[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),
+		[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT]	= RTPSE_MCU_OP(0x16),
+		[RTPSE_MCU_CMD_PORT_SET_PRIORITY]	= RTPSE_MCU_OP(0x1a),
+		[RTPSE_MCU_CMD_PORT_GET_STATUS]		= RTPSE_MCU_OP(0x21),
+		[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]	= RTPSE_MCU_OP(0x30),
+		[RTPSE_MCU_CMD_PORT_GET_CONFIG]		= RTPSE_MCU_OP(0x25),
+		[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]	= RTPSE_MCU_OP(0x26),
+	},
+};
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = {
+	.dialect = &rtpse_mcu_dialect_gen1,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data);
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = {
+	.dialect = &rtpse_mcu_dialect_gen2,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data);
+
+/* Same dialect as gen2, but the MCU expects raw-I2C framing. */
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = {
+	.dialect = &rtpse_mcu_dialect_gen2,
+	.native_i2c = true,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (core)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
new file mode 100644
index 0000000000000..b6769ac1d4707
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+
+#include "realtek-pse-mcu.h"
+
+/*
+ * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so
+ * the response is normally ready on the very first read. For commands the
+ * MCU produces more slowly, keep polling at the typical response cadence
+ * up to the worst-case ceiling.
+ */
+#define RTPSE_MCU_I2C_RETRY_MS	RTPSE_MCU_RESPONSE_MS
+#define RTPSE_MCU_I2C_MAX_TRIES	(RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)
+
+static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+	struct i2c_client *client = to_i2c_client(pse->dev);
+
+	/* Send opcode as SMBus command byte; remaining 11 bytes as block data */
+	return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1,
+					      (const u8 *)req + 1);
+}
+
+static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+				    struct rtpse_mcu_msg *resp)
+{
+	struct i2c_client *client = to_i2c_client(pse->dev);
+	int tries, ret;
+
+	for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+		if (tries > 0)
+			msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+		/* MCU needs 0x00 as command byte for read */
+		ret = i2c_smbus_read_i2c_block_data(client, 0x00,
+						    RTPSE_MCU_MSG_SIZE,
+						    (u8 *)resp);
+		if (ret < 0)
+			return ret;
+		if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {
+	.send = rtpse_mcu_i2c_smbus_send,
+	.recv = rtpse_mcu_i2c_smbus_recv,
+};
+
+static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+	struct i2c_client *client = to_i2c_client(pse->dev);
+	int ret;
+
+	ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE);
+	if (ret < 0)
+		return ret;
+	return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;
+}
+
+static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+				     struct rtpse_mcu_msg *resp)
+{
+	struct i2c_client *client = to_i2c_client(pse->dev);
+	int tries, ret;
+
+	for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+		if (tries > 0)
+			msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+		ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE);
+		if (ret < 0)
+			return ret;
+		if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {
+	.send = rtpse_mcu_i2c_native_send,
+	.recv = rtpse_mcu_i2c_native_recv,
+};
+
+static int rtpse_mcu_i2c_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	const struct rtpse_mcu_match_data *match;
+	struct rtpse_mcu_ctrl *pse;
+	bool use_native;
+
+	match = device_get_match_data(dev);
+	if (!match)
+		return dev_err_probe(dev, -ENODEV, "missing match data\n");
+
+	/* The framing (raw I2C vs SMBus) is carried by the match data. */
+	use_native = match->native_i2c;
+	if (use_native) {
+		if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+			return dev_err_probe(dev, -EOPNOTSUPP,
+				"plain-I2C MCU protocol requires I2C-capable adapter\n");
+	} else {
+		if (!i2c_check_functionality(client->adapter,
+					     I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+					     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+			return dev_err_probe(dev, -EOPNOTSUPP,
+				"SMBus MCU protocol requires SMBus I2C-block support\n");
+	}
+
+	pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);
+	if (!pse)
+		return -ENOMEM;
+
+	pse->dev = dev;
+	pse->pcdev.owner = THIS_MODULE;
+	pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops;
+
+	return rtpse_mcu_register(pse);
+}
+
+static const struct of_device_id rtpse_mcu_i2c_of_match[] = {
+	{ .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data },
+	{ .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data },
+	{ .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);
+
+static struct i2c_driver rtpse_mcu_i2c_driver = {
+	.driver = {
+		.name		= "realtek-pse-mcu-i2c",
+		.of_match_table	= rtpse_mcu_i2c_of_match,
+	},
+	.probe		= rtpse_mcu_i2c_probe,
+};
+module_i2c_driver(rtpse_mcu_i2c_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
new file mode 100644
index 0000000000000..af8bd8ace00e9
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/serdev.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_UART_BAUD_DEFAULT	19200
+#define RTPSE_MCU_UART_TX_TIMEOUT	msecs_to_jiffies(100)
+#define RTPSE_MCU_UART_RX_TIMEOUT	msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS)
+
+struct rtpse_mcu_uart {
+	struct rtpse_mcu_ctrl pse;
+	struct serdev_device *serdev;
+	struct completion rx_done;
+	spinlock_t rx_lock;		/* protects rx_buf and rx_len */
+	size_t rx_len;
+	u8 rx_buf[RTPSE_MCU_MSG_SIZE];
+};
+
+#define to_rtpse_mcu_uart(p)  container_of(p, struct rtpse_mcu_uart, pse)
+
+/*
+ * No framing is done here: a glitched frame costs one transaction, then
+ * the next _send re-frames from rx_len 0. Resync works by returning count
+ * (not take), dropping any overflow so serdev keeps no leftover to bleed
+ * into the next frame.
+ */
+static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev,
+				     const u8 *buf, size_t count)
+{
+	struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev);
+	size_t take;
+
+	scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+		take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len);
+		if (take) {
+			memcpy(ctx->rx_buf + ctx->rx_len, buf, take);
+			ctx->rx_len += take;
+			if (ctx->rx_len == sizeof(ctx->rx_buf))
+				complete(&ctx->rx_done);
+		}
+	}
+
+	/* consume all to avoid desync/misalignment */
+	return count;
+}
+
+static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = {
+	.receive_buf = rtpse_mcu_uart_receive,
+	.write_wakeup = serdev_device_write_wakeup,
+};
+
+static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+	struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+	int written;
+
+	/* clear any leftover rx state before transmitting */
+	scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+		reinit_completion(&ctx->rx_done);
+		ctx->rx_len = 0;
+	}
+
+	written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req),
+				      RTPSE_MCU_UART_TX_TIMEOUT);
+	if (written < 0)
+		return written;
+	if (written != sizeof(*req))
+		return -EIO;
+
+	return 0;
+}
+
+static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse,
+			       const struct rtpse_mcu_msg *req,
+			       struct rtpse_mcu_msg *resp)
+{
+	struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+	unsigned long timeout = RTPSE_MCU_UART_RX_TIMEOUT;
+	bool final;
+
+	/* Wait past a non-final frame (transient NOT_READY / stale) within budget. */
+	do {
+		timeout = wait_for_completion_timeout(&ctx->rx_done, timeout);
+		if (!timeout)
+			return -ETIMEDOUT;
+
+		scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+			if (ctx->rx_len != sizeof(*resp))
+				return -EIO;
+
+			memcpy(resp, ctx->rx_buf, sizeof(*resp));
+			final = rtpse_mcu_resp_is_final(req, resp);
+			if (!final) {
+				reinit_completion(&ctx->rx_done);
+				ctx->rx_len = 0;
+			}
+		}
+	} while (!final);
+
+	return 0;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = {
+	.send = rtpse_mcu_uart_send,
+	.recv = rtpse_mcu_uart_recv,
+};
+
+static int rtpse_mcu_uart_probe(struct serdev_device *serdev)
+{
+	u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT;
+	struct device *dev = &serdev->dev;
+	struct rtpse_mcu_uart *ctx;
+	unsigned int baud;
+	int ret;
+
+	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->serdev = serdev;
+	ctx->pse.dev = dev;
+	ctx->pse.pcdev.owner = THIS_MODULE;
+	ctx->pse.transport = &rtpse_mcu_uart_transport_ops;
+	init_completion(&ctx->rx_done);
+	spin_lock_init(&ctx->rx_lock);
+
+	serdev_device_set_drvdata(serdev, ctx);
+	serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops);
+
+	ret = devm_serdev_device_open(dev, serdev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to open serdev\n");
+
+	fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed);
+
+	baud = serdev_device_set_baudrate(serdev, speed);
+	if (baud != speed)
+		dev_warn(dev, "could not set baudrate %u, controller uses %u\n",
+			 speed, baud);
+
+	serdev_device_set_flow_control(serdev, false);
+
+	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+	if (ret)
+		dev_warn(dev, "could not set parity to none: %d\n", ret);
+
+	return rtpse_mcu_register(&ctx->pse);
+}
+
+static const struct of_device_id rtpse_mcu_uart_of_match[] = {
+	{ .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data },
+	{ .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match);
+
+static struct serdev_device_driver rtpse_mcu_uart_driver = {
+	.driver = {
+		.name = "realtek-pse-mcu-uart",
+		.of_match_table = rtpse_mcu_uart_of_match,
+	},
+	.probe  = rtpse_mcu_uart_probe,
+};
+module_serdev_device_driver(rtpse_mcu_uart_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h
new file mode 100644
index 0000000000000..52f9442d7117c
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _REALTEK_PSE_MCU_H
+#define _REALTEK_PSE_MCU_H
+
+#include <linux/mutex.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/types.h>
+
+/*
+ * Time the MCU itself needs between accepting a request and having a
+ * response ready. These are properties of the MCU firmware, not of the
+ * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS
+ * and both transports size their per-transaction recv ceiling from
+ * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as
+ * needing up to ~1s to produce a reply.
+ */
+#define RTPSE_MCU_RESPONSE_MS			25
+#define RTPSE_MCU_RESPONSE_MAX_MS		1000
+
+/*
+ * Total time to keep retrying the first MCU read at probe, and the pause
+ * between attempts. Right after reset-gpios is deasserted the MCU may not
+ * answer on the bus yet; give it a bounded window to come up before
+ * declaring the probe failed.
+ */
+#define RTPSE_MCU_BOOT_TIMEOUT_MS		3000
+#define RTPSE_MCU_BOOT_RETRY_MS			100
+
+#define RTPSE_MCU_MSG_SIZE			12
+
+struct rtpse_mcu_msg {
+	u8 opcode;
+	u8 seq_num;
+	u8 payload[9];
+	u8 checksum;
+} __packed;
+
+/*
+ * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them).
+ * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient.
+ */
+#define RTPSE_MCU_OPCODE_INCOMPLETE		0xfd	/* -EBADE   */
+#define RTPSE_MCU_OPCODE_BAD_CSUM		0xfe	/* -EBADMSG */
+#define RTPSE_MCU_OPCODE_NOT_READY		0xff	/* -EAGAIN  */
+
+/*
+ * A polling transport can stop here: the reply to this request (opcode and
+ * seq_num), or a terminal error. The seq_num rejects a stale reply left by an
+ * abandoned transaction, instead of desyncing by one frame.
+ */
+static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req,
+					   const struct rtpse_mcu_msg *resp)
+{
+	return (resp->opcode == req->opcode && resp->seq_num == req->seq_num) ||
+	       resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE ||
+	       resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM;
+}
+
+/* Opaque to transports; defined in realtek-pse-mcu-core.c. */
+struct rtpse_mcu_dialect;
+struct rtpse_mcu_chip_info;
+struct rtpse_mcu_ctrl;
+
+/* Per-compatible match data (the of_match .data). */
+struct rtpse_mcu_match_data {
+	const struct rtpse_mcu_dialect *dialect;
+	bool native_i2c;	/* raw-I2C framing (vs SMBus); I2C transport only */
+};
+
+struct rtpse_mcu_transport_ops {
+	int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req);
+	int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+		    struct rtpse_mcu_msg *resp);
+};
+
+struct rtpse_mcu_ctrl {
+	struct device *dev;
+	struct pse_controller_dev pcdev;
+	struct mutex mutex;	/* serializes MCU request/response transactions */
+	const struct rtpse_mcu_dialect *dialect;
+	const struct rtpse_mcu_chip_info *chip;
+	const struct rtpse_mcu_transport_ops *transport;
+	u8 seq;			/* rolling request seq_num, echoed by the MCU */
+};
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse);
+
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data;
+
+#endif

3/2 2026/08/09 11:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23924
							output: 20
							thoughts: 438
						

Thoughts:
**Assessing the Realtek PSE MCU Driver for Virtualization and Fuzzing**

Okay, so we have a new patch introducing a driver for the Realtek PSE MCU. It seems pretty straightforward: `drivers/net/pse-pd/realtek-pse-mcu-core.c`, `drivers/net/pse-pd/realtek-pse-mcu-i2c.c`, and `drivers/net/pse-pd/realtek-pse-mcu-uart.c`. This MCU manages the Power Sourcing Equipment on PoE switches, and the host talks to it via I2C/SMBus or UART. The driver implements `pse_controller_ops`, which is good.

My immediate question, as always, is whether we can actually test this effectively in a virtualized environment. Can we reach this in QEMU or GCE? It's an I2C and UART driver, so we're talking about hardware interactions. I2C is somewhat accessible – we could potentially emulate it or even use something like `i2c-dev` to inject traffic if we had an I2C adapter available. But, realistically, we probably *don't* have the specific hardware on hand, and we certainly don't have it running in our virtual machines.

Then I remember syzkaller. Could it help? I know syzkaller can fuzz I2C devices *if* the driver is instantiated. The key is instantiation. Does syzkaller offer a way to *instantiate* an I2C device? Can it inject I2C traffic to reach the driver? I thought maybe it had some sort of setup function, but I remember it's more about fuzzing a probed device. Maybe `syz_i2c_setup` is what I'm looking for? No, no I remember that syzkaller has some I2C fuzzing, but it assumes the device is probed.

Okay, so the question becomes: can we get this device probed in our virtualized setup? Let's check the device tree. Ah, it's a device tree-based driver, which is promising: we have `rtpse_mcu_i2c_of_match` and `rtpse_mcu_uart_of_match`.

But can syzkaller interact with device trees dynamically for I2C and UART? Does it allow injection of new device tree nodes, or maybe use `i2c_new_client_device` somehow? No, I'm pretty sure syzkaller doesn't do *that* dynamically, at least not in the general case, except maybe through some specific pseudo-syscalls... I need to check. Let me verify again about `syz_i2c_setup`. Is there something like `syz_i2c_client`? No, let's see, it looks like there *is* a `syz_i2c_setup`. Let's check!



4/2 2026/08/09 11:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "syz_i2c"
}

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

5/2 2026/08/09 11:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25025
							output: 21
							thoughts: 370
						

Thoughts:
**I2C Instantiation Strategy**

Okay, first things first, I need to remind myself that syzkaller doesn't have a direct `syz_i2c` call. But, wait... could this be done via sysfs? I remember you can instantiate I2C devices from userspace using `/sys/bus/i2c/devices/i2c-X/new_device`. Let me quickly confirm if that's available. Yes, perfect, `new_device` is indeed present, and it allows instantiating I2C devices by their name.

Now, the driver name I need is `realtek-pse-mcu-i2c`. Okay, let's just double check the `i2c_driver` struct, and yes, `.driver.name` is definitely set to `"realtek-pse-mcu-i2c"`.

Ah, here's the tricky part. I have to remember `new_device` uses the `i2c_device_id` table for matching, not the `of_match_table` like I initially thought. So, I need to check if the driver has an `id_table`. Looking at the code, it's clear:

```c
static struct i2c_driver rtpse_mcu_i2c_driver = {
	.driver = {
		.name		= "realtek-pse-mcu-i2c",
		.of_match_table	= rtpse_mcu_i2c_of_match,
	},
	.probe		= rtpse_mcu_i2c_probe,
};
```

Nope, there's no `id_table`! So, `new_device` uses `i2c_match_id()` which checks the `id_table`. The question then becomes: If there is no `id_table`, does it fall back to matching by the `driver.name`? That's what I need to figure out. Let's delve into the `i2c_device_match` function, located in `drivers/i2c/i2c-core-base.c`.



6/2 2026/08/09 11:36 tool grepper
0m Args:
{
  "Expression": "i2c_device_match"
}

Results:
{
  "Output": "drivers/i2c/i2c-core-base.c=138=EXPORT_SYMBOL(i2c_get_match_data);\ndrivers/i2c/i2c-core-base.c-139-\ndrivers/i2c/i2c-core-base.c:140:static int i2c_device_match(struct device *dev, const struct device_driver *drv)\ndrivers/i2c/i2c-core-base.c-141-{\n--\ndrivers/i2c/i2c-core-base.c=699=const struct bus_type i2c_bus_type = {\ndrivers/i2c/i2c-core-base.c-700-\t.name\t\t= \"i2c\",\ndrivers/i2c/i2c-core-base.c:701:\t.match\t\t= i2c_device_match,\ndrivers/i2c/i2c-core-base.c-702-\t.probe\t\t= i2c_device_probe,\n"
}

7/2 2026/08/09 11:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26162
							output: 43
							thoughts: 13
						

Thoughts:
Okay, let's dive into this. Here's my take on `i2c_device_match`:

**Analyzing `i2c_device_match`**

Alright, I'm going to take a look at the `i2c_device_match` function. As an expert in embedded systems, I immediately want to understand its role in the I2C driver model. My first thought is: this function is clearly crucial for identifying if a particular I2C device, likely described by a structure like `i2c_device_id` or similar, actually corresponds to a specific I2C adapter and address. I'm expecting this is probably the core logic for the probe/attach process of I2C devices. It will likely compare device identifiers, possibly vendor and product IDs, perhaps even revision numbers or similar, pulled from the I2C device on the bus, against a list of known supported devices. The goal is to determine a “match” according to some criteria. I need to see what factors it considers and how those factors translate to the returned "match" value, if any. I bet I'll find some interesting subtleties about how this function handles wildcard matches or versioning compatibility. I also anticipate it will consider how the I2C device driver’s code is architected to accommodate a variety of hardware configurations. The implementation's details about matching strategy will tell me a lot. Now, let’s get into the details…



8/2 2026/08/09 11:36 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-base.c",
  "FirstLine": 140,
  "LineCount": 30
}

Results:
{
  "Contents": " 140:\tstatic int i2c_device_match(struct device *dev, const struct device_driver *drv)\n 141:\t{\n 142:\t\tstruct i2c_client\t*client = i2c_verify_client(dev);\n 143:\t\tconst struct i2c_driver\t*driver;\n 144:\t\n 145:\t\n 146:\t\t/* Attempt an OF style match */\n 147:\t\tif (i2c_of_match_device(drv-\u003eof_match_table, client))\n 148:\t\t\treturn 1;\n 149:\t\n 150:\t\t/* Then ACPI style match */\n 151:\t\tif (acpi_driver_match_device(dev, drv))\n 152:\t\t\treturn 1;\n 153:\t\n 154:\t\tdriver = to_i2c_driver(drv);\n 155:\t\n 156:\t\t/* Finally an I2C match */\n 157:\t\tif (i2c_match_id(driver-\u003eid_table, client))\n 158:\t\t\treturn 1;\n 159:\t\n 160:\t\treturn 0;\n 161:\t}\n 162:\t\n 163:\tstatic int i2c_device_uevent(const struct device *dev, struct kobj_uevent_env *env)\n 164:\t{\n 165:\t\tconst struct i2c_client *client = to_i2c_client(dev);\n 166:\t\tint rc;\n 167:\t\n 168:\t\trc = of_device_uevent_modalias(dev, env);\n 169:\t\tif (rc != -ENODEV)\n"
}

9/2 2026/08/09 11:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26985
							output: 23
							thoughts: 98
						

Thoughts:
**Analyzing the I2C Device Matching Logic**

Okay, so I'm digging into this I2C device instantiation and matching process. My initial understanding was that `i2c_of_match_device` primarily relies on the presence of an OF (Device Tree) node for matching, which makes sense. I mean, it's right there in the name: "OF" - "of course". However, I'm thinking about the scenario where we're instantiating a device via `new_device`. In that case, unless we're dealing with Device Tree overlays – and let's be honest, they aren't always used – the device *won't* have an OF node initially. So, this raises a flag, a question mark, because I know there are other ways to match.

The question then becomes: Does `i2c_of_match_device` have other matching mechanisms built into it? Does it fall back to matching by device name against the "compatible" string, *sans* the vendor prefix, if an OF node isn't available? That's plausible, and it's a common strategy in these kinds of systems to have multiple fallbacks. The most logical thing to do is to go directly to the source code, right? Let me check the implementation of `i2c_of_match_device` and confirm how it handles these cases. The devil, as always, is in the details, especially in Linux kernel device driver code. Time to get my hands dirty.



10/2 2026/08/09 11:36 tool grepper
0m Args:
{
  "Expression": "i2c_of_match_device"
}

Results:
{
  "Output": "drivers/i2c/i2c-core-base.c=140=static int i2c_device_match(struct device *dev, const struct device_driver *drv)\n--\ndrivers/i2c/i2c-core-base.c-146-\t/* Attempt an OF style match */\ndrivers/i2c/i2c-core-base.c:147:\tif (i2c_of_match_device(drv-\u003eof_match_table, client))\ndrivers/i2c/i2c-core-base.c-148-\t\treturn 1;\n--\ndrivers/i2c/i2c-core-base.c=491=static int i2c_device_probe(struct device *dev)\n--\ndrivers/i2c/i2c-core-base.c-541-\t    !acpi_driver_match_device(dev, dev-\u003edriver) \u0026\u0026\ndrivers/i2c/i2c-core-base.c:542:\t    !i2c_of_match_device(dev-\u003edriver-\u003eof_match_table, client)) {\ndrivers/i2c/i2c-core-base.c-543-\t\tstatus = -ENODEV;\n--\ndrivers/i2c/i2c-core-of.c=115=static const struct of_device_id*\ndrivers/i2c/i2c-core-of.c:116:i2c_of_match_device_sysfs(const struct of_device_id *matches,\ndrivers/i2c/i2c-core-of.c-117-\t\t\t\t  struct i2c_client *client)\n--\ndrivers/i2c/i2c-core-of.c=144=const struct of_device_id\ndrivers/i2c/i2c-core-of.c:145:*i2c_of_match_device(const struct of_device_id *matches,\ndrivers/i2c/i2c-core-of.c-146-\t\t     struct i2c_client *client)\n--\ndrivers/i2c/i2c-core-of.c-156-\ndrivers/i2c/i2c-core-of.c:157:\treturn i2c_of_match_device_sysfs(matches, client);\ndrivers/i2c/i2c-core-of.c-158-}\n--\ndrivers/i2c/i2c-core.h=87=void of_i2c_register_devices(struct i2c_adapter *adap);\ndrivers/i2c/i2c-core.h:88:const struct of_device_id *i2c_of_match_device(const struct of_device_id *matches,\ndrivers/i2c/i2c-core.h-89-\t\t\t\t\t       struct i2c_client *client);\n--\ndrivers/i2c/i2c-core.h=93=static inline\ndrivers/i2c/i2c-core.h:94:const struct of_device_id *i2c_of_match_device(const struct of_device_id *matches,\ndrivers/i2c/i2c-core.h-95-\t\t\t\t\t       struct i2c_client *client)\n"
}

11/2 2026/08/09 11:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28247
							output: 43
							thoughts: 18
						

Thoughts:
Okay, let's dive into this `i2c_of_match_device_sysfs` function. I need to understand how this relates to my work with I2C device drivers and how it leverages the device tree.

**Analyzing i2c_of_match_device_sysfs**

Right, so this function is clearly about matching an I2C device based on the device tree and then managing its presence in the sysfs filesystem. I can see it's likely responsible for creating the device nodes within sysfs, allowing user-space applications or other kernel modules to interact with the device. It seems crucial for integrating devices described in the device tree smoothly into the overall system. I'll need to dissect its interactions with the I2C core, particularly how it uses the OF (Open Firmware) APIs to extract information.

I'm particularly interested in how it handles properties from the device tree node and maps them to the appropriate driver structures. Does it handle common properties like `compatible` string, I2C address, and interrupt specification correctly? I want to see how these are populated. I also need to understand how any error handling and resource management is handled.

Furthermore, I need to see if it allows for any specific sysfs attributes, like controlling power states or configuring the I2C device. Understanding its interactions with the `devm_` family of memory management functions will also be important to ensure proper resource cleanup when the device is unbound. Ultimately, my analysis will determine its robustness and efficiency, and also highlight any potential areas for improvement or optimization within my own driver development process. This is going to be helpful.



12/2 2026/08/09 11:36 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-of.c",
  "FirstLine": 115,
  "LineCount": 30
}

Results:
{
  "Contents": " 115:\tstatic const struct of_device_id*\n 116:\ti2c_of_match_device_sysfs(const struct of_device_id *matches,\n 117:\t\t\t\t\t  struct i2c_client *client)\n 118:\t{\n 119:\t\tconst char *name;\n 120:\t\n 121:\t\tfor (; matches-\u003ecompatible[0]; matches++) {\n 122:\t\t\t/*\n 123:\t\t\t * Adding devices through the i2c sysfs interface provides us\n 124:\t\t\t * a string to match which may be compatible with the device\n 125:\t\t\t * tree compatible strings, however with no actual of_node the\n 126:\t\t\t * of_match_device() will not match\n 127:\t\t\t */\n 128:\t\t\tif (sysfs_streq(client-\u003ename, matches-\u003ecompatible))\n 129:\t\t\t\treturn matches;\n 130:\t\n 131:\t\t\tname = strchr(matches-\u003ecompatible, ',');\n 132:\t\t\tif (!name)\n 133:\t\t\t\tname = matches-\u003ecompatible;\n 134:\t\t\telse\n 135:\t\t\t\tname++;\n 136:\t\n 137:\t\t\tif (sysfs_streq(client-\u003ename, name))\n 138:\t\t\t\treturn matches;\n 139:\t\t}\n 140:\t\n 141:\t\treturn NULL;\n 142:\t}\n 143:\t\n 144:\tconst struct of_device_id\n"
}

13/2 2026/08/09 11:36 llm patch-evaluator
5m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29050
							output: 29
							thoughts: 115
						

Thoughts:
**Device Instantiation Mysteries**

Okay, so I'm thinking about how devices get created, specifically within the I2C context. This `i2c_of_match_device_sysfs` function is key. It's brilliant – it gives us the power to match by the compatible string *or* just the part after the comma. So, if I write, say, `pse-mcu-gen1-smbus` to `/sys/bus/i2c/devices/i2c-X/new_device`, the system *should* make the match. That's fantastic.

But then my mind wanders… what about UART? UART devices are different; they're brought into the fold via `serdev`. The real question becomes: is there an analogous method for `serdev` devices? Can these UART devices also be instantiated from userspace in a comparable way? I need to dig into `serdev` and see if it has a similarly elegant mechanism for device creation. It’s time to check the documentation or the source code.



14/2 2026/08/09 11:41 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "new_device",
  "PathPrefix": "drivers/tty/serdev"
}

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

15/2 2026/08/09 11:41 llm patch-evaluator
10m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29433
							output: 34
							thoughts: 279
						

Thoughts:
**My Line of Reasoning**

Okay, so I'm thinking about how to approach this. Typically, with these sorts of devices, we're looking at instantiation through the device tree or ACPI. But then I remember that syzkaller has those built-in syscalls like `syz_usb_connect` and `syz_mac80211_setup`. Does it have something similar for device tree injection? No, it doesn't.

But then there's the I2C driver! I know that one *can* be instantiated via sysfs, right? That's a potential avenue. So, let's look at whether or not the I2C driver is even reachable. If we can get that I2C driver up and running, `rtpse_mcu_i2c_probe` should get called.

Alright, let's look at the `rtpse_mcu_i2c_probe` function now... Ah, there it is... We've got `device_get_match_data(dev)`. That's where we'll hit our first check. Wait a minute... Will `device_get_match_data(dev)` return NULL if the driver is instantiated via sysfs and not from device tree or ACPI? Seems likely. I need to make sure! Let's dive deeper and review the implementation of `device_get_match_data`.



16/2 2026/08/09 11:52 tool grepper
0m Args:
{
  "Expression": "device_get_match_data",
  "PathPrefix": "drivers/base/property.c"
}

Results:
{
  "Output": "drivers/base/property.c=1347=EXPORT_SYMBOL(fwnode_graph_parse_endpoint);\ndrivers/base/property.c-1348-\ndrivers/base/property.c:1349:const void *device_get_match_data(const struct device *dev)\ndrivers/base/property.c-1350-{\ndrivers/base/property.c:1351:\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\ndrivers/base/property.c-1352-}\ndrivers/base/property.c:1353:EXPORT_SYMBOL_GPL(device_get_match_data);\ndrivers/base/property.c-1354-\n"
}

17/2 2026/08/09 11:52 llm patch-evaluator
6m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30249
							output: 36
							thoughts: 81
						

Thoughts:
**Analyzing the Match Data Retrieval**

Okay, so I'm digging into this code and something's immediately jumping out at me. The function `device_get_match_data` calls `fwnode_call_ptr_op` with `dev_fwnode(dev)` as the first argument. Right. Now, if the device in question was brought into existence through sysfs, it's not going to have a fwnode, is it? It's the whole point of sysfs, it abstracts away the firmware node.

Therefore, `device_get_match_data` is essentially going to be passed a NULL fwnode! And, if that happens, the function's clearly going to return NULL, right? We need to verify that this is actually what's happening. I need to make sure this assumption about NULL return for sysfs-instantiated devices is correct, as this has potential knock-on effects for how device matching is handled. Let me double-check the implementation, and run some tests.



18/2 2026/08/09 11:58 tool read-file
0m Args:
{
  "File": "drivers/base/property.c",
  "FirstLine": 1349,
  "LineCount": 20
}

Results:
{
  "Contents": "1349:\tconst void *device_get_match_data(const struct device *dev)\n1350:\t{\n1351:\t\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\n1352:\t}\n1353:\tEXPORT_SYMBOL_GPL(device_get_match_data);\n1354:\t\n1355:\tstatic unsigned int fwnode_graph_devcon_matches(const struct fwnode_handle *fwnode,\n1356:\t\t\t\t\t\t\tconst char *con_id, void *data,\n1357:\t\t\t\t\t\t\tdevcon_match_fn_t match,\n1358:\t\t\t\t\t\t\tvoid **matches,\n1359:\t\t\t\t\t\t\tunsigned int matches_len)\n1360:\t{\n1361:\t\tstruct fwnode_handle *node;\n1362:\t\tstruct fwnode_handle *ep;\n1363:\t\tunsigned int count = 0;\n1364:\t\tvoid *ret;\n1365:\t\n1366:\t\tfwnode_graph_for_each_endpoint(fwnode, ep) {\n1367:\t\t\tif (matches \u0026\u0026 count \u003e= matches_len) {\n1368:\t\t\t\tfwnode_handle_put(ep);\n"
}

19/2 2026/08/09 11:58 llm patch-evaluator
3m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30928
							output: 44
							thoughts: 49
						

Thoughts:
Okay, let's see. My first thought is that there might be something important happening within `i2c_device_probe`, specifically within the `drivers/i2c/i2c-core-base.c` file. It's plausible that this is where the match data is actually being set.

So, I need to dive into `i2c_device_probe` now. I need to take a look and see how that function works to confirm or deny my suspicion. Let's see what's actually going on in there. Time to inspect the code.



20/2 2026/08/09 12:01 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-base.c",
  "FirstLine": 491,
  "LineCount": 100
}

Results:
{
  "Contents": " 491:\tstatic int i2c_device_probe(struct device *dev)\n 492:\t{\n 493:\t\tstruct fwnode_handle\t*fwnode = dev_fwnode(dev);\n 494:\t\tstruct i2c_client\t*client = i2c_verify_client(dev);\n 495:\t\tstruct i2c_driver\t*driver;\n 496:\t\tbool do_power_on;\n 497:\t\tint status;\n 498:\t\n 499:\t\tif (!client)\n 500:\t\t\treturn 0;\n 501:\t\n 502:\t\tclient-\u003eirq = client-\u003einit_irq;\n 503:\t\n 504:\t\tif (!client-\u003eirq) {\n 505:\t\t\tint irq = -ENOENT;\n 506:\t\n 507:\t\t\tif (client-\u003eflags \u0026 I2C_CLIENT_HOST_NOTIFY) {\n 508:\t\t\t\tdev_dbg(dev, \"Using Host Notify IRQ\\n\");\n 509:\t\t\t\t/* Keep adapter active when Host Notify is required */\n 510:\t\t\t\tpm_runtime_get_sync(\u0026client-\u003eadapter-\u003edev);\n 511:\t\t\t\tirq = i2c_smbus_host_notify_to_irq(client);\n 512:\t\t\t} else if (is_of_node(fwnode)) {\n 513:\t\t\t\tirq = fwnode_irq_get_byname(fwnode, \"irq\");\n 514:\t\t\t\tif (irq == -EINVAL || irq == -ENODATA)\n 515:\t\t\t\t\tirq = fwnode_irq_get(fwnode, 0);\n 516:\t\t\t} else if (is_acpi_device_node(fwnode)) {\n 517:\t\t\t\tbool wake_capable;\n 518:\t\n 519:\t\t\t\tirq = i2c_acpi_get_irq(client, \u0026wake_capable);\n 520:\t\t\t\tif (irq \u003e 0 \u0026\u0026 wake_capable)\n 521:\t\t\t\t\tclient-\u003eflags |= I2C_CLIENT_WAKE;\n 522:\t\t\t}\n 523:\t\t\tif (irq == -EPROBE_DEFER) {\n 524:\t\t\t\tstatus = dev_err_probe(dev, irq, \"can't get irq\\n\");\n 525:\t\t\t\tgoto put_sync_adapter;\n 526:\t\t\t}\n 527:\t\n 528:\t\t\tif (irq \u003c 0)\n 529:\t\t\t\tirq = 0;\n 530:\t\n 531:\t\t\tclient-\u003eirq = irq;\n 532:\t\t}\n 533:\t\n 534:\t\tdriver = to_i2c_driver(dev-\u003edriver);\n 535:\t\n 536:\t\t/*\n 537:\t\t * An I2C ID table is not mandatory, if and only if, a suitable OF\n 538:\t\t * or ACPI ID table is supplied for the probing device.\n 539:\t\t */\n 540:\t\tif (!driver-\u003eid_table \u0026\u0026\n 541:\t\t    !acpi_driver_match_device(dev, dev-\u003edriver) \u0026\u0026\n 542:\t\t    !i2c_of_match_device(dev-\u003edriver-\u003eof_match_table, client)) {\n 543:\t\t\tstatus = -ENODEV;\n 544:\t\t\tgoto put_sync_adapter;\n 545:\t\t}\n 546:\t\n 547:\t\tif (client-\u003eflags \u0026 I2C_CLIENT_WAKE) {\n 548:\t\t\tint wakeirq;\n 549:\t\n 550:\t\t\twakeirq = fwnode_irq_get_byname(fwnode, \"wakeup\");\n 551:\t\t\tif (wakeirq == -EPROBE_DEFER) {\n 552:\t\t\t\tstatus = dev_err_probe(dev, wakeirq, \"can't get wakeirq\\n\");\n 553:\t\t\t\tgoto put_sync_adapter;\n 554:\t\t\t}\n 555:\t\n 556:\t\t\tdevice_init_wakeup(\u0026client-\u003edev, true);\n 557:\t\n 558:\t\t\tif (wakeirq \u003e 0 \u0026\u0026 wakeirq != client-\u003eirq)\n 559:\t\t\t\tstatus = dev_pm_set_dedicated_wake_irq(dev, wakeirq);\n 560:\t\t\telse if (client-\u003eirq \u003e 0)\n 561:\t\t\t\tstatus = dev_pm_set_wake_irq(dev, client-\u003eirq);\n 562:\t\t\telse\n 563:\t\t\t\tstatus = 0;\n 564:\t\n 565:\t\t\tif (status)\n 566:\t\t\t\tdev_warn(\u0026client-\u003edev, \"failed to set up wakeup irq\\n\");\n 567:\t\t}\n 568:\t\n 569:\t\tdev_dbg(dev, \"probe\\n\");\n 570:\t\n 571:\t\tstatus = of_clk_set_defaults(to_of_node(fwnode), false);\n 572:\t\tif (status \u003c 0)\n 573:\t\t\tgoto err_clear_wakeup_irq;\n 574:\t\n 575:\t\tdo_power_on = !i2c_acpi_waive_d0_probe(dev);\n 576:\t\tstatus = dev_pm_domain_attach(\u0026client-\u003edev, PD_FLAG_DETACH_POWER_OFF |\n 577:\t\t\t\t\t      (do_power_on ? PD_FLAG_ATTACH_POWER_ON : 0));\n 578:\t\tif (status)\n 579:\t\t\tgoto err_clear_wakeup_irq;\n 580:\t\n 581:\t\tclient-\u003edevres_group_id = devres_open_group(\u0026client-\u003edev, NULL,\n 582:\t\t\t\t\t\t\t    GFP_KERNEL);\n 583:\t\tif (!client-\u003edevres_group_id) {\n 584:\t\t\tstatus = -ENOMEM;\n 585:\t\t\tgoto err_clear_wakeup_irq;\n 586:\t\t}\n 587:\t\n 588:\t\tclient-\u003edebugfs = debugfs_create_dir(dev_name(\u0026client-\u003edev),\n 589:\t\t\t\t\t\t     client-\u003eadapter-\u003edebugfs);\n 590:\t\n"
}

21/2 2026/08/09 12:01 llm patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32798
							output: 23
							thoughts: 142
						

Thoughts:
**Analyzing Device Matching in I2C Driver Initialization**

Okay, so `i2c_of_match_device` gives me the `of_device_id` that matched, that's straightforward. But, I'm just doing a quick mental walk-through of the code path.  `device_get_match_data` is what I really want to focus on, and that uses `fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev)`.  Right, so `dev_fwnode(dev)` is the crucial piece. If that's NULL, which it can be, then `fwnode_call_ptr_op` is going to return NULL as well. I need to be careful with that. I'm recalling now that I2C devices created through sysfs, for example, won't have an fwnode. That will lead to a NULL return for those devices. Hmm, makes sense. I need to figure out how to retrieve the match data for I2C devices created in this scenario. Time to check `i2c_get_match_data` now, I reckon that will handle this use case.



22/2 2026/08/09 12:03 tool grepper
0m Args:
{
  "Expression": "i2c_get_match_data"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 1303 lines.\nUse more precise expression if possible.\n\ndrivers/auxdisplay/ht16k33.c=657=static int ht16k33_probe(struct i2c_client *client)\n--\ndrivers/auxdisplay/ht16k33.c-673-\tpriv-\u003eclient = client;\ndrivers/auxdisplay/ht16k33.c:674:\tpriv-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/auxdisplay/ht16k33.c-675-\n--\ndrivers/char/tpm/tpm_i2c_nuvoton.c=527=static int i2c_nuvoton_probe(struct i2c_client *client)\n--\ndrivers/char/tpm/tpm_i2c_nuvoton.c-549-\ndrivers/char/tpm/tpm_i2c_nuvoton.c:550:\tif (i2c_get_match_data(client))\ndrivers/char/tpm/tpm_i2c_nuvoton.c-551-\t\tchip-\u003eflags |= TPM_CHIP_FLAG_TPM2;\n--\ndrivers/clk/clk-cdce925.c=617=static int cdce925_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-cdce925.c-650-\tdata-\u003ei2c_client = client;\ndrivers/clk/clk-cdce925.c:651:\tdata-\u003echip_info = i2c_get_match_data(client);\ndrivers/clk/clk-cdce925.c-652-\tconfig.max_register = CDCE925_OFFSET_PLL +\n--\ndrivers/clk/clk-renesas-pcie.c=282=static int rs9_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-renesas-pcie.c-295-\trs9-\u003eclient = client;\ndrivers/clk/clk-renesas-pcie.c:296:\trs9-\u003echip_info = i2c_get_match_data(client);\ndrivers/clk/clk-renesas-pcie.c-297-\tif (!rs9-\u003echip_info)\n--\ndrivers/clk/clk-si521xx.c=281=static int si521xx_probe(struct i2c_client *client)\ndrivers/clk/clk-si521xx.c-282-{\ndrivers/clk/clk-si521xx.c:283:\tconst u16 chip_info = (u16)(uintptr_t)i2c_get_match_data(client);\ndrivers/clk/clk-si521xx.c-284-\tconst struct clk_parent_data clk_parent_data = { .index = 0 };\n--\ndrivers/clk/clk-si5351.c=1436=static int si5351_i2c_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-si5351.c-1445-\ndrivers/clk/clk-si5351.c:1446:\tvariant = (enum si5351_variant)(uintptr_t)i2c_get_match_data(client);\ndrivers/clk/clk-si5351.c-1447-\tret = si5351_dt_parse(client, variant);\n--\ndrivers/clk/clk-si544.c=434=static int si544_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-si544.c-448-\tdata-\u003ei2c_client = client;\ndrivers/clk/clk-si544.c:449:\tdata-\u003emax_freq = (uintptr_t)i2c_get_match_data(client);\ndrivers/clk/clk-si544.c-450-\n--\ndrivers/clk/clk-si570.c=412=static int si570_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-si570.c-429-\ndrivers/clk/clk-si570.c:430:\tdata-\u003einfo = i2c_get_match_data(client);\ndrivers/clk/clk-si570.c-431-\tif (data-\u003einfo-\u003ehas_temperature_stability) {\n--\ndrivers/clk/clk-versaclock3.c=997=static int vc3_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-versaclock3.c-1048-\ndrivers/clk/clk-versaclock3.c:1049:\tdata = i2c_get_match_data(client);\ndrivers/clk/clk-versaclock3.c-1050-\n--\ndrivers/clk/clk-versaclock5.c=951=static int vc5_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-versaclock5.c-965-\tvc5-\u003eclient = client;\ndrivers/clk/clk-versaclock5.c:966:\tvc5-\u003echip_info = i2c_get_match_data(client);\ndrivers/clk/clk-versaclock5.c-967-\n--\ndrivers/clk/clk-versaclock7.c=1100=static int vc7_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-versaclock7.c-1116-\tvc7-\u003eclient = client;\ndrivers/clk/clk-versaclock7.c:1117:\tvc7-\u003echip_info = i2c_get_match_data(client);\ndrivers/clk/clk-versaclock7.c-1118-\n--\ndrivers/crypto/atmel-sha204a.c=164=static int atmel_sha204a_probe(struct i2c_client *client)\n--\ndrivers/crypto/atmel-sha204a.c-180-\ndrivers/crypto/atmel-sha204a.c:181:\tquality = i2c_get_match_data(client);\ndrivers/crypto/atmel-sha204a.c-182-\tif (quality)\n--\ndrivers/gpio/gpio-pca953x.c=1192=static int pca953x_probe(struct i2c_client *client)\n--\ndrivers/gpio/gpio-pca953x.c-1228-\tchip-\u003eclient = client;\ndrivers/gpio/gpio-pca953x.c:1229:\tchip-\u003edriver_data = (uintptr_t)i2c_get_match_data(client);\ndrivers/gpio/gpio-pca953x.c-1230-\tif (!chip-\u003edriver_data)\n--\ndrivers/gpio/gpio-pcf857x.c=277=static int pcf857x_probe(struct i2c_client *client)\n--\ndrivers/gpio/gpio-pcf857x.c-300-\tgpio-\u003echip.direction_output\t= pcf857x_output;\ndrivers/gpio/gpio-pcf857x.c:301:\tgpio-\u003echip.ngpio\t\t= (uintptr_t)i2c_get_match_data(client);\ndrivers/gpio/gpio-pcf857x.c-302-\n--\ndrivers/gpu/drm/bridge/adv7511/adv7511_drv.c=1229=static int adv7511_probe(struct i2c_client *i2c)\n--\ndrivers/gpu/drm/bridge/adv7511/adv7511_drv.c-1247-\tadv7511-\u003estatus = connector_status_disconnected;\ndrivers/gpu/drm/bridge/adv7511/adv7511_drv.c:1248:\tadv7511-\u003einfo = i2c_get_match_data(i2c);\ndrivers/gpu/drm/bridge/adv7511/adv7511_drv.c-1249-\n--\ndrivers/gpu/drm/bridge/waveshare-dsi.c=144=static int ws_bridge_probe(struct i2c_client *i2c)\n--\ndrivers/gpu/drm/bridge/waveshare-dsi.c-179-\ndrivers/gpu/drm/bridge/waveshare-dsi.c:180:\tws-\u003ebridge.type = (uintptr_t)i2c_get_match_data(i2c);\ndrivers/gpu/drm/bridge/waveshare-dsi.c-181-\tws-\u003ebridge.of_node = dev-\u003eof_node;\n--\ndrivers/hwmon/ad7418.c=233=static int ad7418_probe(struct i2c_client *client)\n--\ndrivers/hwmon/ad7418.c-252-\tdata-\u003eclient = client;\ndrivers/hwmon/ad7418.c:253:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/ad7418.c-254-\n--\ndrivers/hwmon/adm1031.c=1024=static int adm1031_probe(struct i2c_client *client)\n--\ndrivers/hwmon/adm1031.c-1035-\tdata-\u003eclient = client;\ndrivers/hwmon/adm1031.c:1036:\tdata-\u003echip_type = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/adm1031.c-1037-\tmutex_init(\u0026data-\u003eupdate_lock);\n--\ndrivers/hwmon/ads7828.c=102=static int ads7828_probe(struct i2c_client *client)\n--\ndrivers/hwmon/ads7828.c-138-\ndrivers/hwmon/ads7828.c:139:\tchip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/ads7828.c-140-\n--\ndrivers/hwmon/adt7475.c=1805=static int adt7475_probe(struct i2c_client *client)\n--\ndrivers/hwmon/adt7475.c-1827-\ndrivers/hwmon/adt7475.c:1828:\tchip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/adt7475.c-1829-\n--\ndrivers/hwmon/aht10.c=332=static int aht10_probe(struct i2c_client *client)\ndrivers/hwmon/aht10.c-333-{\ndrivers/hwmon/aht10.c:334:\tenum aht10_variant variant = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/aht10.c-335-\tstruct device *device = \u0026client-\u003edev;\n--\ndrivers/hwmon/dme1737.c=2464=static int dme1737_i2c_probe(struct i2c_client *client)\n--\ndrivers/hwmon/dme1737.c-2474-\ti2c_set_clientdata(client, data);\ndrivers/hwmon/dme1737.c:2475:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/dme1737.c-2476-\tdata-\u003eclient = client;\n--\ndrivers/hwmon/ds1621.c=345=static int ds1621_probe(struct i2c_client *client)\n--\ndrivers/hwmon/ds1621.c-356-\ndrivers/hwmon/ds1621.c:357:\tdata-\u003ekind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/ds1621.c-358-\tdata-\u003eclient = client;\n--\ndrivers/hwmon/emc1812.c=824=static int emc1812_chip_identify(struct emc1812_data *data, struct i2c_client *client)\n--\ndrivers/hwmon/emc1812.c-856-\t\t */\ndrivers/hwmon/emc1812.c:857:\t\tchip = i2c_get_match_data(client);\ndrivers/hwmon/emc1812.c-858-\t\tif (!chip)\n--\ndrivers/hwmon/f75375s.c=791=static int f75375_probe(struct i2c_client *client)\n--\ndrivers/hwmon/f75375s.c-807-\tmutex_init(\u0026data-\u003eupdate_lock);\ndrivers/hwmon/f75375s.c:808:\tdata-\u003ekind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/f75375s.c-809-\n--\ndrivers/hwmon/fschmd.c=1082=static int fschmd_probe(struct i2c_client *client)\n--\ndrivers/hwmon/fschmd.c-1088-\tint i, err;\ndrivers/hwmon/fschmd.c:1089:\tenum chips kind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/fschmd.c-1090-\n--\ndrivers/hwmon/ina238.c=883=static int ina238_probe(struct i2c_client *client)\n--\ndrivers/hwmon/ina238.c-891-\ndrivers/hwmon/ina238.c:892:\tchip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/ina238.c-893-\n--\ndrivers/hwmon/ina2xx.c=961=static int ina2xx_probe(struct i2c_client *client)\n--\ndrivers/hwmon/ina2xx.c-968-\ndrivers/hwmon/ina2xx.c:969:\tchip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/ina2xx.c-970-\n--\ndrivers/hwmon/lm63.c=1245=static int lm63_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm63.c-1259-\t/* Set the device type */\ndrivers/hwmon/lm63.c:1260:\tdata-\u003ekind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm63.c-1261-\tif (data-\u003ekind == lm64)\n--\ndrivers/hwmon/lm75.c=818=static int lm75_i2c_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm75.c-830-\ndrivers/hwmon/lm75.c:831:\treturn lm75_generic_probe(dev, client-\u003ename, (uintptr_t)i2c_get_match_data(client),\ndrivers/hwmon/lm75.c-832-\t\t\t\t  client-\u003eirq, regmap);\n--\ndrivers/hwmon/lm78.c=630=static int lm78_i2c_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm78.c-640-\tdata-\u003eclient = client;\ndrivers/hwmon/lm78.c:641:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm78.c-642-\n--\ndrivers/hwmon/lm83.c=420=static int lm83_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm83.c-433-\ndrivers/hwmon/lm83.c:434:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm83.c-435-\n--\ndrivers/hwmon/lm85.c=1547=static int lm85_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm85.c-1558-\tdata-\u003eclient = client;\ndrivers/hwmon/lm85.c:1559:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm85.c-1560-\tmutex_init(\u0026data-\u003eupdate_lock);\n--\ndrivers/hwmon/lm90.c=2768=static int lm90_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm90.c-2790-\t/* Set the device type */\ndrivers/hwmon/lm90.c:2791:\tdata-\u003ekind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm90.c-2792-\n--\ndrivers/hwmon/lm92.c=374=static int lm92_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm92.c-391-\tdata-\u003eregmap = regmap;\ndrivers/hwmon/lm92.c:392:\tdata-\u003eresolution = (unsigned long)i2c_get_match_data(client);\ndrivers/hwmon/lm92.c-393-\n--\ndrivers/hwmon/lm95234.c=503=static int lm95234_probe(struct i2c_client *client)\n--\ndrivers/hwmon/lm95234.c-514-\ndrivers/hwmon/lm95234.c:515:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/lm95234.c-516-\n--\ndrivers/hwmon/max16065.c=505=static int max16065_probe(struct i2c_client *client)\n--\ndrivers/hwmon/max16065.c-514-\tint groups = 0;\ndrivers/hwmon/max16065.c:515:\tenum chips chip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/max16065.c-516-\n--\ndrivers/hwmon/max1668.c=269=static int max1668_probe(struct i2c_client *client)\n--\ndrivers/hwmon/max1668.c-287-\ndrivers/hwmon/max1668.c:288:\tdata-\u003echannels = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/max1668.c-289-\n--\ndrivers/hwmon/max6650.c=763=static int max6650_probe(struct i2c_client *client)\n--\ndrivers/hwmon/max6650.c-778-\ndrivers/hwmon/max6650.c:779:\tdata-\u003enr_fans = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/max6650.c-780-\n--\ndrivers/hwmon/max6697.c=537=static int max6697_probe(struct i2c_client *client)\n--\ndrivers/hwmon/max6697.c-553-\tdata-\u003eregmap = regmap;\ndrivers/hwmon/max6697.c:554:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/max6697.c-555-\tdata-\u003echip = \u0026max6697_chip_data[data-\u003etype];\n--\ndrivers/hwmon/mcp3021.c=119=static int mcp3021_probe(struct i2c_client *client)\n--\ndrivers/hwmon/mcp3021.c-150-\ndrivers/hwmon/mcp3021.c:151:\ttype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/mcp3021.c-152-\tswitch (type) {\n--\ndrivers/hwmon/mcp9982.c=911=static int mcp9982_probe(struct i2c_client *client)\n--\ndrivers/hwmon/mcp9982.c-930-\ndrivers/hwmon/mcp9982.c:931:\tchip = i2c_get_match_data(client);\ndrivers/hwmon/mcp9982.c-932-\tif (!chip)\n--\ndrivers/hwmon/nct6775-i2c.c=155=static int nct6775_i2c_probe(struct i2c_client *client)\n--\ndrivers/hwmon/nct6775-i2c.c-163-\ndrivers/hwmon/nct6775-i2c.c:164:\tdata-\u003ekind = (enum kinds)(uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/nct6775-i2c.c-165-\tdata-\u003eread_only = true;\n--\ndrivers/hwmon/pmbus/adp1050.c=79=static int adp1050_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/adp1050.c-82-\ndrivers/hwmon/pmbus/adp1050.c:83:\tinfo = (struct pmbus_driver_info *)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/adp1050.c-84-\tif (!info)\n--\ndrivers/hwmon/pmbus/bel-pfe.c=91=static int pfe_pmbus_probe(struct i2c_client *client)\ndrivers/hwmon/pmbus/bel-pfe.c-92-{\ndrivers/hwmon/pmbus/bel-pfe.c:93:\tint model = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/bel-pfe.c-94-\n--\ndrivers/hwmon/pmbus/ibm-cffps.c=482=static int ibm_cffps_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/ibm-cffps.c-484-\tint i, rc;\ndrivers/hwmon/pmbus/ibm-cffps.c:485:\tenum versions vs = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/ibm-cffps.c-486-\tstruct dentry *debugfs;\n--\ndrivers/hwmon/pmbus/isl68137.c=325=static int isl68137_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/isl68137.c-347-\ndrivers/hwmon/pmbus/isl68137.c:348:\tswitch ((uintptr_t)i2c_get_match_data(client)) {\ndrivers/hwmon/pmbus/isl68137.c-349-\tcase raa_dmpvr1_2rail:\n--\ndrivers/hwmon/pmbus/lm25066.c=464=static int lm25066_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/lm25066.c-484-\ndrivers/hwmon/pmbus/lm25066.c:485:\tdata-\u003eid = (enum chips)(unsigned long)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/lm25066.c-486-\n--\ndrivers/hwmon/pmbus/max20730.c=661=static int max20730_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/max20730.c-717-\ndrivers/hwmon/pmbus/max20730.c:718:\tchip_id = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/max20730.c-719-\n--\ndrivers/hwmon/pmbus/max34440.c=737=static int max34440_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/max34440.c-745-\t\treturn -ENOMEM;\ndrivers/hwmon/pmbus/max34440.c:746:\tdata-\u003eid = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/max34440.c-747-\tdata-\u003einfo = max34440_info[data-\u003eid];\n--\ndrivers/hwmon/pmbus/mp2856.c=395=static int mp2856_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/mp2856.c-406-\ndrivers/hwmon/pmbus/mp2856.c:407:\tchip_id = (kernel_ulong_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/mp2856.c-408-\n--\ndrivers/hwmon/pmbus/mp2975.c=996=static int mp2975_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/mp2975.c-1002-\ndrivers/hwmon/pmbus/mp2975.c:1003:\tddinfo = i2c_get_match_data(client);\ndrivers/hwmon/pmbus/mp2975.c-1004-\tif (!ddinfo)\n--\ndrivers/hwmon/pmbus/mpq8785.c=130=static int mpq8785_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/mpq8785.c-144-\telse\ndrivers/hwmon/pmbus/mpq8785.c:145:\t\tchip_id = (kernel_ulong_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/mpq8785.c-146-\n--\ndrivers/hwmon/pmbus/pmbus.c=164=static int pmbus_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/pmbus.c-174-\ndrivers/hwmon/pmbus/pmbus.c:175:\tdevice_info = (struct pmbus_device_info *)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/pmbus.c-176-\tif (device_info-\u003eflags) {\n--\ndrivers/hwmon/pmbus/q54sj108a2.c=281=static int q54sj108a2_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/q54sj108a2.c-297-\ndrivers/hwmon/pmbus/q54sj108a2.c:298:\tchip_id = (enum chips)(uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/q54sj108a2.c-299-\n--\ndrivers/hwmon/pmbus/tps53679.c=250=static int tps53679_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/tps53679.c-255-\ndrivers/hwmon/pmbus/tps53679.c:256:\tchip_id = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/tps53679.c-257-\n--\ndrivers/hwmon/pmbus/xdp720.c=76=static int xdp720_probe(struct i2c_client *client)\n--\ndrivers/hwmon/pmbus/xdp720.c-86-\ndrivers/hwmon/pmbus/xdp720.c:87:\tdata-\u003eid = (enum xdp720_chip_id)(uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/pmbus/xdp720.c-88-\tdata-\u003einfo = xdp720_info;\n--\ndrivers/hwmon/powr1220.c=274=static int powr1220_probe(struct i2c_client *client)\n--\ndrivers/hwmon/powr1220.c-286-\ndrivers/hwmon/powr1220.c:287:\tchip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/powr1220.c-288-\tswitch (chip) {\n--\ndrivers/hwmon/sht3x.c=875=static int sht3x_probe(struct i2c_client *client)\n--\ndrivers/hwmon/sht3x.c-903-\tdata-\u003eclient = client;\ndrivers/hwmon/sht3x.c:904:\tdata-\u003echip_id = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/sht3x.c-905-\tcrc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL);\n--\ndrivers/hwmon/shtc1.c=189=static int shtc1_probe(struct i2c_client *client)\n--\ndrivers/hwmon/shtc1.c-195-\tstruct device *hwmon_dev;\ndrivers/hwmon/shtc1.c:196:\tenum shtcx_chips chip = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/shtc1.c-197-\tstruct i2c_adapter *adap = client-\u003eadapter;\n--\ndrivers/hwmon/thmc50.c=380=static int thmc50_probe(struct i2c_client *client)\n--\ndrivers/hwmon/thmc50.c-391-\tdata-\u003eclient = client;\ndrivers/hwmon/thmc50.c:392:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/thmc50.c-393-\tmutex_init(\u0026data-\u003eupdate_lock);\n--\ndrivers/hwmon/tmp108.c=499=static int tmp108_probe(struct i2c_client *client)\n--\ndrivers/hwmon/tmp108.c-512-\ndrivers/hwmon/tmp108.c:513:\treturn tmp108_common_probe(dev, regmap, client-\u003ename, i2c_get_match_data(client));\ndrivers/hwmon/tmp108.c-514-}\n--\ndrivers/hwmon/tmp401.c=665=static int tmp401_probe(struct i2c_client *client)\n--\ndrivers/hwmon/tmp401.c-680-\tdata-\u003eclient = client;\ndrivers/hwmon/tmp401.c:681:\tdata-\u003ekind = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/tmp401.c-682-\n--\ndrivers/hwmon/tmp421.c=425=static int tmp421_probe(struct i2c_client *client)\n--\ndrivers/hwmon/tmp421.c-435-\ndrivers/hwmon/tmp421.c:436:\tdata-\u003echannels = (unsigned long)i2c_get_match_data(client);\ndrivers/hwmon/tmp421.c-437-\tdata-\u003eclient = client;\n--\ndrivers/hwmon/tmp464.c=637=static int tmp464_probe(struct i2c_client *client)\n--\ndrivers/hwmon/tmp464.c-651-\ndrivers/hwmon/tmp464.c:652:\tdata-\u003echannels = (int)(unsigned long)i2c_get_match_data(client);\ndrivers/hwmon/tmp464.c-653-\n--\ndrivers/hwmon/tmp513.c=712=static int tmp51x_probe(struct i2c_client *client)\n--\ndrivers/hwmon/tmp513.c-722-\ndrivers/hwmon/tmp513.c:723:\tdata-\u003emax_channels = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/tmp513.c-724-\n--\ndrivers/hwmon/w83781d.c=1195=static int w83781d_probe(struct i2c_client *client)\n--\ndrivers/hwmon/w83781d.c-1208-\ndrivers/hwmon/w83781d.c:1209:\tdata-\u003etype = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/w83781d.c-1210-\tdata-\u003eclient = client;\n--\ndrivers/hwmon/w83795.c=2137=static int w83795_probe(struct i2c_client *client)\n--\ndrivers/hwmon/w83795.c-2149-\ti2c_set_clientdata(client, data);\ndrivers/hwmon/w83795.c:2150:\tdata-\u003echip_type = (uintptr_t)i2c_get_match_data(client);\ndrivers/hwmon/w83795.c-2151-\tdata-\u003ebank = i2c_smbus_read_byte_data(client, W83795_REG_BANKSEL);\n--\ndrivers/i2c/i2c-core-base.c=119=EXPORT_SYMBOL_GPL(i2c_match_id);\ndrivers/i2c/i2c-core-base.c-120-\ndrivers/i2c/i2c-core-base.c:121:const void *i2c_get_match_data(const struct i2c_client *client)\ndrivers/i2c/i2c-core-base.c-122-{\n--\ndrivers/i2c/i2c-core-base.c-137-}\ndrivers/i2c/i2c-core-base.c:138:EXPORT_SYMBOL(i2c_get_match_data);\ndrivers/i2c/i2c-core-base.c-139-\n--\ndrivers/iio/accel/adxl313_i2c.c=64=static int adxl313_i2c_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/adxl313_i2c.c-73-\t */\ndrivers/iio/accel/adxl313_i2c.c:74:\tchip_data = i2c_get_match_data(client);\ndrivers/iio/accel/adxl313_i2c.c-75-\n--\ndrivers/iio/accel/adxl355_i2c.c=22=static int adxl355_i2c_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/adxl355_i2c.c-27-\ndrivers/iio/accel/adxl355_i2c.c:28:\tchip_data = i2c_get_match_data(client);\ndrivers/iio/accel/adxl355_i2c.c-29-\tif (!chip_data)\n--\ndrivers/iio/accel/adxl372_i2c.c=20=static int adxl372_i2c_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/adxl372_i2c.c-26-\ndrivers/iio/accel/adxl372_i2c.c:27:\tchip_info = i2c_get_match_data(client);\ndrivers/iio/accel/adxl372_i2c.c-28-\n--\ndrivers/iio/accel/adxl380_i2c.c=20=static int adxl380_i2c_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/adxl380_i2c.c-24-\ndrivers/iio/accel/adxl380_i2c.c:25:\tchip_data = i2c_get_match_data(client);\ndrivers/iio/accel/adxl380_i2c.c-26-\n--\ndrivers/iio/accel/bma180.c=917=static int bma180_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/bma180.c-931-\tdata-\u003eclient = client;\ndrivers/iio/accel/bma180.c:932:\tdata-\u003epart_info = i2c_get_match_data(client);\ndrivers/iio/accel/bma180.c-933-\n--\ndrivers/iio/accel/da280.c=100=static int da280_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/da280.c-110-\ndrivers/iio/accel/da280.c:111:\tmatch_data = i2c_get_match_data(client);\ndrivers/iio/accel/da280.c-112-\tif (!match_data) {\n--\ndrivers/iio/accel/kionix-kx022a-i2c.c=15=static int kx022a_i2c_probe(struct i2c_client *i2c)\n--\ndrivers/iio/accel/kionix-kx022a-i2c.c-25-\ndrivers/iio/accel/kionix-kx022a-i2c.c:26:\tchip_info = i2c_get_match_data(i2c);\ndrivers/iio/accel/kionix-kx022a-i2c.c-27-\tif (!chip_info)\n--\ndrivers/iio/accel/mc3230.c=143=static int mc3230_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/mc3230.c-149-\ndrivers/iio/accel/mc3230.c:150:\tchip_info = i2c_get_match_data(client);\ndrivers/iio/accel/mc3230.c-151-\tif (chip_info == NULL) {\n--\ndrivers/iio/accel/mma8452.c=1548=static int mma8452_probe(struct i2c_client *client)\n--\ndrivers/iio/accel/mma8452.c-1562-\ndrivers/iio/accel/mma8452.c:1563:\tdata-\u003echip_info = i2c_get_match_data(client);\ndrivers/iio/accel/mma8452.c-1564-\tif (!data-\u003echip_info)\n--\ndrivers/iio/adc/ltc2309.c=202=static int ltc2309_probe(struct i2c_client *client)\n--\ndrivers/iio/adc/ltc2309.c-213-\tltc2309 = iio_priv(indio_dev);\ndrivers/iio/adc/ltc2309.c:214:\tchip_info = i2c_get_match_data(client);\ndrivers/iio/adc/ltc2309.c-215-\n--\ndrivers/iio/adc/ltc2497.c=95=static int ltc2497_probe(struct i2c_client *client)\n--\ndrivers/iio/adc/ltc2497.c-115-\ndrivers/iio/adc/ltc2497.c:116:\tchip_info = i2c_get_match_data(client);\ndrivers/iio/adc/ltc2497.c-117-\tst-\u003ecommon_ddata.chip_info = chip_info;\n--\ndrivers/iio/adc/max1363.c=1557=static int max1363_probe(struct i2c_client *client)\n--\ndrivers/iio/adc/max1363.c-1575-\ndrivers/iio/adc/max1363.c:1576:\tst-\u003echip_info = i2c_get_match_data(client);\ndrivers/iio/adc/max1363.c-1577-\tst-\u003eclient = client;\n--\ndrivers/iio/adc/max34408.c=193=static int max34408_probe(struct i2c_client *client)\n--\ndrivers/iio/adc/max34408.c-202-\ndrivers/iio/adc/max34408.c:203:\tmodel_data = i2c_get_match_data(client);\ndrivers/iio/adc/max34408.c-204-\tif (!model_data)\n--\ndrivers/iio/adc/nct7201.c=407=static int nct7201_probe(struct i2c_client *client)\n--\ndrivers/iio/adc/nct7201.c-414-\ndrivers/iio/adc/nct7201.c:415:\tmodel_data = i2c_get_match_data(client);\ndrivers/iio/adc/nct7201.c-416-\tif (!model_data)\n--\ndrivers/iio/adc/pac1934.c=1481=static int pac1934_probe(struct i2c_client *client)\n--\n\n"
}

23/2 2026/08/09 12:03 llm patch-evaluator
10m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44496
							output: 43
							thoughts: 41
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into `i2c_get_match_data` Usage and Implementation**

Right, `i2c_get_match_data`... that's a key function, isn't it? It's plastered all over the place in the I2C driver landscape. I'm thinking about it now because I'm probably looking at a driver where its return value is crucial and I need to understand its behaviour better. It's a fundamental utility for getting device-specific data during the probe process, and many drivers lean on it. So, let me take a closer look at the actual implementation of `i2c_get_match_data`. I want to refresh my understanding of how it's designed to interface with the platform data and device tree bindings, and what the fallback mechanisms are. I need to be sure there are no surprises in how a specific device match results in which configuration data being returned.



24/2 2026/08/09 12:13 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-base.c",
  "FirstLine": 119,
  "LineCount": 20
}

Results:
{
  "Contents": " 119:\tEXPORT_SYMBOL_GPL(i2c_match_id);\n 120:\t\n 121:\tconst void *i2c_get_match_data(const struct i2c_client *client)\n 122:\t{\n 123:\t\tstruct i2c_driver *driver = to_i2c_driver(client-\u003edev.driver);\n 124:\t\tconst struct i2c_device_id *match;\n 125:\t\tconst void *data;\n 126:\t\n 127:\t\tdata = device_get_match_data(\u0026client-\u003edev);\n 128:\t\tif (!data) {\n 129:\t\t\tmatch = i2c_match_id(driver-\u003eid_table, client);\n 130:\t\t\tif (!match)\n 131:\t\t\t\treturn NULL;\n 132:\t\n 133:\t\t\tdata = (const void *)match-\u003edriver_data;\n 134:\t\t}\n 135:\t\n 136:\t\treturn data;\n 137:\t}\n 138:\tEXPORT_SYMBOL(i2c_get_match_data);\n"
}

25/2 2026/08/09 12:13 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45100
							output: 36
							thoughts: 263
						

Thoughts:
**Analyzing the rtpse_mcu_i2c_probe Function**

Okay, so the code flow starts with `device_get_match_data` – that's the first thing called. The critical part is if it returns NULL, which would trigger a fallback to `i2c_match_id` (not shown here, but implied). However, the critical point is that `rtpse_mcu_i2c_probe` is calling `device_get_match_data(dev)`, and NOT `i2c_get_match_data(client)`! This could be a significant difference.

Now, if this device is brought up through sysfs, `device_get_match_data` should return NULL, right? Since there's no fwnode present in that scenario. That means the driver needs to work either way - sysfs or fwnode!

I need to quickly verify that `device_get_match_data` doesn't handle sysfs matching somehow that I'm not immediately considering. Let me refresh my memory of how that function works again...



26/2 2026/08/09 12:13 tool read-file
0m Args:
{
  "File": "drivers/base/property.c",
  "FirstLine": 1349,
  "LineCount": 10
}

Results:
{
  "Contents": "1349:\tconst void *device_get_match_data(const struct device *dev)\n1350:\t{\n1351:\t\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\n1352:\t}\n1353:\tEXPORT_SYMBOL_GPL(device_get_match_data);\n1354:\t\n1355:\tstatic unsigned int fwnode_graph_devcon_matches(const struct fwnode_handle *fwnode,\n1356:\t\t\t\t\t\t\tconst char *con_id, void *data,\n1357:\t\t\t\t\t\t\tdevcon_match_fn_t match,\n1358:\t\t\t\t\t\t\tvoid **matches,\n"
}

27/2 2026/08/09 12:13 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45831
							output: 19
							thoughts: 421
						

Thoughts:
**Analyzing Device Instantiation in the Kernel for Syzkaller Coverage**

Okay, so I'm looking at how this `fwnode_call_ptr_op` works and trying to understand how it interacts with devices. It seems like it calls `device_get_match_data` on the fwnode. If `dev_fwnode(dev)` is NULL, which is the situation for devices instantiated through sysfs, the whole thing will return NULL. My immediate thought is, "Wait, what about device tree overlays? Syzkaller doesn't do that, right?". I should be checking if Syzkaller creates devices via `syz_i2c_setup` or some similar function. No, I checked, that's not it. Does it use `syz_open_dev` or related calls? Nope. Okay, Syzkaller has some genetlink calls, which is another approach for something else. Is there *any* other way this device could get instantiated?

Could `i2c_new_client_device` from another driver be a mechanism here? No, Syzkaller isn't doing that, and that would also be an unlikely scenario. Wait, would the kernel be compiled with mock I2C devices? No, that's not typical for Syzkaller's standard VM setups.

So then, is there any possibility to instantiate a platform or I2C device with a fwnode from userspace? The software node API comes to mind, but I don't think it's exposed to userspace.

Now, what if the driver is UART related? I see a `rtpse_mcu_uart_probe` which uses a serdev device. Serdev devices are generally instantiated from device tree or ACPI. I have to wonder if they can be instantiated from userspace. There's no sysfs interface for that.

Wait, Syzkaller has `syz_mac80211_setup` which creates a mock device. So does Syzkaller have any way to create serdev devices? I have to look for any `syz_serdev` calls or something similar to find out.



28/2 2026/08/09 12:14 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "syz_serdev"
}

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

29/2 2026/08/09 12:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 46744
							output: 36
							thoughts: 778
						

Thoughts:
**Analyzing Serdev Device Instantiation in a Virtualized Environment**

Okay, so I'm thinking about how this `realtek,pse-mcu-gen1` serdev driver works, and the challenge of testing it in a virtualized environment like QEMU. My first thought was about `tty` line disciplines – would those play a role? But, no, Serdev is a bus, not a line discipline. It seems devices on the serdev bus are brought into existence through the TTY port if a corresponding child node exists for the UART in either the device tree or ACPI tables.

In a standard QEMU VM, the usual UARTs (like `ttyS0`) don't have these child nodes for `realtek,pse-mcu-gen1`. So, it looks like the serdev device would never even be created under these circumstances.

Hmm, can I use `I2C_BOARD_INFO` in a test module? No, Syzkaller, which I'm relying on, doesn't load custom modules. Okay, maybe I could attach a serdev device somehow via `ldattach`? Nope, `ldattach` is for setting a line discipline, and serdev is an alternative that gets bound at device creation, dependent on the firmware. Okay, what about `hciattach`? That's a no go too. It uses the `N_HCI` line discipline.

So, how can I even reach the driver's code in a virtualized environment? If the device can't be probed, the code is effectively unreachable.

Wait, is the driver a platform driver? No, it's an I2C and serdev driver.

Alright, let's look closer at the I2C instantiation path, since that's what I'm simulating. Can `device_get_match_data` work with sysfs-based I2C instantiation? If I write `realtek,pse-mcu-gen1-smbus` to `/sys/bus/i2c/devices/i2c-X/new_device`, the I2C core creates an `i2c_client` with that name. Then `i2c_device_match` is called. It calls `i2c_of_match_device` which in turn calls `i2c_of_match_device_sysfs`, which matches the name against the compatible string.

This correctly returns the `of_device_id` match. Then the `i2c_device_probe` is called. And within that `i2c_device_probe` the following check is performed:

```c
	if (!driver->id_table &&
	    !acpi_driver_match_device(dev, dev->driver) &&
	    !i2c_of_match_device(dev->driver->of_match_table, client)) {
		status = -ENODEV;
		goto put_sync_adapter;
	}
```

This check passes because `i2c_of_match_device` returned non-NULL. Okay, good. Then `driver->probe(client)` is called. In the `rtpse_mcu_i2c_probe` function:

```c
	match = device_get_match_data(dev);
	if (!match)
		return dev_err_probe(dev, -ENODEV, "missing match data\n");
```

Here's the critical part: `device_get_match_data(dev)` calls `fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev)`. Since I created the device via sysfs, `dev_fwnode(dev)` is NULL. Therefore `device_get_match_data` returns NULL. And the probe fails with `-ENODEV`! It seems like this approach won't work.

Wait, am I right about this? I need to double-check if `device_get_match_data` has a fallback for I2C devices. If not, it means this whole sysfs instantiation method is going to fail to properly load this device.



30/2 2026/08/09 12:14 tool read-file
0m Args:
{
  "File": "drivers/base/property.c",
  "FirstLine": 1340,
  "LineCount": 20
}

Results:
{
  "Contents": "1340:\tint fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode,\n1341:\t\t\t\t\tstruct fwnode_endpoint *endpoint)\n1342:\t{\n1343:\t\tmemset(endpoint, 0, sizeof(*endpoint));\n1344:\t\n1345:\t\treturn fwnode_call_int_op(fwnode, graph_parse_endpoint, endpoint);\n1346:\t}\n1347:\tEXPORT_SYMBOL(fwnode_graph_parse_endpoint);\n1348:\t\n1349:\tconst void *device_get_match_data(const struct device *dev)\n1350:\t{\n1351:\t\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\n1352:\t}\n1353:\tEXPORT_SYMBOL_GPL(device_get_match_data);\n1354:\t\n1355:\tstatic unsigned int fwnode_graph_devcon_matches(const struct fwnode_handle *fwnode,\n1356:\t\t\t\t\t\t\tconst char *con_id, void *data,\n1357:\t\t\t\t\t\t\tdevcon_match_fn_t match,\n1358:\t\t\t\t\t\t\tvoid **matches,\n1359:\t\t\t\t\t\t\tunsigned int matches_len)\n"
}

31/2 2026/08/09 12:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48820
							output: 43
							thoughts: 310
						

Thoughts:
**Analyzing Device Matching in I2C and FWNode Context**

Okay, so `device_get_match_data` just acts as a wrapper, essentially, and calls the fwnode operation. That's straightforward. Now, a critical point comes to mind: what happens if the device is matched through ACPI? ACPI fwnode *does* have `device_get_match_data` implemented. And the same for OF, OF fwnode has it too. The potential hiccup lies with sysfs instantiated devices. These don't have an fwnode associated with them. That means `device_get_match_data` *will* return NULL.

Ah, I see! This is exactly the reason `i2c_get_match_data` was created. It's designed to handle this fallback scenario. It seems logical that `i2c_get_match_data` would gracefully revert to `i2c_match_id` if `device_get_match_data` yields nothing. However, I’m concerned because the `rtpse_mcu_i2c_probe` function is explicitly using `device_get_match_data` and not `i2c_get_match_data`.

Let me just check if `i2c_get_match_data` actually reverts to `i2c_of_match_device_sysfs`... No, that's wrong. I see it now. `i2c_get_match_data` falls back to `i2c_match_id(driver->id_table, client)`. So, the problem persists if the driver doesn't actually have an `id_table` at all. In that case, `i2c_get_match_data` would *also* return NULL for sysfs instantiated devices! I'm going to re-examine the implementation of `i2c_get_match_data`.



32/2 2026/08/09 12:14 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-base.c",
  "FirstLine": 121,
  "LineCount": 17
}

Results:
{
  "Contents": " 121:\tconst void *i2c_get_match_data(const struct i2c_client *client)\n 122:\t{\n 123:\t\tstruct i2c_driver *driver = to_i2c_driver(client-\u003edev.driver);\n 124:\t\tconst struct i2c_device_id *match;\n 125:\t\tconst void *data;\n 126:\t\n 127:\t\tdata = device_get_match_data(\u0026client-\u003edev);\n 128:\t\tif (!data) {\n 129:\t\t\tmatch = i2c_match_id(driver-\u003eid_table, client);\n 130:\t\t\tif (!match)\n 131:\t\t\t\treturn NULL;\n 132:\t\n 133:\t\t\tdata = (const void *)match-\u003edriver_data;\n 134:\t\t}\n 135:\t\n 136:\t\treturn data;\n 137:\t}\n"
}

33/2 2026/08/09 12:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49846
							output: 34
							thoughts: 577
						

Thoughts:
**Unreachable Driver in a Virtualized Environment**

Okay, so I've been digging into this `rtpse_mcu_i2c_driver`, and it's looking pretty bleak. The code specifically checks for match data using `device_get_match_data`. Now, I know that if that function returns NULL, the driver *should* fall back to checking `driver->id_table`. But wait... the `rtpse_mcu_i2c_driver` *doesn't even have* an `id_table` defined! So, even if the code *tried* to use `i2c_get_match_data` (which it doesn't, it uses the more general one), it would return NULL for devices instantiated via sysfs, which is the default in our virtual environment. That `device_get_match_data` is definitely returning NULL, making the whole thing a non-starter.

Consequently, `rtpse_mcu_i2c_probe` is going to exit immediately with `-ENODEV` and the rest of the code is unreachable. Effectively, everything in `realtek-pse-mcu-core.c` and `realtek-pse-mcu-i2c.c` is going to be dead code in our situation, unless there's a custom device tree in place.

I briefly considered if there's *any* other mechanism to provide a fwnode from userspace - can we create a software node or something? Nope, that's definitely a kernel-side only deal. What about device tree overlays? That's the one glimmer of hope. I know Configfs *can* load device tree overlays from userspace if `CONFIG_OF_OVERLAY` and `CONFIG_OF_CONFIGFS` are enabled. But... does syzkaller actually use those? Hmm, no, it looks like it doesn't currently support generating and loading device tree overlays via configfs. So we're back to square one.

Alright, let me double-check. I'm going to look for another entry point. The driver registers `pse_controller_dev`, but that only happens at the end of `rtpse_mcu_register`. Now, that's called from `rtpse_mcu_i2c_probe` or `rtpse_mcu_uart_probe`. Since both of those probe functions are going to fail (the I2C one fails on the lack of match data, and the UART one fails because serdev can't be instantiated), the core code is completely unreachable. Dead end.

Now, let me just quickly double check if there has been any recent change in `device_get_match_data` to support sysfs I2C instantiation.



34/2 2026/08/09 12:14 tool grepper
0m Args:
{
  "Expression": "device_get_match_data",
  "PathPrefix": "drivers/base/property.c"
}

Results:
{
  "Output": "drivers/base/property.c=1347=EXPORT_SYMBOL(fwnode_graph_parse_endpoint);\ndrivers/base/property.c-1348-\ndrivers/base/property.c:1349:const void *device_get_match_data(const struct device *dev)\ndrivers/base/property.c-1350-{\ndrivers/base/property.c:1351:\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\ndrivers/base/property.c-1352-}\ndrivers/base/property.c:1353:EXPORT_SYMBOL_GPL(device_get_match_data);\ndrivers/base/property.c-1354-\n"
}

35/2 2026/08/09 12:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51245
							output: 38
							thoughts: 91
						

Thoughts:
Okay, let's see, my initial reaction was incorrect. It's not a direct call, it seems. I need to dig deeper.

**Fwnode Connection Logic - Diving Deeper**

Hang on... I need to reconsider the relationship between `fwnode_call_ptr_op` and how devices are matched and initialized. What if the `dev->driver->of_match_table` isn't just used for matching, but somehow *also* feeds into setting the fwnode, perhaps indirectly through a sysfs or i2c operation? I need to look at the surrounding code to determine how that fwnode is actually getting connected.

I'll check the i2c initialization, specifically `i2c_new_client_device` or the related `new_device` sysfs interactions to see if they're responsible for attaching the fwnode in a way I'm not immediately seeing. My goal is to trace the actual fwnode attachment in that area, since I didn't see it directly. This seems like the logical next step to understand the connection between these components.



36/2 2026/08/09 12:14 tool grepper
0m Args:
{
  "Expression": "new_device",
  "PathPrefix": "drivers/i2c/i2c-core-base.c"
}

Results:
{
  "Output": "drivers/i2c/i2c-core-base.c=1263=static ssize_t\ndrivers/i2c/i2c-core-base.c:1264:new_device_store(struct device *dev, struct device_attribute *attr,\ndrivers/i2c/i2c-core-base.c-1265-\t\t const char *buf, size_t count)\n--\ndrivers/i2c/i2c-core-base.c-1276-\tif (!blank) {\ndrivers/i2c/i2c-core-base.c:1277:\t\tdev_err(dev, \"%s: Missing parameters\\n\", \"new_device\");\ndrivers/i2c/i2c-core-base.c-1278-\t\treturn -EINVAL;\n--\ndrivers/i2c/i2c-core-base.c-1280-\tif (blank - buf \u003e I2C_NAME_SIZE - 1) {\ndrivers/i2c/i2c-core-base.c:1281:\t\tdev_err(dev, \"%s: Invalid device name\\n\", \"new_device\");\ndrivers/i2c/i2c-core-base.c-1282-\t\treturn -EINVAL;\n--\ndrivers/i2c/i2c-core-base.c-1288-\tif (res \u003c 1) {\ndrivers/i2c/i2c-core-base.c:1289:\t\tdev_err(dev, \"%s: Can't parse I2C address\\n\", \"new_device\");\ndrivers/i2c/i2c-core-base.c-1290-\t\treturn -EINVAL;\n--\ndrivers/i2c/i2c-core-base.c-1292-\tif (res \u003e 1  \u0026\u0026 end != '\\n') {\ndrivers/i2c/i2c-core-base.c:1293:\t\tdev_err(dev, \"%s: Extra parameters\\n\", \"new_device\");\ndrivers/i2c/i2c-core-base.c-1294-\t\treturn -EINVAL;\n--\ndrivers/i2c/i2c-core-base.c-1314-\tmutex_unlock(\u0026adap-\u003euserspace_clients_lock);\ndrivers/i2c/i2c-core-base.c:1315:\tdev_info(dev, \"%s: Instantiated device %s at 0x%02hx\\n\", \"new_device\",\ndrivers/i2c/i2c-core-base.c-1316-\t\t info.type, info.addr);\n--\ndrivers/i2c/i2c-core-base.c-1319-}\ndrivers/i2c/i2c-core-base.c:1320:static DEVICE_ATTR_WO(new_device);\ndrivers/i2c/i2c-core-base.c-1321-\n--\ndrivers/i2c/i2c-core-base.c-1324- * they got it wrong. This interface can only be used to delete devices\ndrivers/i2c/i2c-core-base.c:1325: * instantiated by i2c_sysfs_new_device above. This guarantees that we\ndrivers/i2c/i2c-core-base.c-1326- * don't delete devices to which some kernel code still has references.\n--\ndrivers/i2c/i2c-core-base.c=1378=static struct attribute *i2c_adapter_attrs[] = {\ndrivers/i2c/i2c-core-base.c-1379-\t\u0026dev_attr_name.attr,\ndrivers/i2c/i2c-core-base.c:1380:\t\u0026dev_attr_new_device.attr,\ndrivers/i2c/i2c-core-base.c-1381-\t\u0026dev_attr_delete_device.attr,\n"
}

37/2 2026/08/09 12:14 llm patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52609
							output: 44
							thoughts: 11
						

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

**Analyzing the Codebase: Initial Approach**

Alright, so the instruction is to examine `new_device_store`. My first instinct is to dive right in and start parsing the code. I'm immediately looking for key indicators – the architectural patterns being employed, any obvious data structures or interfaces. Is it following a typical MVC, clean architecture, or something more bespoke? I’ll be scanning for any immediate red flags, performance bottlenecks, or potential security vulnerabilities. I'm mentally formulating a rough data flow diagram already, anticipating how data likely enters, is processed, and ultimately persists or is returned. Depending on the complexity, I'll probably start making notes of classes and functions that look particularly relevant for further study. The goal, initially, is high-level understanding - the forest before the trees, you know? Let's get to work and understand this new piece of code.



38/2 2026/08/09 12:16 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-base.c",
  "FirstLine": 1263,
  "LineCount": 50
}

Results:
{
  "Contents": "1263:\tstatic ssize_t\n1264:\tnew_device_store(struct device *dev, struct device_attribute *attr,\n1265:\t\t\t const char *buf, size_t count)\n1266:\t{\n1267:\t\tstruct i2c_adapter *adap = to_i2c_adapter(dev);\n1268:\t\tstruct i2c_board_info info;\n1269:\t\tstruct i2c_client *client;\n1270:\t\tchar *blank, end;\n1271:\t\tint res;\n1272:\t\n1273:\t\tmemset(\u0026info, 0, sizeof(struct i2c_board_info));\n1274:\t\n1275:\t\tblank = strchr(buf, ' ');\n1276:\t\tif (!blank) {\n1277:\t\t\tdev_err(dev, \"%s: Missing parameters\\n\", \"new_device\");\n1278:\t\t\treturn -EINVAL;\n1279:\t\t}\n1280:\t\tif (blank - buf \u003e I2C_NAME_SIZE - 1) {\n1281:\t\t\tdev_err(dev, \"%s: Invalid device name\\n\", \"new_device\");\n1282:\t\t\treturn -EINVAL;\n1283:\t\t}\n1284:\t\tmemcpy(info.type, buf, blank - buf);\n1285:\t\n1286:\t\t/* Parse remaining parameters, reject extra parameters */\n1287:\t\tres = sscanf(++blank, \"%hi%c\", \u0026info.addr, \u0026end);\n1288:\t\tif (res \u003c 1) {\n1289:\t\t\tdev_err(dev, \"%s: Can't parse I2C address\\n\", \"new_device\");\n1290:\t\t\treturn -EINVAL;\n1291:\t\t}\n1292:\t\tif (res \u003e 1  \u0026\u0026 end != '\\n') {\n1293:\t\t\tdev_err(dev, \"%s: Extra parameters\\n\", \"new_device\");\n1294:\t\t\treturn -EINVAL;\n1295:\t\t}\n1296:\t\n1297:\t\tif ((info.addr \u0026 I2C_ADDR_OFFSET_TEN_BIT) == I2C_ADDR_OFFSET_TEN_BIT) {\n1298:\t\t\tinfo.addr \u0026= ~I2C_ADDR_OFFSET_TEN_BIT;\n1299:\t\t\tinfo.flags |= I2C_CLIENT_TEN;\n1300:\t\t}\n1301:\t\n1302:\t\tif (info.addr \u0026 I2C_ADDR_OFFSET_SLAVE) {\n1303:\t\t\tinfo.addr \u0026= ~I2C_ADDR_OFFSET_SLAVE;\n1304:\t\t\tinfo.flags |= I2C_CLIENT_SLAVE;\n1305:\t\t}\n1306:\t\n1307:\t\tclient = i2c_new_client_device(adap, \u0026info);\n1308:\t\tif (IS_ERR(client))\n1309:\t\t\treturn PTR_ERR(client);\n1310:\t\n1311:\t\t/* Keep track of the added device */\n1312:\t\tmutex_lock(\u0026adap-\u003euserspace_clients_lock);\n"
}

39/2 2026/08/09 12:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53699
							output: 34
							thoughts: 269
						

Thoughts:
**My Analysis of Device Matching in I2C Context**

Okay, so `i2c_new_client_device` is the function that's creating this device. And I'm pretty sure it doesn't attach an `fwnode`, so calling `device_get_match_data` right after that, I would expect it to return `NULL`. But I always have to be thorough...

Hold on, I need to make sure I haven't missed something. Is there *any* other pathway for `device_get_match_data` to return something useful? Right, it checks `dev_fwnode(dev)`. If *that* is NULL, `device_get_match_data` just returns `NULL` itself. But wait... is there *any* scenario where `dev_fwnode(dev)` wouldn't be NULL?

Let's see... `i2c_new_client_device` only sets `client->dev.fwnode = info->fwnode`. Okay, that makes sense. But the `info.fwnode` variable is initialized to 0, which is NULL, by that `memset` call when `i2c_board_info` is created. So the `fwnode` is definitely NULL, as I initially thought.

Hmm... just to be completely certain, what about those weird cases? What if this driver is being instantiated through `i2c_new_dummy_device` or some other method that *isn't* `i2c_new_client_device`? But wait... based on my memory, there aren't any other drivers that instantiate the `realtek,pse-mcu-gen1` device. So that's highly unlikely.

Now, this is an important point. Going back to `device_get_match_data`, what happens if the `fwnode` is NULL? Does `device_get_match_data` maybe *fallback* to `of_match_device`? I should probably double-check that part of the code again, just to be absolutely sure. Okay, time to go back to the source...



40/2 2026/08/09 12:16 tool grepper
0m Args:
{
  "Expression": "device_get_match_data",
  "PathPrefix": "drivers/base/property.c"
}

Results:
{
  "Output": "drivers/base/property.c=1347=EXPORT_SYMBOL(fwnode_graph_parse_endpoint);\ndrivers/base/property.c-1348-\ndrivers/base/property.c:1349:const void *device_get_match_data(const struct device *dev)\ndrivers/base/property.c-1350-{\ndrivers/base/property.c:1351:\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\ndrivers/base/property.c-1352-}\ndrivers/base/property.c:1353:EXPORT_SYMBOL_GPL(device_get_match_data);\ndrivers/base/property.c-1354-\n"
}

41/2 2026/08/09 12:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 54664
							output: 23
							thoughts: 54
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own mental processing:

**Analyzing `fwnode_call_ptr_op` in this context**

Right, so it looks like the core operation here is just a call to `fwnode_call_ptr_op`. Straightforward enough. My immediate concern, as always, is null checks. If `fwnode` is NULL, then `fwnode_call_ptr_op` itself returns NULL, which is the expected behavior for that scenario. But I can't just take that for granted. I need to dive deeper and verify that behavior. The details around what is happening depend on the implementation of `fwnode_call_ptr_op` itself. Let me check the code there, so I can ensure this basic assumption is solid.



42/2 2026/08/09 12:16 tool grepper
0m Args:
{
  "Expression": "fwnode_call_ptr_op"
}

Results:
{
  "Output": "drivers/base/property.c=664=const char *fwnode_get_name(const struct fwnode_handle *fwnode)\ndrivers/base/property.c-665-{\ndrivers/base/property.c:666:\treturn fwnode_call_ptr_op(fwnode, get_name);\ndrivers/base/property.c-667-}\n--\ndrivers/base/property.c=677=const char *fwnode_get_name_prefix(const struct fwnode_handle *fwnode)\ndrivers/base/property.c-678-{\ndrivers/base/property.c:679:\treturn fwnode_call_ptr_op(fwnode, get_name_prefix);\ndrivers/base/property.c-680-}\n--\ndrivers/base/property.c=720=struct fwnode_handle *fwnode_get_parent(const struct fwnode_handle *fwnode)\ndrivers/base/property.c-721-{\ndrivers/base/property.c:722:\treturn fwnode_call_ptr_op(fwnode, get_parent);\ndrivers/base/property.c-723-}\n--\ndrivers/base/property.c=807=fwnode_get_next_child_node(const struct fwnode_handle *fwnode,\n--\ndrivers/base/property.c-815-\t/* Try to find a child in primary fwnode */\ndrivers/base/property.c:816:\tnext = fwnode_call_ptr_op(fwnode, get_next_child_node, child);\ndrivers/base/property.c-817-\tif (next)\n--\ndrivers/base/property.c-820-\t/* When no more children in primary, continue with secondary */\ndrivers/base/property.c:821:\treturn fwnode_call_ptr_op(fwnode-\u003esecondary, get_next_child_node, child);\ndrivers/base/property.c-822-}\n--\ndrivers/base/property.c=878=fwnode_get_named_child_node(const struct fwnode_handle *fwnode,\n--\ndrivers/base/property.c-880-{\ndrivers/base/property.c:881:\treturn fwnode_call_ptr_op(fwnode, get_named_child_node, childname);\ndrivers/base/property.c-882-}\n--\ndrivers/base/property.c=909=struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode)\n--\ndrivers/base/property.c-913-\ndrivers/base/property.c:914:\treturn fwnode_call_ptr_op(fwnode, get);\ndrivers/base/property.c-915-}\n--\ndrivers/base/property.c=1048=void __iomem *fwnode_iomap(struct fwnode_handle *fwnode, int index)\ndrivers/base/property.c-1049-{\ndrivers/base/property.c:1050:\treturn fwnode_call_ptr_op(fwnode, iomap, index);\ndrivers/base/property.c-1051-}\n--\ndrivers/base/property.c=1115=fwnode_graph_get_next_endpoint(const struct fwnode_handle *fwnode,\n--\ndrivers/base/property.c-1134-\ndrivers/base/property.c:1135:\tep = fwnode_call_ptr_op(parent, graph_get_next_endpoint, prev);\ndrivers/base/property.c-1136-\tif (ep)\n--\ndrivers/base/property.c=1157=fwnode_graph_get_port_parent(const struct fwnode_handle *endpoint)\n--\ndrivers/base/property.c-1161-\tport = fwnode_get_parent(endpoint);\ndrivers/base/property.c:1162:\tparent = fwnode_call_ptr_op(port, graph_get_port_parent);\ndrivers/base/property.c-1163-\n--\ndrivers/base/property.c=1219=fwnode_graph_get_remote_endpoint(const struct fwnode_handle *fwnode)\ndrivers/base/property.c-1220-{\ndrivers/base/property.c:1221:\treturn fwnode_call_ptr_op(fwnode, graph_get_remote_endpoint);\ndrivers/base/property.c-1222-}\n--\ndrivers/base/property.c=1349=const void *device_get_match_data(const struct device *dev)\ndrivers/base/property.c-1350-{\ndrivers/base/property.c:1351:\treturn fwnode_call_ptr_op(dev_fwnode(dev), device_get_match_data, dev);\ndrivers/base/property.c-1352-}\n--\ninclude/linux/fwnode.h=140=struct fwnode_operations {\n--\ninclude/linux/fwnode.h-198-\ninclude/linux/fwnode.h:199:#define fwnode_call_ptr_op(fwnode, op, ...)\t\t\\\ninclude/linux/fwnode.h-200-\t(fwnode_has_op(fwnode, op) ?\t\t\t\\\n"
}

43/2 2026/08/09 12:16 llm patch-evaluator
7m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 56300
							output: 37
							thoughts: 27
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into the `fwnode_call_ptr_op` Macro**

Okay, time to dissect `fwnode_call_ptr_op` within `include/linux/fwnode.h`. I need to refresh my memory on how this abstraction handles function pointers, especially when dealing with the FW node structure. My immediate thought is, this is likely designed for polymorphism within the fwnode framework. It is common to have a base structure with function pointers to allow for various specific device types, the macro probably makes these calls cleaner.

I'm expecting to see how it manages null pointer checks, and any potential safety nets. Does it use the likely `WARN_ON` to signal an issue? How does it handle the implicit `struct fwnode_handle` and the associated operation structure?

Specifically, I'm interested in how it deals with device-specific operations on the fwnode, and how it reduces boilerplate for dispatching these function calls, since the `fwnode` concept is used in many different device types. I'm keen to understand how it abstracts the function calls based on the 'type' of the `fwnode`.



44/2 2026/08/09 12:23 tool read-file
0m Args:
{
  "File": "include/linux/fwnode.h",
  "FirstLine": 195,
  "LineCount": 10
}

Results:
{
  "Contents": " 195:\t#define fwnode_call_bool_op(fwnode, op, ...)\t\t\\\n 196:\t\t(fwnode_has_op(fwnode, op) ?\t\t\t\\\n 197:\t\t (fwnode)-\u003eops-\u003eop(fwnode, ## __VA_ARGS__) : false)\n 198:\t\n 199:\t#define fwnode_call_ptr_op(fwnode, op, ...)\t\t\\\n 200:\t\t(fwnode_has_op(fwnode, op) ?\t\t\t\\\n 201:\t\t (fwnode)-\u003eops-\u003eop(fwnode, ## __VA_ARGS__) : NULL)\n 202:\t#define fwnode_call_void_op(fwnode, op, ...)\t\t\t\t\\\n 203:\t\tdo {\t\t\t\t\t\t\t\t\\\n 204:\t\t\tif (fwnode_has_op(fwnode, op))\t\t\t\t\\\n"
}

45/2 2026/08/09 12:23 llm patch-evaluator
3m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 56876
							output: 43
							thoughts: 1777
						

Thoughts:
**Unreachable Code Analysis**

Okay, so if `fwnode` is NULL, then `fwnode_has_op()` returns false and subsequently NULL. Which means `device_get_match_data` definitely returns NULL for sysfs instantiated devices. Therefore, as suspected, both `rtpse_mcu_i2c_probe` fails with `-ENODEV` and `rtpse_mcu_uart_probe` can't be used since it's a serdev and can't be created from userspace. So, in the end, this Realtek PSE-MCU driver is effectively unreachable in a virtualized environment without a custom device tree.

But wait, hold on. Is there *any* other way to instantiate an I2C device with an fwnode? What if another driver creates an I2C device with `i2c_new_client_device` and *that* driver attaches a fwnode? I don't see any such driver in the kernel that would instantiate `realtek,pse-mcu-gen1`.

What about syzkaller? Could it inject a device tree node? No. Syzkaller sticks to standard x86_64 or arm64 VMs with static device trees or ACPI.

What about ACPI and can syzkaller inject ACPI tables? No again. Syzkaller doesn't support dynamic ACPI injection.

Okay, let's explore if syzkaller can use `i2c_new_client_device` indirectly via some pseudo-syscall? I've checked and it doesn't support any of those syscalls: `syz_i2c_setup`, `syz_i2c_client`, `syz_open_dev`, `syz_usb_connect`, `syz_mac80211_setup`, `syz_80211_inject_frame`, `syz_kvm_setup_cpu`, `syz_init_net_socket`, `syz_genetlink_get_family_id`, `syz_mount_image`, `syz_read_part_table`, `syz_usb_control_io`, `syz_usb_ep_write`, `syz_usb_ep_read`, `syz_usb_disconnect`, `syz_io_uring_setup`, `syz_io_uring_submit`, `syz_bpf`, `syz_btf`, `syz_fuse_mount`, `syz_fuse_request`, or `syz_fuse_reply`. Nope, those are all unrelated.

Back to the driver. If `device_get_match_data` returns NULL, the probe fails. So, could it be non-NULL for sysfs instantiated devices? I'll check. If `i2c_new_client_device` attaches a fwnode if the compatible string matches an existing node in the DT? No, it just uses `info->fwnode` which is NULL. Therefore, the probe *will* fail.

What if the device is created via `i2c_sysfs_new_device`?
```c
	client = i2c_new_client_device(adap, &info);
```
`info.fwnode` is NULL. `client->dev.fwnode` is therefore NULL. So `device_get_match_data` returns NULL. Hence, probe fails.

Is there any other way?

Could the driver load as a module? Nope, loading a module doesn't instantiate a device. What about built-in? Still no. What if it gets instantiated via `i2c_new_dummy_device`? Only if another driver calls it. And no other driver calls it for this device.

What about `i2c_new_ancillary_device`, `i2c_new_scanned_device`, `i2c_register_board_info`, `i2c_of_match_device`, `acpi_i2c_register_devices` or `i2c_acpi_new_device`? It requires other platform/static code or an ACPI table that has a node for it.

What about `i2c_sysfs_new_device`? As I saw earlier, the `device_get_match_data` returns NULL, so the probe will fail.

Let me verify if `device_get_match_data` has a fallback for I2C.
```c
const void *device_get_match_data(const struct device *dev)
{
	return fwnode_call_ptr_op(dev_fwnode(dev), device_


46/2 2026/08/09 12:27 tool read-file
0m Args:
{
  "File": "drivers/i2c/i2c-core-of.c",
  "FirstLine": 115,
  "LineCount": 30
}

Results:
{
  "Contents": " 115:\tstatic const struct of_device_id*\n 116:\ti2c_of_match_device_sysfs(const struct of_device_id *matches,\n 117:\t\t\t\t\t  struct i2c_client *client)\n 118:\t{\n 119:\t\tconst char *name;\n 120:\t\n 121:\t\tfor (; matches-\u003ecompatible[0]; matches++) {\n 122:\t\t\t/*\n 123:\t\t\t * Adding devices through the i2c sysfs interface provides us\n 124:\t\t\t * a string to match which may be compatible with the device\n 125:\t\t\t * tree compatible strings, however with no actual of_node the\n 126:\t\t\t * of_match_device() will not match\n 127:\t\t\t */\n 128:\t\t\tif (sysfs_streq(client-\u003ename, matches-\u003ecompatible))\n 129:\t\t\t\treturn matches;\n 130:\t\n 131:\t\t\tname = strchr(matches-\u003ecompatible, ',');\n 132:\t\t\tif (!name)\n 133:\t\t\t\tname = matches-\u003ecompatible;\n 134:\t\t\telse\n 135:\t\t\t\tname++;\n 136:\t\n 137:\t\t\tif (sysfs_streq(client-\u003ename, name))\n 138:\t\t\t\treturn matches;\n 139:\t\t}\n 140:\t\n 141:\t\treturn NULL;\n 142:\t}\n 143:\t\n 144:\tconst struct of_device_id\n"
}

47/2 2026/08/09 12:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60124
							output: 29
							thoughts: 144
						

Thoughts:
**Debugging the rtpse_mcu_i2c_probe Failure**

Okay, so the problem is clear now. The function `dev_fwnode(dev)` isn't setting the `fwnode` correctly. It's just returning the existing match, which means `dev_fwnode(dev)` is still NULL. Consequently, `device_get_match_data` is going to return NULL, and then we're going to hit the `-ENODEV` error in `rtpse_mcu_i2c_probe`. That explains that.

But hang on… what about the UART driver? I vaguely recall there being a `rtpse_mcu_uart_probe` function as well. I'll need to check the code:

```c
static int rtpse_mcu_uart_probe(struct serdev_device *serdev)
```

Right, serdev devices. Those are usually instantiated either through the device tree or ACPI. Hmmm... can a serdev device *also* be instantiated from userspace? I'm drawing a blank on that. I'm going to have to dig around and check if there's any sysfs interface for a serdev. That could be a clue.



48/2 2026/08/09 12:27 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "new_device",
  "PathPrefix": "drivers/tty/serdev"
}

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

49/2 2026/08/09 12:27 llm patch-evaluator
7m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60581
							output: 221
							thoughts: 62912
						

50/2 2026/08/09 12:34 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a driver for the Realtek PSE MCU, which is an I2C/SMBus or UART attached microcontroller that manages PSE (Power Sourcing Equipment) hardware on PoE switches. The driver is instantiated via device tree or ACPI. In a virtualized environment like QEMU or GCE, there are no device tree nodes or ACPI tables for this specific hardware. While I2C devices can sometimes be instantiated via sysfs, the probe function of this driver relies on `device_get_match_data()`, which returns NULL for sysfs-instantiated devices because they lack a firmware node (fwnode). The driver does not have an `id_table` to fall back on, so the probe will always fail with -ENODEV. The UART (serdev) driver cannot be instantiated from userspace at all. Therefore, the core logic of this driver is unreachable in standard virtualized environments.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a driver for the Realtek PSE MCU, which is an I2C/SMBus or UART attached microcontroller that manages PSE (Power Sourcing Equipment) hardware on PoE switches. The driver is instantiated via device tree or ACPI. In a virtualized environment like QEMU or GCE, there are no device tree nodes or ACPI tables for this specific hardware. While I2C devices can sometimes be instantiated via sysfs, the probe function of this driver relies on `device_get_match_data()`, which returns NULL for sysfs-instantiated devices because they lack a firmware node (fwnode). The driver does not have an `id_table` to fall back on, so the probe will always fail with -ENODEV. The UART (serdev) driver cannot be instantiated from userspace at all. Therefore, the core logic of this driver is unreachable in standard virtualized environments.",
  "WorthFuzzing": false
}

51/1 2026/08/09 12:34 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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