| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/11 22:35 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"NFC_NCI",
"NFC_S3FWRN5_I2C",
"NFC_S3FWRN82_UART"
],
"FocusSymbols": [
"s3fwrn5_nci_rf_configure_dual",
"s3fwrn5_nci_clk_cfg",
"s3fwrn5_nci_dual_rsp",
"s3fwrn5_nci_init"
],
"KMSANReasoning": "The patch introduces support for a new NFC chip variant (S3NRN4V) and adds new NCI commands for firmware configuration and calibration data updates. All data structures sent to the device (such as `set_option`, `stop_cmd`, and `sub_oid`) are fully initialized before being passed to `nci_prop_cmd`. The length of the data sent is carefully calculated to match the initialized bytes, preventing any uninitialized stack memory from being sent. Data received from the device is copied into `info-\u003edual_rsp` with its length tracked by `info-\u003edual_rsp_len`, and bounds checks are performed before accessing it. While there is a potential out-of-bounds read if `skb-\u003elen` is 0 in `s3fwrn5_nci_dual_rsp` (`skb-\u003edata[0]`), this would be caught by KASAN. There are no uninitialized memory leaks to user space or uninitialized variables used in control flow. Standard KASAN is sufficient to test these changes.",
"NeedsKMSAN": false,
"Reasoning": "The patch adds support for the s3nrn4v variant to the s3fwrn5 NFC driver. It introduces new NCI proprietary commands and responses, and a new RF configuration routine. The driver is reachable via the NCI interface and I2C/UART buses, which can be fuzzed using virtual devices or syzkaller's NCI support.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/11 22:35 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 7b4020aae05276d4a47c3fbad7153d7da32efb91\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 11 22:35:27 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml\nindex 12baee45752c7..40a69466802bb 100644\n--- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml\n+++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml\n@@ -14,6 +14,7 @@ properties:\n enum:\n - samsung,s3fwrn5-i2c\n - samsung,s3fwrn82\n+ - samsung,s3nrn4v\n \n en-gpios:\n maxItems: 1\n@@ -29,6 +30,9 @@ properties:\n clocks:\n maxItems: 1\n \n+ pvdd-supply:\n+ description: PVDD power supply\n+\n wake-gpios:\n maxItems: 1\n description:\n@@ -58,12 +62,25 @@ allOf:\n properties:\n compatible:\n contains:\n- const: samsung,s3fwrn5-i2c\n+ enum:\n+ - samsung,s3fwrn5-i2c\n+ - samsung,s3nrn4v\n then:\n required:\n - interrupts\n - reg\n \n+ # Deployed DTs for the older parts never described a supply, so PVDD is\n+ # only required for the new device.\n+ - if:\n+ properties:\n+ compatible:\n+ contains:\n+ const: samsung,s3nrn4v\n+ then:\n+ required:\n+ - pvdd-supply\n+\n examples:\n - |\n #include \u003cdt-bindings/gpio/gpio.h\u003e\ndiff --git a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts\nindex ebdca0e2d2f3a..bdba848275db4 100644\n--- a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts\n+++ b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts\n@@ -601,9 +601,22 @@ \u0026gcc {\n };\n \n \u0026i2c1 {\n-\t/* Samsung NFC @ 0x27 */\n-\n \tstatus = \"okay\";\n+\n+\tnfc@27 {\n+\t\tcompatible = \"samsung,s3nrn4v\";\n+\t\treg = \u003c0x27\u003e;\n+\n+\t\tinterrupts-extended = \u003c\u0026tlmm 31 IRQ_TYPE_EDGE_RISING\u003e;\n+\t\ten-gpios = \u003c\u0026tlmm 56 GPIO_ACTIVE_HIGH\u003e;\n+\t\twake-gpios = \u003c\u0026tlmm 7 GPIO_ACTIVE_HIGH\u003e;\n+\t\tclocks = \u003c\u0026rpmhcc RPMH_LN_BB_CLK2\u003e;\n+\n+\t\tpvdd-supply = \u003c\u0026vreg_l20b\u003e;\n+\n+\t\tpinctrl-0 = \u003c\u0026nfc_default\u003e;\n+\t\tpinctrl-names = \"default\";\n+\t};\n };\n \n \u0026i2c3 {\n@@ -825,6 +838,22 @@ \u0026tlmm {\n \t\t\t \u003c13 1\u003e, /* NC */\n \t\t\t \u003c63 2\u003e; /* WLAN UART */\n \n+\tnfc_default: nfc-default-state {\n+\t\tirq-pins {\n+\t\t\tpins = \"gpio31\";\n+\t\t\tfunction = \"gpio\";\n+\t\t\tdrive-strength = \u003c2\u003e;\n+\t\t\tbias-disable;\n+\t\t};\n+\n+\t\tpd-pins {\n+\t\t\tpins = \"gpio56\";\n+\t\t\tfunction = \"gpio\";\n+\t\t\tdrive-strength = \u003c2\u003e;\n+\t\t\tbias-pull-up;\n+\t\t};\n+\t};\n+\n \tsdc2_card_det_n: sdc2-card-det-state {\n \t\tpins = \"gpio65\";\n \t\tfunction = \"gpio\";\ndiff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c\nindex af0fa8bd970b4..563470cb928de 100644\n--- a/drivers/nfc/s3fwrn5/core.c\n+++ b/drivers/nfc/s3fwrn5/core.c\n@@ -20,6 +20,9 @@\n \t\t\t\tNFC_PROTO_ISO14443_B_MASK | \\\n \t\t\t\tNFC_PROTO_ISO15693_MASK)\n \n+#define S3NRN4V_HWREG_NAME\t\"samsung/s3nrn4v/hwreg.bin\"\n+#define S3NRN4V_SWREG_NAME\t\"samsung/s3nrn4v/swreg.bin\"\n+\n static int s3fwrn5_firmware_init(struct s3fwrn5_info *info)\n {\n \tstruct s3fwrn5_fw_info *fw_info = \u0026info-\u003efw_info;\n@@ -74,6 +77,22 @@ static int s3fwrn5_firmware_update(struct s3fwrn5_info *info)\n \treturn ret;\n }\n \n+static int s3fwrn5_nci_init(struct nci_dev *ndev)\n+{\n+\tstruct s3fwrn5_info *info = nci_get_drvdata(ndev);\n+\tint ret = 0;\n+\n+\tif (info-\u003evariant == S3FWRN5_VARIANT_S3NRN4V) {\n+\t\tret = s3fwrn5_nci_clk_cfg(info);\n+\t\tif (ret \u003c 0)\n+\t\t\tdev_err(\u0026ndev-\u003enfc_dev-\u003edev,\n+\t\t\t\t\"failed to configure the reference clock: %d\\n\",\n+\t\t\t\tret);\n+\t}\n+\n+\treturn ret;\n+}\n+\n static int s3fwrn5_nci_open(struct nci_dev *ndev)\n {\n \tstruct s3fwrn5_info *info = nci_get_drvdata(ndev);\n@@ -127,6 +146,15 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)\n \tstruct s3fwrn5_info *info = nci_get_drvdata(ndev);\n \tint ret;\n \n+\tif (info-\u003evariant == S3FWRN5_VARIANT_S3NRN4V) {\n+\t\tret = s3fwrn5_nci_rf_configure_dual(info, S3NRN4V_HWREG_NAME,\n+\t\t\t\t\t\t S3NRN4V_SWREG_NAME);\n+\t\tif (ret \u003c 0)\n+\t\t\tdev_warn(\u0026ndev-\u003enfc_dev-\u003edev,\n+\t\t\t\t \"RF calibration data update failed: %d\\n\", ret);\n+\t\treturn 0;\n+\t}\n+\n \tif (s3fwrn5_firmware_init(info)) {\n \t\t//skip bootloader mode\n \t\treturn 0;\n@@ -149,6 +177,7 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)\n }\n \n static const struct nci_ops s3fwrn5_nci_ops = {\n+\t.init = s3fwrn5_nci_init,\n \t.open = s3fwrn5_nci_open,\n \t.close = s3fwrn5_nci_close,\n \t.send = s3fwrn5_nci_send,\n@@ -158,7 +187,7 @@ static const struct nci_ops s3fwrn5_nci_ops = {\n };\n \n int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,\n-\tconst struct s3fwrn5_phy_ops *phy_ops)\n+\tconst struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)\n {\n \tstruct s3fwrn5_info *info;\n \tint ret;\n@@ -170,6 +199,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,\n \tinfo-\u003ephy_id = phy_id;\n \tinfo-\u003epdev = pdev;\n \tinfo-\u003ephy_ops = phy_ops;\n+\tinfo-\u003evariant = variant;\n \tmutex_init(\u0026info-\u003emutex);\n \n \ts3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);\n@@ -225,3 +255,5 @@ EXPORT_SYMBOL(s3fwrn5_recv_frame);\n MODULE_LICENSE(\"GPL\");\n MODULE_DESCRIPTION(\"Samsung S3FWRN5 NFC driver\");\n MODULE_AUTHOR(\"Robert Baldyga \u003cr.baldyga@samsung.com\u003e\");\n+MODULE_FIRMWARE(S3NRN4V_HWREG_NAME);\n+MODULE_FIRMWARE(S3NRN4V_SWREG_NAME);\ndiff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c\nindex 4ba7626117118..884420ac83b5b 100644\n--- a/drivers/nfc/s3fwrn5/i2c.c\n+++ b/drivers/nfc/s3fwrn5/i2c.c\n@@ -11,13 +11,12 @@\n #include \u003clinux/gpio/consumer.h\u003e\n #include \u003clinux/delay.h\u003e\n #include \u003clinux/module.h\u003e\n+#include \u003clinux/regulator/consumer.h\u003e\n \n #include \u003cnet/nfc/nfc.h\u003e\n \n #include \"phy_common.h\"\n \n-#define S3FWRN5_I2C_DRIVER_NAME \"s3fwrn5_i2c\"\n-\n struct s3fwrn5_i2c_phy {\n \tstruct phy_common common;\n \tstruct i2c_client *i2c_dev;\n@@ -146,6 +145,7 @@ static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)\n \n static int s3fwrn5_i2c_probe(struct i2c_client *client)\n {\n+\tenum s3fwrn5_variant variant;\n \tstruct s3fwrn5_i2c_phy *phy;\n \tint ret;\n \n@@ -160,6 +160,11 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)\n \tphy-\u003ei2c_dev = client;\n \ti2c_set_clientdata(client, phy);\n \n+\tret = devm_regulator_get_enable(\u0026client-\u003edev, \"pvdd\");\n+\tif (ret)\n+\t\treturn dev_err_probe(\u0026client-\u003edev, ret,\n+\t\t\t\t \"failed to enable pvdd\\n\");\n+\n \tphy-\u003ecommon.gpio_en = devm_gpiod_get(\u0026client-\u003edev, \"en\", GPIOD_OUT_HIGH);\n \tif (IS_ERR(phy-\u003ecommon.gpio_en))\n \t\treturn PTR_ERR(phy-\u003ecommon.gpio_en);\n@@ -179,14 +184,15 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)\n \t\treturn dev_err_probe(\u0026client-\u003edev, PTR_ERR(phy-\u003eclk),\n \t\t\t\t \"failed to get clock\\n\");\n \n+\tvariant = (uintptr_t)i2c_get_match_data(client);\n \tret = s3fwrn5_probe(\u0026phy-\u003ecommon.ndev, phy, \u0026phy-\u003ei2c_dev-\u003edev,\n-\t\t\t \u0026i2c_phy_ops);\n+\t\t\t \u0026i2c_phy_ops, variant);\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n \tret = devm_request_threaded_irq(\u0026client-\u003edev, phy-\u003ei2c_dev-\u003eirq, NULL,\n \t\ts3fwrn5_i2c_irq_thread_fn, IRQF_ONESHOT,\n-\t\tS3FWRN5_I2C_DRIVER_NAME, phy);\n+\t\t\"s3fwrn5_i2c\", phy);\n \tif (ret)\n \t\tgoto s3fwrn5_remove;\n \n@@ -205,21 +211,25 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)\n }\n \n static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {\n-\t{ .name = S3FWRN5_I2C_DRIVER_NAME },\n+\t{ .name = \"s3fwrn5_i2c\", .driver_data = S3FWRN5_VARIANT_FWDL },\n+\t{ .name = \"s3nrn4v\", .driver_data = S3FWRN5_VARIANT_S3NRN4V },\n \t{ }\n };\n MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);\n \n static const struct of_device_id of_s3fwrn5_i2c_match[] = {\n-\t{ .compatible = \"samsung,s3fwrn5-i2c\" },\n+\t{ .compatible = \"samsung,s3fwrn5-i2c\",\n+\t .data = (void *)S3FWRN5_VARIANT_FWDL },\n+\t{ .compatible = \"samsung,s3nrn4v\",\n+\t .data = (void *)S3FWRN5_VARIANT_S3NRN4V },\n \t{ }\n };\n MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);\n \n static struct i2c_driver s3fwrn5_i2c_driver = {\n \t.driver = {\n-\t\t.name = S3FWRN5_I2C_DRIVER_NAME,\n-\t\t.of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),\n+\t\t.name = \"s3fwrn5_i2c\",\n+\t\t.of_match_table = of_s3fwrn5_i2c_match,\n \t},\n \t.probe = s3fwrn5_i2c_probe,\n \t.remove = s3fwrn5_i2c_remove,\ndiff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c\nindex 5a9de11bbecea..381aa7bf29a23 100644\n--- a/drivers/nfc/s3fwrn5/nci.c\n+++ b/drivers/nfc/s3fwrn5/nci.c\n@@ -8,6 +8,9 @@\n \n #include \u003clinux/completion.h\u003e\n #include \u003clinux/firmware.h\u003e\n+#include \u003clinux/minmax.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/unaligned.h\u003e\n \n #include \"s3fwrn5.h\"\n #include \"nci.h\"\n@@ -20,7 +23,24 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)\n \treturn 0;\n }\n \n-const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {\n+/*\n+ * DUAL_OPTION responses are not uniform: GET_VER answers with the chip's\n+ * calibration versions instead of a status byte, so stash the payload for\n+ * the caller before completing the request.\n+ */\n+static int s3fwrn5_nci_dual_rsp(struct nci_dev *ndev, struct sk_buff *skb)\n+{\n+\tstruct s3fwrn5_info *info = nci_get_drvdata(ndev);\n+\n+\tinfo-\u003edual_rsp_len = min_t(unsigned int, skb-\u003elen,\n+\t\t\t\t sizeof(info-\u003edual_rsp));\n+\tmemcpy(info-\u003edual_rsp, skb-\u003edata, info-\u003edual_rsp_len);\n+\n+\tnci_req_complete(ndev, skb-\u003edata[0]);\n+\treturn 0;\n+}\n+\n+const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {\n \t{\n \t\t.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,\n \t\t\t\tNCI_PROP_SET_RFREG),\n@@ -41,6 +61,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {\n \t\t\t\tNCI_PROP_FW_CFG),\n \t\t.rsp = s3fwrn5_nci_prop_rsp,\n \t},\n+\t{\n+\t\t.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,\n+\t\t\t\tNCI_PROP_DUAL_OPTION),\n+\t\t.rsp = s3fwrn5_nci_dual_rsp,\n+\t},\n };\n \n #define S3FWRN5_RFREG_SECTION_SIZE 252\n@@ -117,3 +142,154 @@ int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)\n \trelease_firmware(fw);\n \treturn ret;\n }\n+\n+/*\n+ * The S3NRN4V expects the single-byte FW_CFG form (just the clock-speed\n+ * selector).\n+ */\n+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)\n+{\n+\tu8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;\n+\n+\treturn nci_prop_cmd(info-\u003endev, NCI_PROP_FW_CFG, 1, \u0026clk_speed);\n+}\n+\n+/*\n+ * An 8-byte calibration version: 5 bytes of date stamp and a 3-byte CSC\n+ * code, at fixed offsets both in a blob's 16-byte tail and in each half of\n+ * the GET_VER response (HW at offset 0, SW at offset 15).\n+ */\n+static bool s3fwrn5_nci_dual_version_eq(const u8 *a, const u8 *b)\n+{\n+\treturn !memcmp(a + 5, b + 5, 5) \u0026\u0026 !memcmp(a + 12, b + 12, 3);\n+}\n+\n+static bool s3fwrn5_nci_dual_cal_current(struct s3fwrn5_info *info,\n+\t\t\t\t\t const struct firmware *hw_fw,\n+\t\t\t\t\t const struct firmware *sw_fw)\n+{\n+\tif (info-\u003edual_rsp_len \u003c 30)\n+\t\treturn false;\n+\tif (hw_fw-\u003esize \u003c 16 || sw_fw-\u003esize \u003c 16)\n+\t\treturn false;\n+\n+\treturn s3fwrn5_nci_dual_version_eq(info-\u003edual_rsp,\n+\t\t\t\t\t hw_fw-\u003edata + hw_fw-\u003esize - 16) \u0026\u0026\n+\t s3fwrn5_nci_dual_version_eq(info-\u003edual_rsp + 15,\n+\t\t\t\t\t sw_fw-\u003edata + sw_fw-\u003esize - 16);\n+}\n+\n+/*\n+ * S3NRN4V RF calibration data update: the HW and SW blobs merged into one\n+ * stream (HW first), pushed as START_UPDATE, one SET_OPTION per 252-byte\n+ * section, then STOP_UPDATE carrying a 16-bit checksum (running sum of the\n+ * merged stream as 32-bit words).\n+ */\n+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,\n+\t\t\t\t const char *hw_name, const char *sw_name)\n+{\n+\tstruct nci_prop_dual_set_option_cmd set_option;\n+\tstruct device *dev = \u0026info-\u003endev-\u003enfc_dev-\u003edev;\n+\tconst struct firmware *hw_fw, *sw_fw;\n+\tsize_t merged_size, i, len;\n+\tu8 *merged;\n+\tu8 stop_cmd[3];\n+\tu32 checksum;\n+\tu8 sub_oid;\n+\tint ret;\n+\n+\tret = firmware_request_nowarn(\u0026hw_fw, hw_name, dev);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\tret = firmware_request_nowarn(\u0026sw_fw, sw_name, dev);\n+\tif (ret \u003c 0)\n+\t\tgoto out_hw;\n+\n+\tmerged_size = hw_fw-\u003esize + sw_fw-\u003esize;\n+\n+\t/*\n+\t * The stream is checksummed as 32-bit words and pushed in at most 256\n+\t * sections (the section index is a single byte); reject blobs that\n+\t * would silently break either.\n+\t */\n+\tif (!merged_size || merged_size % 4 ||\n+\t merged_size \u003e 256 * NCI_PROP_DUAL_SECTION_SIZE) {\n+\t\tdev_err(dev, \"invalid calibration data size: %zu\\n\", merged_size);\n+\t\tret = -EINVAL;\n+\t\tgoto out_sw;\n+\t}\n+\n+\t/*\n+\t * Ask the chip for its current calibration versions and skip the\n+\t * upload when both already match the blobs; a mismatch or an\n+\t * unparseable answer means the upload proceeds. GET_VER answers with\n+\t * versions, not a status byte, so nci_prop_cmd()'s return carries no\n+\t * meaning here.\n+\t */\n+\tsub_oid = NCI_PROP_DUAL_SUB_GET_VER;\n+\tinfo-\u003edual_rsp_len = 0;\n+\tnci_prop_cmd(info-\u003endev, NCI_PROP_DUAL_OPTION, 1, \u0026sub_oid);\n+\tif (s3fwrn5_nci_dual_cal_current(info, hw_fw, sw_fw)) {\n+\t\tdev_dbg(dev, \"calibration data already current\\n\");\n+\t\tret = 0;\n+\t\tgoto out_sw;\n+\t}\n+\n+\tmerged = kvmalloc(merged_size, GFP_KERNEL);\n+\tif (!merged) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out_sw;\n+\t}\n+\tmemcpy(merged, hw_fw-\u003edata, hw_fw-\u003esize);\n+\tmemcpy(merged + hw_fw-\u003esize, sw_fw-\u003edata, sw_fw-\u003esize);\n+\n+\tchecksum = 0;\n+\tfor (i = 0; i + 4 \u003c= merged_size; i += 4)\n+\t\tchecksum += get_unaligned_le32(merged + i);\n+\n+\t/* START_UPDATE */\n+\tsub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;\n+\tret = nci_prop_cmd(info-\u003endev, NCI_PROP_DUAL_OPTION, 1, \u0026sub_oid);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(dev, \"Unable to start calibration data update\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\t/* SET_OPTION per section */\n+\tset_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;\n+\tset_option.index = 0;\n+\tfor (i = 0; i \u003c merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {\n+\t\tlen = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);\n+\t\tmemcpy(set_option.data, merged + i, len);\n+\t\tret = nci_prop_cmd(info-\u003endev, NCI_PROP_DUAL_OPTION,\n+\t\t\t\t len + 2, (__u8 *)\u0026set_option);\n+\t\tif (ret \u003c 0) {\n+\t\t\tdev_err(dev, \"calibration data update error: %d\\n\",\n+\t\t\t\tret);\n+\t\t\t/* Abort form: STOP_UPDATE with the sub-OID alone. */\n+\t\t\tsub_oid = NCI_PROP_DUAL_SUB_STOP_UPDATE;\n+\t\t\tnci_prop_cmd(info-\u003endev, NCI_PROP_DUAL_OPTION, 1,\n+\t\t\t\t \u0026sub_oid);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tset_option.index++;\n+\t}\n+\n+\t/* STOP_UPDATE with checksum */\n+\tstop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;\n+\tput_unaligned_le16(checksum, \u0026stop_cmd[1]);\n+\tret = nci_prop_cmd(info-\u003endev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);\n+\tif (ret \u003c 0) {\n+\t\tdev_err(dev, \"Unable to stop calibration data update\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tdev_dbg(dev, \"calibration data update: success\\n\");\n+out:\n+\tkvfree(merged);\n+out_sw:\n+\trelease_firmware(sw_fw);\n+out_hw:\n+\trelease_firmware(hw_fw);\n+\treturn ret;\n+}\ndiff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h\nindex bc4bce2bbc4d9..453a1c9d65eb0 100644\n--- a/drivers/nfc/s3fwrn5/nci.h\n+++ b/drivers/nfc/s3fwrn5/nci.h\n@@ -40,6 +40,9 @@ struct nci_prop_stop_rfreg_rsp {\n \n #define NCI_PROP_FW_CFG\t\t0x28\n \n+/* S3NRN4V single-byte FW_CFG payload; 0x11 selects the 19.2 MHz reference. */\n+#define NCI_PROP_FW_CFG_CLK_SPEED\t0x11\n+\n struct nci_prop_fw_cfg_cmd {\n \t__u8 clk_type;\n \t__u8 clk_speed;\n@@ -50,7 +53,25 @@ struct nci_prop_fw_cfg_rsp {\n \t__u8 status;\n };\n \n-extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];\n+#define NCI_PROP_DUAL_OPTION\t\t0x2a\n+\n+#define NCI_PROP_DUAL_SUB_GET_VER\t0x00\n+#define NCI_PROP_DUAL_SUB_START_UPDATE\t0x01\n+#define NCI_PROP_DUAL_SUB_SET_OPTION\t0x02\n+#define NCI_PROP_DUAL_SUB_STOP_UPDATE\t0x03\n+\n+#define NCI_PROP_DUAL_SECTION_SIZE\t252\n+\n+struct nci_prop_dual_set_option_cmd {\n+\t__u8 sub_oid;\n+\t__u8 index;\n+\t__u8 data[NCI_PROP_DUAL_SECTION_SIZE];\n+};\n+\n+extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];\n int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);\n+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,\n+\t\t\t\t const char *hw_name, const char *sw_name);\n+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);\n \n #endif /* __LOCAL_S3FWRN5_NCI_H_ */\ndiff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h\nindex 2b492236090b8..63f679f171666 100644\n--- a/drivers/nfc/s3fwrn5/s3fwrn5.h\n+++ b/drivers/nfc/s3fwrn5/s3fwrn5.h\n@@ -21,6 +21,13 @@ enum s3fwrn5_mode {\n \tS3FWRN5_MODE_FW,\n };\n \n+enum s3fwrn5_variant {\n+\t/* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */\n+\tS3FWRN5_VARIANT_FWDL,\n+\t/* S3NRN4V: no firmware download; FW_CFG + DUAL_OPTION bring-up */\n+\tS3FWRN5_VARIANT_S3NRN4V,\n+};\n+\n struct s3fwrn5_phy_ops {\n \tvoid (*set_wake)(void *id, bool sleep);\n \tvoid (*set_mode)(void *id, enum s3fwrn5_mode);\n@@ -36,6 +43,11 @@ struct s3fwrn5_info {\n \tconst struct s3fwrn5_phy_ops *phy_ops;\n \n \tstruct s3fwrn5_fw_info fw_info;\n+\tenum s3fwrn5_variant variant;\n+\n+\t/* Last DUAL_OPTION response payload (GET_VER carries versions). */\n+\tu8 dual_rsp[32];\n+\tu8 dual_rsp_len;\n \n \tstruct mutex mutex;\n };\n@@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)\n }\n \n int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,\n-\tconst struct s3fwrn5_phy_ops *phy_ops);\n+\tconst struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);\n void s3fwrn5_remove(struct nci_dev *ndev);\n \n int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,\ndiff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c\nindex 8f142a2551016..75397c3dbc58b 100644\n--- a/drivers/nfc/s3fwrn5/uart.c\n+++ b/drivers/nfc/s3fwrn5/uart.c\n@@ -136,7 +136,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)\n \t}\n \n \tret = s3fwrn5_probe(\u0026phy-\u003ecommon.ndev, phy, \u0026phy-\u003eser_dev-\u003edev,\n-\t\t\t \u0026uart_phy_ops);\n+\t\t\t \u0026uart_phy_ops, S3FWRN5_VARIANT_FWDL);\n \tif (ret \u003c 0)\n \t\tgoto err_serdev;\n \n"
} |
| 2/1 | 2026/08/11 22:35 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"NFC_NCI",
"NFC_S3FWRN5_I2C",
"NFC_S3FWRN82_UART"
],
"FocusSymbols": [
"s3fwrn5_nci_rf_configure_dual",
"s3fwrn5_nci_clk_cfg",
"s3fwrn5_nci_dual_rsp",
"s3fwrn5_nci_init"
],
"Reasoning": "The patch adds support for the s3nrn4v variant to the s3fwrn5 NFC driver. It introduces new NCI proprietary commands and responses, and a new RF configuration routine. The driver is reachable via the NCI interface and I2C/UART buses, which can be fuzzed using virtual devices or syzkaller's NCI support.",
"WorthFuzzing": true
}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 7b4020aae05276d4a47c3fbad7153d7da32efb91
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 11 22:35:27 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
index 12baee45752c7..40a69466802bb 100644
--- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
+++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
@@ -14,6 +14,7 @@ properties:
enum:
- samsung,s3fwrn5-i2c
- samsung,s3fwrn82
+ - samsung,s3nrn4v
en-gpios:
maxItems: 1
@@ -29,6 +30,9 @@ properties:
clocks:
maxItems: 1
+ pvdd-supply:
+ description: PVDD power supply
+
wake-gpios:
maxItems: 1
description:
@@ -58,12 +62,25 @@ allOf:
properties:
compatible:
contains:
- const: samsung,s3fwrn5-i2c
+ enum:
+ - samsung,s3fwrn5-i2c
+ - samsung,s3nrn4v
then:
required:
- interrupts
- reg
+ # Deployed DTs for the older parts never described a supply, so PVDD is
+ # only required for the new device.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ required:
+ - pvdd-supply
+
examples:
- |
#include <dt-bindings/gpio/gpio.h>
diff --git a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
index ebdca0e2d2f3a..bdba848275db4 100644
--- a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
+++ b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
@@ -601,9 +601,22 @@ &gcc {
};
&i2c1 {
- /* Samsung NFC @ 0x27 */
-
status = "okay";
+
+ nfc@27 {
+ compatible = "samsung,s3nrn4v";
+ reg = <0x27>;
+
+ interrupts-extended = <&tlmm 31 IRQ_TYPE_EDGE_RISING>;
+ en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
+ wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
+ clocks = <&rpmhcc RPMH_LN_BB_CLK2>;
+
+ pvdd-supply = <&vreg_l20b>;
+
+ pinctrl-0 = <&nfc_default>;
+ pinctrl-names = "default";
+ };
};
&i2c3 {
@@ -825,6 +838,22 @@ &tlmm {
<13 1>, /* NC */
<63 2>; /* WLAN UART */
+ nfc_default: nfc-default-state {
+ irq-pins {
+ pins = "gpio31";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ pd-pins {
+ pins = "gpio56";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+ };
+
sdc2_card_det_n: sdc2-card-det-state {
pins = "gpio65";
function = "gpio";
diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c
index af0fa8bd970b4..563470cb928de 100644
--- a/drivers/nfc/s3fwrn5/core.c
+++ b/drivers/nfc/s3fwrn5/core.c
@@ -20,6 +20,9 @@
NFC_PROTO_ISO14443_B_MASK | \
NFC_PROTO_ISO15693_MASK)
+#define S3NRN4V_HWREG_NAME "samsung/s3nrn4v/hwreg.bin"
+#define S3NRN4V_SWREG_NAME "samsung/s3nrn4v/swreg.bin"
+
static int s3fwrn5_firmware_init(struct s3fwrn5_info *info)
{
struct s3fwrn5_fw_info *fw_info = &info->fw_info;
@@ -74,6 +77,22 @@ static int s3fwrn5_firmware_update(struct s3fwrn5_info *info)
return ret;
}
+static int s3fwrn5_nci_init(struct nci_dev *ndev)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+ int ret = 0;
+
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ ret = s3fwrn5_nci_clk_cfg(info);
+ if (ret < 0)
+ dev_err(&ndev->nfc_dev->dev,
+ "failed to configure the reference clock: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+
static int s3fwrn5_nci_open(struct nci_dev *ndev)
{
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
@@ -127,6 +146,15 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
int ret;
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ ret = s3fwrn5_nci_rf_configure_dual(info, S3NRN4V_HWREG_NAME,
+ S3NRN4V_SWREG_NAME);
+ if (ret < 0)
+ dev_warn(&ndev->nfc_dev->dev,
+ "RF calibration data update failed: %d\n", ret);
+ return 0;
+ }
+
if (s3fwrn5_firmware_init(info)) {
//skip bootloader mode
return 0;
@@ -149,6 +177,7 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
}
static const struct nci_ops s3fwrn5_nci_ops = {
+ .init = s3fwrn5_nci_init,
.open = s3fwrn5_nci_open,
.close = s3fwrn5_nci_close,
.send = s3fwrn5_nci_send,
@@ -158,7 +187,7 @@ static const struct nci_ops s3fwrn5_nci_ops = {
};
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops)
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
{
struct s3fwrn5_info *info;
int ret;
@@ -170,6 +199,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
info->phy_id = phy_id;
info->pdev = pdev;
info->phy_ops = phy_ops;
+ info->variant = variant;
mutex_init(&info->mutex);
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
@@ -225,3 +255,5 @@ EXPORT_SYMBOL(s3fwrn5_recv_frame);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver");
MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>");
+MODULE_FIRMWARE(S3NRN4V_HWREG_NAME);
+MODULE_FIRMWARE(S3NRN4V_SWREG_NAME);
diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c
index 4ba7626117118..884420ac83b5b 100644
--- a/drivers/nfc/s3fwrn5/i2c.c
+++ b/drivers/nfc/s3fwrn5/i2c.c
@@ -11,13 +11,12 @@
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/module.h>
+#include <linux/regulator/consumer.h>
#include <net/nfc/nfc.h>
#include "phy_common.h"
-#define S3FWRN5_I2C_DRIVER_NAME "s3fwrn5_i2c"
-
struct s3fwrn5_i2c_phy {
struct phy_common common;
struct i2c_client *i2c_dev;
@@ -146,6 +145,7 @@ static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)
static int s3fwrn5_i2c_probe(struct i2c_client *client)
{
+ enum s3fwrn5_variant variant;
struct s3fwrn5_i2c_phy *phy;
int ret;
@@ -160,6 +160,11 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
phy->i2c_dev = client;
i2c_set_clientdata(client, phy);
+ ret = devm_regulator_get_enable(&client->dev, "pvdd");
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "failed to enable pvdd\n");
+
phy->common.gpio_en = devm_gpiod_get(&client->dev, "en", GPIOD_OUT_HIGH);
if (IS_ERR(phy->common.gpio_en))
return PTR_ERR(phy->common.gpio_en);
@@ -179,14 +184,15 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
"failed to get clock\n");
+ variant = (uintptr_t)i2c_get_match_data(client);
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
- &i2c_phy_ops);
+ &i2c_phy_ops, variant);
if (ret < 0)
return ret;
ret = devm_request_threaded_irq(&client->dev, phy->i2c_dev->irq, NULL,
s3fwrn5_i2c_irq_thread_fn, IRQF_ONESHOT,
- S3FWRN5_I2C_DRIVER_NAME, phy);
+ "s3fwrn5_i2c", phy);
if (ret)
goto s3fwrn5_remove;
@@ -205,21 +211,25 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
}
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
- { .name = S3FWRN5_I2C_DRIVER_NAME },
+ { .name = "s3fwrn5_i2c", .driver_data = S3FWRN5_VARIANT_FWDL },
+ { .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
- { .compatible = "samsung,s3fwrn5-i2c" },
+ { .compatible = "samsung,s3fwrn5-i2c",
+ .data = (void *)S3FWRN5_VARIANT_FWDL },
+ { .compatible = "samsung,s3nrn4v",
+ .data = (void *)S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
static struct i2c_driver s3fwrn5_i2c_driver = {
.driver = {
- .name = S3FWRN5_I2C_DRIVER_NAME,
- .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
+ .name = "s3fwrn5_i2c",
+ .of_match_table = of_s3fwrn5_i2c_match,
},
.probe = s3fwrn5_i2c_probe,
.remove = s3fwrn5_i2c_remove,
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c
index 5a9de11bbecea..381aa7bf29a23 100644
--- a/drivers/nfc/s3fwrn5/nci.c
+++ b/drivers/nfc/s3fwrn5/nci.c
@@ -8,6 +8,9 @@
#include <linux/completion.h>
#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "s3fwrn5.h"
#include "nci.h"
@@ -20,7 +23,24 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
-const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
+/*
+ * DUAL_OPTION responses are not uniform: GET_VER answers with the chip's
+ * calibration versions instead of a status byte, so stash the payload for
+ * the caller before completing the request.
+ */
+static int s3fwrn5_nci_dual_rsp(struct nci_dev *ndev, struct sk_buff *skb)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+
+ info->dual_rsp_len = min_t(unsigned int, skb->len,
+ sizeof(info->dual_rsp));
+ memcpy(info->dual_rsp, skb->data, info->dual_rsp_len);
+
+ nci_req_complete(ndev, skb->data[0]);
+ return 0;
+}
+
+const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
NCI_PROP_SET_RFREG),
@@ -41,6 +61,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
NCI_PROP_FW_CFG),
.rsp = s3fwrn5_nci_prop_rsp,
},
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
+ NCI_PROP_DUAL_OPTION),
+ .rsp = s3fwrn5_nci_dual_rsp,
+ },
};
#define S3FWRN5_RFREG_SECTION_SIZE 252
@@ -117,3 +142,154 @@ int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)
release_firmware(fw);
return ret;
}
+
+/*
+ * The S3NRN4V expects the single-byte FW_CFG form (just the clock-speed
+ * selector).
+ */
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
+{
+ u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
+
+ return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
+}
+
+/*
+ * An 8-byte calibration version: 5 bytes of date stamp and a 3-byte CSC
+ * code, at fixed offsets both in a blob's 16-byte tail and in each half of
+ * the GET_VER response (HW at offset 0, SW at offset 15).
+ */
+static bool s3fwrn5_nci_dual_version_eq(const u8 *a, const u8 *b)
+{
+ return !memcmp(a + 5, b + 5, 5) && !memcmp(a + 12, b + 12, 3);
+}
+
+static bool s3fwrn5_nci_dual_cal_current(struct s3fwrn5_info *info,
+ const struct firmware *hw_fw,
+ const struct firmware *sw_fw)
+{
+ if (info->dual_rsp_len < 30)
+ return false;
+ if (hw_fw->size < 16 || sw_fw->size < 16)
+ return false;
+
+ return s3fwrn5_nci_dual_version_eq(info->dual_rsp,
+ hw_fw->data + hw_fw->size - 16) &&
+ s3fwrn5_nci_dual_version_eq(info->dual_rsp + 15,
+ sw_fw->data + sw_fw->size - 16);
+}
+
+/*
+ * S3NRN4V RF calibration data update: the HW and SW blobs merged into one
+ * stream (HW first), pushed as START_UPDATE, one SET_OPTION per 252-byte
+ * section, then STOP_UPDATE carrying a 16-bit checksum (running sum of the
+ * merged stream as 32-bit words).
+ */
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name)
+{
+ struct nci_prop_dual_set_option_cmd set_option;
+ struct device *dev = &info->ndev->nfc_dev->dev;
+ const struct firmware *hw_fw, *sw_fw;
+ size_t merged_size, i, len;
+ u8 *merged;
+ u8 stop_cmd[3];
+ u32 checksum;
+ u8 sub_oid;
+ int ret;
+
+ ret = firmware_request_nowarn(&hw_fw, hw_name, dev);
+ if (ret < 0)
+ return ret;
+ ret = firmware_request_nowarn(&sw_fw, sw_name, dev);
+ if (ret < 0)
+ goto out_hw;
+
+ merged_size = hw_fw->size + sw_fw->size;
+
+ /*
+ * The stream is checksummed as 32-bit words and pushed in at most 256
+ * sections (the section index is a single byte); reject blobs that
+ * would silently break either.
+ */
+ if (!merged_size || merged_size % 4 ||
+ merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
+ dev_err(dev, "invalid calibration data size: %zu\n", merged_size);
+ ret = -EINVAL;
+ goto out_sw;
+ }
+
+ /*
+ * Ask the chip for its current calibration versions and skip the
+ * upload when both already match the blobs; a mismatch or an
+ * unparseable answer means the upload proceeds. GET_VER answers with
+ * versions, not a status byte, so nci_prop_cmd()'s return carries no
+ * meaning here.
+ */
+ sub_oid = NCI_PROP_DUAL_SUB_GET_VER;
+ info->dual_rsp_len = 0;
+ nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (s3fwrn5_nci_dual_cal_current(info, hw_fw, sw_fw)) {
+ dev_dbg(dev, "calibration data already current\n");
+ ret = 0;
+ goto out_sw;
+ }
+
+ merged = kvmalloc(merged_size, GFP_KERNEL);
+ if (!merged) {
+ ret = -ENOMEM;
+ goto out_sw;
+ }
+ memcpy(merged, hw_fw->data, hw_fw->size);
+ memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
+
+ checksum = 0;
+ for (i = 0; i + 4 <= merged_size; i += 4)
+ checksum += get_unaligned_le32(merged + i);
+
+ /* START_UPDATE */
+ sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (ret < 0) {
+ dev_err(dev, "Unable to start calibration data update\n");
+ goto out;
+ }
+
+ /* SET_OPTION per section */
+ set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
+ set_option.index = 0;
+ for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
+ len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
+ memcpy(set_option.data, merged + i, len);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
+ len + 2, (__u8 *)&set_option);
+ if (ret < 0) {
+ dev_err(dev, "calibration data update error: %d\n",
+ ret);
+ /* Abort form: STOP_UPDATE with the sub-OID alone. */
+ sub_oid = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1,
+ &sub_oid);
+ goto out;
+ }
+ set_option.index++;
+ }
+
+ /* STOP_UPDATE with checksum */
+ stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ put_unaligned_le16(checksum, &stop_cmd[1]);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to stop calibration data update\n");
+ goto out;
+ }
+
+ dev_dbg(dev, "calibration data update: success\n");
+out:
+ kvfree(merged);
+out_sw:
+ release_firmware(sw_fw);
+out_hw:
+ release_firmware(hw_fw);
+ return ret;
+}
diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h
index bc4bce2bbc4d9..453a1c9d65eb0 100644
--- a/drivers/nfc/s3fwrn5/nci.h
+++ b/drivers/nfc/s3fwrn5/nci.h
@@ -40,6 +40,9 @@ struct nci_prop_stop_rfreg_rsp {
#define NCI_PROP_FW_CFG 0x28
+/* S3NRN4V single-byte FW_CFG payload; 0x11 selects the 19.2 MHz reference. */
+#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
+
struct nci_prop_fw_cfg_cmd {
__u8 clk_type;
__u8 clk_speed;
@@ -50,7 +53,25 @@ struct nci_prop_fw_cfg_rsp {
__u8 status;
};
-extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
+#define NCI_PROP_DUAL_OPTION 0x2a
+
+#define NCI_PROP_DUAL_SUB_GET_VER 0x00
+#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
+#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
+#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
+
+#define NCI_PROP_DUAL_SECTION_SIZE 252
+
+struct nci_prop_dual_set_option_cmd {
+ __u8 sub_oid;
+ __u8 index;
+ __u8 data[NCI_PROP_DUAL_SECTION_SIZE];
+};
+
+extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name);
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h
index 2b492236090b8..63f679f171666 100644
--- a/drivers/nfc/s3fwrn5/s3fwrn5.h
+++ b/drivers/nfc/s3fwrn5/s3fwrn5.h
@@ -21,6 +21,13 @@ enum s3fwrn5_mode {
S3FWRN5_MODE_FW,
};
+enum s3fwrn5_variant {
+ /* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
+ S3FWRN5_VARIANT_FWDL,
+ /* S3NRN4V: no firmware download; FW_CFG + DUAL_OPTION bring-up */
+ S3FWRN5_VARIANT_S3NRN4V,
+};
+
struct s3fwrn5_phy_ops {
void (*set_wake)(void *id, bool sleep);
void (*set_mode)(void *id, enum s3fwrn5_mode);
@@ -36,6 +43,11 @@ struct s3fwrn5_info {
const struct s3fwrn5_phy_ops *phy_ops;
struct s3fwrn5_fw_info fw_info;
+ enum s3fwrn5_variant variant;
+
+ /* Last DUAL_OPTION response payload (GET_VER carries versions). */
+ u8 dual_rsp[32];
+ u8 dual_rsp_len;
struct mutex mutex;
};
@@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
}
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops);
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
void s3fwrn5_remove(struct nci_dev *ndev);
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c
index 8f142a2551016..75397c3dbc58b 100644
--- a/drivers/nfc/s3fwrn5/uart.c
+++ b/drivers/nfc/s3fwrn5/uart.c
@@ -136,7 +136,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
}
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
- &uart_phy_ops);
+ &uart_phy_ops, S3FWRN5_VARIANT_FWDL);
if (ret < 0)
goto err_serdev;
|
| 3/2 | 2026/08/11 22:35 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9984 output: 25 thoughts: 310 Thoughts: **Patch Series Evaluation: s3fwrn5 Driver Enhancement** Okay, let's get down to brass tacks. I've got this patch series modifying the `s3fwrn5` NFC driver. Seems like it's adding support for a new variant, `s3nrn4v`. The first thing I'm seeing is the addition of a new compatible string, "samsung,s3nrn4v", in the device tree bindings, along with a corresponding example in a DTS file. That's standard procedure for adding support for a new hardware rev. Now, it looks like a fairly comprehensive change, touching several core files: `core.c`, `i2c.c`, `nci.c`, `nci.h`, `s3fwrn5.h`, and `uart.c`. Looks like it's a deep dive. My next immediate thought is, how is this driver *reached*? It's an NFC driver, so I'm expecting NCI – the NFC Controller Interface – is the likely candidate. Yup, seems I was correct. The patch extends the existing NCI functionality to handle the `s3nrn4v`. I'm seeing additions to manage the proprietary commands and responses for this new variant, good. Specifically, the `s3fwrn5_nci_prop_ops` structure is being extended with `NCI_PROP_DUAL_OPTION`, and there are new functions being added: `s3fwrn5_nci_dual_rsp`, `s3fwrn5_nci_rf_configure_dual`, and `s3fwrn5_nci_clk_cfg`. This is all making sense, dealing with the low-level communication. Finally, before I dig too deep into the code, I want to confirm that this `s3fwrn5` driver is even enabled in any config. Going to run `grepper` to search for `CONFIG_NFC_S3FWRN5` to see what I can find. Gotta make sure the code is actually used somewhere! Okay, let's see what that turns up. |
| 4/2 | 2026/08/11 22:35 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_NFC_S3FWRN5"
}Results: {
"Output": "arch/arm/configs/exynos_defconfig=83=CONFIG_NFC_SHDLC=y\narch/arm/configs/exynos_defconfig:84:CONFIG_NFC_S3FWRN5_I2C=y\narch/arm/configs/exynos_defconfig-85-CONFIG_DEVTMPFS=y\n--\narch/arm/configs/multi_v7_defconfig=173=CONFIG_NFC_SHDLC=y\narch/arm/configs/multi_v7_defconfig:174:CONFIG_NFC_S3FWRN5_I2C=m\narch/arm/configs/multi_v7_defconfig-175-CONFIG_PCIEPORTBUS=y\n--\narch/arm64/configs/defconfig=208=CONFIG_NFC_NXP_NCI_I2C=m\narch/arm64/configs/defconfig:209:CONFIG_NFC_S3FWRN5_I2C=m\narch/arm64/configs/defconfig-210-CONFIG_PCI=y\n--\ndrivers/gpio/gpiolib-of.c=487=static struct gpio_desc *of_find_gpio_rename(struct device_node *np,\n--\ndrivers/gpio/gpiolib-of.c-526-#endif\ndrivers/gpio/gpiolib-of.c:527:#if IS_ENABLED(CONFIG_NFC_S3FWRN5_I2C)\ndrivers/gpio/gpiolib-of.c-528-\t\t{ \"en\",\t\t\"s3fwrn5,en-gpios\",\t\"samsung,s3fwrn5-i2c\" },\n--\ndrivers/nfc/Makefile=17=obj-$(CONFIG_NFC_NXP_NCI)\t+= nxp-nci/\ndrivers/nfc/Makefile:18:obj-$(CONFIG_NFC_S3FWRN5)\t+= s3fwrn5/\ndrivers/nfc/Makefile-19-obj-$(CONFIG_NFC_ST95HF)\t+= st95hf/\n--\ndrivers/nfc/s3fwrn5/Makefile=8=s3fwrn82_uart-objs = uart.o\ndrivers/nfc/s3fwrn5/Makefile-9-\ndrivers/nfc/s3fwrn5/Makefile:10:obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5.o\ndrivers/nfc/s3fwrn5/Makefile:11:obj-$(CONFIG_NFC_S3FWRN5_I2C) += s3fwrn5_i2c.o\ndrivers/nfc/s3fwrn5/Makefile-12-obj-$(CONFIG_NFC_S3FWRN82_UART) += s3fwrn82_uart.o\n"
} |
| 5/2 | 2026/08/11 22:35 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11500 output: 27 thoughts: 466 Thoughts: **Fuzzing the S3FWRN5 NFC Driver** Okay, so I'm looking at this `CONFIG_NFC_S3FWRN5` driver, and it also uses `CONFIG_NFC_S3FWRN5_I2C`. Right off the bat, it's registering itself through both `i2c_driver` and `serdev_device_driver`, which is good to know, and means I have a few different angles to approach this. Can I fuzz this effectively? Absolutely. Syzkaller's `syz_i2c` and virtual I2C devices would be perfect for the I2C aspect. Plus, the NCI interface is exposed to user-space through netlink or sockets, and Syzkaller has solid support for NCI/NFC fuzzing. That's a major advantage. Ah, wait, it's an NCI driver – `s3fwrn5`. That means it registers through `nci_allocate_device` and `nci_register_device`. The NCI core will handle a lot of the communication, which is important to remember. The driver implements `nci_ops` (`s3fwrn5_nci_ops`), which includes the standard stuff like `init`, `open`, `close`, `send`, `setup`, `post_setup`, and `prop_ops`. This patch specifically adds `s3fwrn5_nci_init` to `nci_ops`, and also `s3fwrn5_nci_dual_rsp` to `s3fwrn5_nci_prop_ops`. It adds `s3fwrn5_nci_rf_configure_dual` as well, which is called from `s3fwrn5_nci_post_setup`. Now, here's where it gets interesting: `s3fwrn5_nci_rf_configure_dual` calls `firmware_request_nowarn`. That's a big flag. That means it's definitely reachable and a prime candidate for fuzzing. I can definitely trigger that code path. Okay, let's nail down which functions to focus on for fuzzing. We've got: * `s3fwrn5_nci_init` * `s3fwrn5_nci_dual_rsp` * `s3fwrn5_nci_rf_configure_dual` * `s3fwrn5_nci_clk_cfg` I need to quickly check `drivers/nfc/s3fwrn5/nci.c` to see if those functions are declared static or not to confirm they're actually exposed. |
| 6/2 | 2026/08/11 22:35 | tool | grepper |
0mArgs:{
"Expression": "s3fwrn5_nci_dual_rsp"
}Results: {
"Output": "drivers/nfc/s3fwrn5/nci.c=18=static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)\n--\ndrivers/nfc/s3fwrn5/nci.c-30- */\ndrivers/nfc/s3fwrn5/nci.c:31:static int s3fwrn5_nci_dual_rsp(struct nci_dev *ndev, struct sk_buff *skb)\ndrivers/nfc/s3fwrn5/nci.c-32-{\n--\ndrivers/nfc/s3fwrn5/nci.c=43=const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {\n--\ndrivers/nfc/s3fwrn5/nci.c-66-\t\t\t\tNCI_PROP_DUAL_OPTION),\ndrivers/nfc/s3fwrn5/nci.c:67:\t\t.rsp = s3fwrn5_nci_dual_rsp,\ndrivers/nfc/s3fwrn5/nci.c-68-\t},\n"
} |
| 7/2 | 2026/08/11 22:35 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12905 output: 25 thoughts: 215 Thoughts: **Analyzing NFC Driver Initialization** Okay, so I'm looking at this code and trying to optimize it based on the instruction to prefer non-static, non-inlined API entrypoints over internal static helper functions. My initial thought was that `s3fwrn5_nci_dual_rsp` is static, which might be a potential issue. But hang on... it's passed as a function pointer within `s3fwrn5_nci_prop_ops`. This means the compiler won't inline it, which actually makes it suitable for use. Let's see… I know that `s3fwrn5_nci_rf_configure_dual` and `s3fwrn5_nci_clk_cfg` are *non*-static, so they're in a good state. However, I recall that `s3fwrn5_nci_init` *is* static. But it is passed within `nci_ops`. That seems similar to the `s3fwrn5_nci_dual_rsp` situation, and may be fine. To be absolutely sure about `s3fwrn5_nci_init`, I'll quickly check its definition in `drivers/nfc/s3fwrn5/core.c`. I need to review how it's used within that file to see if there's any immediate optimization opportunities. |
| 8/2 | 2026/08/11 22:35 | tool | grepper |
0mArgs:{
"Expression": "s3fwrn5_nci_init"
}Results: {
"Output": "drivers/nfc/s3fwrn5/core.c=41=static int s3fwrn5_firmware_update(struct s3fwrn5_info *info)\n--\ndrivers/nfc/s3fwrn5/core.c-79-\ndrivers/nfc/s3fwrn5/core.c:80:static int s3fwrn5_nci_init(struct nci_dev *ndev)\ndrivers/nfc/s3fwrn5/core.c-81-{\n--\ndrivers/nfc/s3fwrn5/core.c=179=static const struct nci_ops s3fwrn5_nci_ops = {\ndrivers/nfc/s3fwrn5/core.c:180:\t.init = s3fwrn5_nci_init,\ndrivers/nfc/s3fwrn5/core.c-181-\t.open = s3fwrn5_nci_open,\n"
} |
| 9/2 | 2026/08/11 22:35 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13712 output: 29 thoughts: 45 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Analyzing the Static Nature of a Dual RF Configuration** Alright, so the initial thought is that the situation is "static". That immediately suggests a potential problem – or at least, something that warrants careful investigation. I'm inferring a lack of dynamism, possibly a hardcoded configuration or a fixed state. This feels like a smell test, so I need to dig deeper. My immediate instinct, given the context of likely NFC/RF and dual configurations, is to jump straight into the kernel code. Specifically, I'm thinking about the `s3fwrn5` driver, and it's almost certain that the key function responsible for this dual RF setup is `s3fwrn5_nci_rf_configure_dual` within `drivers/nfc/s3fwrn5/nci.c`. I have a feeling the static-ness I'm sensing is reflected somewhere within that function, whether it's through the parameters passed, the logic flow, or the way resources are allocated. My gut says that this is the point where the configuration is hardcoded, preventing the system from adjusting dynamically to user needs. |
| 10/2 | 2026/08/11 22:35 | tool | grepper |
0mArgs:{
"Expression": "s3fwrn5_nci_rf_configure_dual"
}Results: {
"Output": "drivers/nfc/s3fwrn5/core.c=144=static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)\n--\ndrivers/nfc/s3fwrn5/core.c-149-\tif (info-\u003evariant == S3FWRN5_VARIANT_S3NRN4V) {\ndrivers/nfc/s3fwrn5/core.c:150:\t\tret = s3fwrn5_nci_rf_configure_dual(info, S3NRN4V_HWREG_NAME,\ndrivers/nfc/s3fwrn5/core.c-151-\t\t\t\t\t\t S3NRN4V_SWREG_NAME);\n--\ndrivers/nfc/s3fwrn5/nci.c=167=static bool s3fwrn5_nci_dual_cal_current(struct s3fwrn5_info *info,\n--\ndrivers/nfc/s3fwrn5/nci.c-187- */\ndrivers/nfc/s3fwrn5/nci.c:188:int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,\ndrivers/nfc/s3fwrn5/nci.c-189-\t\t\t\t const char *hw_name, const char *sw_name)\n--\ndrivers/nfc/s3fwrn5/nci.h=72=int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);\ndrivers/nfc/s3fwrn5/nci.h:73:int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,\ndrivers/nfc/s3fwrn5/nci.h-74-\t\t\t\t const char *hw_name, const char *sw_name);\n"
} |
| 11/2 | 2026/08/11 22:35 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14555 output: 27 thoughts: 26 Thoughts: Okay, here's my summary, presented as a stream of thought: **Analyzing the Non-Static Nature** Alright, I'm thinking this is not a static situation, that's the first thing that jumps out at me. We're dealing with something dynamic here. That means the focus shifts, we need to look for the things that are variable. Let's see... what's the next step? My gut says we need to look at what's changing. Right. Okay, let's dive into that `s3fwrn5_nci_clk_cfg` element. This seems like a pivotal piece, considering what we're looking at. Gotta figure out what it's doing. |
| 12/2 | 2026/08/11 22:36 | tool | grepper |
0mArgs:{
"Expression": "s3fwrn5_nci_clk_cfg"
}Results: {
"Output": "drivers/nfc/s3fwrn5/core.c=80=static int s3fwrn5_nci_init(struct nci_dev *ndev)\n--\ndrivers/nfc/s3fwrn5/core.c-85-\tif (info-\u003evariant == S3FWRN5_VARIANT_S3NRN4V) {\ndrivers/nfc/s3fwrn5/core.c:86:\t\tret = s3fwrn5_nci_clk_cfg(info);\ndrivers/nfc/s3fwrn5/core.c-87-\t\tif (ret \u003c 0)\n--\ndrivers/nfc/s3fwrn5/nci.c=73=int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)\n--\ndrivers/nfc/s3fwrn5/nci.c-149- */\ndrivers/nfc/s3fwrn5/nci.c:150:int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)\ndrivers/nfc/s3fwrn5/nci.c-151-{\n--\ndrivers/nfc/s3fwrn5/nci.h=73=int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,\ndrivers/nfc/s3fwrn5/nci.h-74-\t\t\t\t const char *hw_name, const char *sw_name);\ndrivers/nfc/s3fwrn5/nci.h:75:int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);\ndrivers/nfc/s3fwrn5/nci.h-76-\n"
} |
| 13/2 | 2026/08/11 22:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15240 output: 23 thoughts: 310 Thoughts: **Analyzing the S3FWRN5 NCI Driver Entrypoints** Okay, so this isn't a static situation. Good. I need to figure out which functions are the key entry points for the S3FWRN5 NCI driver. Based on what's here, the initial functions to consider are: `s3fwrn5_nci_rf_configure_dual`, `s3fwrn5_nci_clk_cfg`, `s3fwrn5_nci_dual_rsp`, and `s3fwrn5_nci_init`. Wait a minute, `s3fwrn5_nci_dual_rsp` and `s3fwrn5_nci_init` are *static*. Normally, I'd skip those, but they are also passed around as function pointers, which strongly suggests that they won't be inlined by the compiler. Since the instructions say to prioritize non-static, non-inlined API entrypoints but these *are* entrypoints from the NCI core, I'll include them. Thus, `s3fwrn5_nci_rf_configure_dual` and `s3fwrn5_nci_clk_cfg` will be the primary focus. Including `s3fwrn5_nci_dual_rsp` and `s3fwrn5_nci_init` is also relevant because they are important parts of this subsystem. Now, let's just quickly check for any relevant configuration defines. It looks like `NFC_S3FWRN5` and `NFC_S3FWRN5_I2C` are likely candidates. I should probably also check the `drivers/nfc/s3fwrn5/Kconfig` file to see if there are any other dependencies or configurations that I need to be aware of. |
| 14/2 | 2026/08/11 22:36 | tool | grepper |
0mArgs:{
"Expression": "config NFC_S3FWRN5"
}Results: {
"Output": "drivers/nfc/s3fwrn5/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/nfc/s3fwrn5/Kconfig:2:config NFC_S3FWRN5\ndrivers/nfc/s3fwrn5/Kconfig-3-\ttristate\n--\ndrivers/nfc/s3fwrn5/Kconfig-9-\ndrivers/nfc/s3fwrn5/Kconfig:10:config NFC_S3FWRN5_I2C\ndrivers/nfc/s3fwrn5/Kconfig-11-\ttristate \"Samsung S3FWRN5 I2C support\"\n"
} |
| 15/2 | 2026/08/11 22:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16162 output: 41 thoughts: 20 Thoughts: Here's my summary, as though I'm reading and thinking about that Kconfig file: **Diving into the NFC Driver's Configuration** Alright, let's crack open `drivers/nfc/s3fwrn5/Kconfig`. I need to get a handle on how this S3FWRN5 NFC driver is configured within the kernel. Immediately, I'm expecting to see the typical structure of a Kconfig file: dependencies, help text, and the actual configuration symbols. I'll be looking for the main enable option for the driver, I'm sure it will be something like `CONFIG_NFC_S3FWRN5`. Then I will look for any sub-options, maybe I will find settings to toggle specific features, like different NFC modes or power-saving configurations, or even debug options. I'm particularly interested in the dependencies. I need to understand what other kernel components, hardware interfaces (like I2C, SPI), or even other NFC modules this driver relies on. Are there any platform-specific dependencies? That will influence how I interpret the options. The help text is crucial. I expect it to give me a concise description of the driver's purpose, the device it supports, and potentially any limitations or known issues. This is also where I will find information on how the options interact and what they do. Finally, I'll pay close attention to the default values of these options. Are they sensible for a wide range of devices, or are they geared towards a particular hardware setup? This helps me understand the intended use case of the driver. If the default settings are obscure, I'll need to drill down deeper into the documentation. Ultimately, I'll be using this information to determine how to best build and configure a kernel with this driver, and how to troubleshoot it if there are any issues. This file will be my roadmap to understanding and controlling the S3FWRN5 driver's behavior. |
| 16/2 | 2026/08/11 22:36 | tool | read-file |
0mArgs:{
"File": "drivers/nfc/s3fwrn5/Kconfig",
"FirstLine": 1,
"LineCount": 100
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\tconfig NFC_S3FWRN5\n 3:\t\ttristate\n 4:\t\tselect CRYPTO_LIB_SHA1\n 5:\t\thelp\n 6:\t\t Core driver for Samsung S3FWRN5 NFC chip. Contains core utilities\n 7:\t\t of chip. It's intended to be used by PHYs to avoid duplicating lots\n 8:\t\t of common code.\n 9:\t\n 10:\tconfig NFC_S3FWRN5_I2C\n 11:\t\ttristate \"Samsung S3FWRN5 I2C support\"\n 12:\t\tdepends on NFC_NCI \u0026\u0026 I2C\n 13:\t\tselect NFC_S3FWRN5\n 14:\t\tdefault n\n 15:\t\thelp\n 16:\t\t This module adds support for an I2C interface to the S3FWRN5 chip.\n 17:\t\t Select this if your platform is using the I2C bus.\n 18:\t\n 19:\t\t To compile this driver as a module, choose m here. The module will\n 20:\t\t be called s3fwrn5_i2c.ko.\n 21:\t\t Say N if unsure.\n 22:\t\n 23:\tconfig NFC_S3FWRN82_UART\n 24:\t tristate \"Samsung S3FWRN82 UART support\"\n 25:\t depends on NFC_NCI \u0026\u0026 SERIAL_DEV_BUS\n 26:\t select NFC_S3FWRN5\n 27:\t help\n 28:\t This module adds support for a UART interface to the S3FWRN82 chip.\n 29:\t Select this if your platform is using the UART bus.\n 30:\t\n 31:\t To compile this driver as a module, choose m here. The module will\n 32:\t be called s3fwrn82_uart.ko.\n 33:\t Say N if unsure.\n"
} |
| 17/2 | 2026/08/11 22:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17159 output: 209 thoughts: 797 Thoughts: **My Configuration Plan for the S3FWRN5 NFC Driver** Okay, so I'm diving into this `s3fwrn5` NFC driver and the first thing I need to figure out are the relevant configurations. I need to get support for this `s3nrn4v` variant working. The code mentions `NFC_S3FWRN5_I2C` and `NFC_S3FWRN82_UART`, right? And based on the dependencies, it looks like `NFC_S3FWRN5_I2C` depends on `NFC_NCI` and `I2C`, while `NFC_S3FWRN82_UART` depends on `NFC_NCI` and `SERIAL_DEV_BUS`. Makes sense. I need both of those - `NFC_S3FWRN5_I2C` and `NFC_S3FWRN82_UART`. `NFC_NCI` is a core NFC config, so I'll be sure that is included. We can include the others if necessary. Now, let's look at the functions themselves. From what I can tell, I need to focus on: * `s3fwrn5_nci_rf_configure_dual` * `s3fwrn5_nci_clk_cfg` * `s3fwrn5_nci_dual_rsp` * `s3fwrn5_nci_init` Wait, `s3fwrn5_nci_dual_rsp` and `s3fwrn5_nci_init` are static. However, they're not inlined, which makes them entry points for the new functionality. So I will include them. `s3fwrn5_nci_rf_configure_dual` and `s3fwrn5_nci_clk_cfg` are also not static. I think I'll include those too because they are not static. Just to be absolutely sure, let me double-check. `s3fwrn5_nci_init` is static. Yes. And `s3fwrn5_nci_dual_rsp` is also static, as I thought. What about `s3fwrn5_nci_rf_configure_dual`? No, not static. And `s3fwrn5_nci_clk_cfg`? Also not static. So, here's what I'm including in the final list: Functions to look at: * `s3fwrn5_nci_rf_configure_dual` * `s3fwrn5_nci_clk_cfg` * `s3fwrn5_nci_dual_rsp` * `s3fwrn5_nci_init` Configurations to enable: * `NFC_S3FWRN5_I2C` * `NFC_S3FWRN82_UART` * `NFC_NCI` The patch adds support for the `s3nrn4v` variant to the `s3fwrn5` NFC driver. This new variant has new NCI commands and responses and introduces a new RF configuration routine. The driver is accessible via the NCI interface and the I2C/UART buses. I should be able to fuzz it using virtual devices or syzkaller's NCI support. One final check. I see that `s3fwrn5_nci_dual_rsp` is passed as a function pointer in `s3fwrn5_nci_prop_ops`, so it can't be inlined. Similarly, `s3fwrn5_nci_init` is in `s3fwrn5_nci_ops`. Alright, I'm confident with this. I will now `set-results`. |
| 18/2 | 2026/08/11 22:36 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"NFC_NCI",
"NFC_S3FWRN5_I2C",
"NFC_S3FWRN82_UART"
],
"FocusSymbols": [
"s3fwrn5_nci_rf_configure_dual",
"s3fwrn5_nci_clk_cfg",
"s3fwrn5_nci_dual_rsp",
"s3fwrn5_nci_init"
],
"Reasoning": "The patch adds support for the s3nrn4v variant to the s3fwrn5 NFC driver. It introduces new NCI proprietary commands and responses, and a new RF configuration routine. The driver is reachable via the NCI interface and I2C/UART buses, which can be fuzzed using virtual devices or syzkaller's NCI support.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"NFC_NCI",
"NFC_S3FWRN5_I2C",
"NFC_S3FWRN82_UART"
],
"FocusSymbols": [
"s3fwrn5_nci_rf_configure_dual",
"s3fwrn5_nci_clk_cfg",
"s3fwrn5_nci_dual_rsp",
"s3fwrn5_nci_init"
],
"Reasoning": "The patch adds support for the s3nrn4v variant to the s3fwrn5 NFC driver. It introduces new NCI proprietary commands and responses, and a new RF configuration routine. The driver is reachable via the NCI interface and I2C/UART buses, which can be fuzzed using virtual devices or syzkaller's NCI support.",
"WorthFuzzing": true
} |
| 19/1 | 2026/08/11 22:36 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 20/2 | 2026/08/11 22:36 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch introduces support for a new NFC chip variant (S3NRN4V) and adds new NCI commands for firmware configuration and calibration data updates. All data structures sent to the device (such as `set_option`, `stop_cmd`, and `sub_oid`) are fully initialized before being passed to `nci_prop_cmd`. The length of the data sent is carefully calculated to match the initialized bytes, preventing any uninitialized stack memory from being sent. Data received from the device is copied into `info-\u003edual_rsp` with its length tracked by `info-\u003edual_rsp_len`, and bounds checks are performed before accessing it. While there is a potential out-of-bounds read if `skb-\u003elen` is 0 in `s3fwrn5_nci_dual_rsp` (`skb-\u003edata[0]`), this would be caught by KASAN. There are no uninitialized memory leaks to user space or uninitialized variables used in control flow. Standard KASAN is sufficient to test these changes.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 7b4020aae05276d4a47c3fbad7153d7da32efb91
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 11 22:35:27 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
index 12baee45752c7..40a69466802bb 100644
--- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
+++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
@@ -14,6 +14,7 @@ properties:
enum:
- samsung,s3fwrn5-i2c
- samsung,s3fwrn82
+ - samsung,s3nrn4v
en-gpios:
maxItems: 1
@@ -29,6 +30,9 @@ properties:
clocks:
maxItems: 1
+ pvdd-supply:
+ description: PVDD power supply
+
wake-gpios:
maxItems: 1
description:
@@ -58,12 +62,25 @@ allOf:
properties:
compatible:
contains:
- const: samsung,s3fwrn5-i2c
+ enum:
+ - samsung,s3fwrn5-i2c
+ - samsung,s3nrn4v
then:
required:
- interrupts
- reg
+ # Deployed DTs for the older parts never described a supply, so PVDD is
+ # only required for the new device.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ required:
+ - pvdd-supply
+
examples:
- |
#include <dt-bindings/gpio/gpio.h>
diff --git a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
index ebdca0e2d2f3a..bdba848275db4 100644
--- a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
+++ b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
@@ -601,9 +601,22 @@ &gcc {
};
&i2c1 {
- /* Samsung NFC @ 0x27 */
-
status = "okay";
+
+ nfc@27 {
+ compatible = "samsung,s3nrn4v";
+ reg = <0x27>;
+
+ interrupts-extended = <&tlmm 31 IRQ_TYPE_EDGE_RISING>;
+ en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
+ wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
+ clocks = <&rpmhcc RPMH_LN_BB_CLK2>;
+
+ pvdd-supply = <&vreg_l20b>;
+
+ pinctrl-0 = <&nfc_default>;
+ pinctrl-names = "default";
+ };
};
&i2c3 {
@@ -825,6 +838,22 @@ &tlmm {
<13 1>, /* NC */
<63 2>; /* WLAN UART */
+ nfc_default: nfc-default-state {
+ irq-pins {
+ pins = "gpio31";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ pd-pins {
+ pins = "gpio56";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+ };
+
sdc2_card_det_n: sdc2-card-det-state {
pins = "gpio65";
function = "gpio";
diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c
index af0fa8bd970b4..563470cb928de 100644
--- a/drivers/nfc/s3fwrn5/core.c
+++ b/drivers/nfc/s3fwrn5/core.c
@@ -20,6 +20,9 @@
NFC_PROTO_ISO14443_B_MASK | \
NFC_PROTO_ISO15693_MASK)
+#define S3NRN4V_HWREG_NAME "samsung/s3nrn4v/hwreg.bin"
+#define S3NRN4V_SWREG_NAME "samsung/s3nrn4v/swreg.bin"
+
static int s3fwrn5_firmware_init(struct s3fwrn5_info *info)
{
struct s3fwrn5_fw_info *fw_info = &info->fw_info;
@@ -74,6 +77,22 @@ static int s3fwrn5_firmware_update(struct s3fwrn5_info *info)
return ret;
}
+static int s3fwrn5_nci_init(struct nci_dev *ndev)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+ int ret = 0;
+
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ ret = s3fwrn5_nci_clk_cfg(info);
+ if (ret < 0)
+ dev_err(&ndev->nfc_dev->dev,
+ "failed to configure the reference clock: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+
static int s3fwrn5_nci_open(struct nci_dev *ndev)
{
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
@@ -127,6 +146,15 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
int ret;
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ ret = s3fwrn5_nci_rf_configure_dual(info, S3NRN4V_HWREG_NAME,
+ S3NRN4V_SWREG_NAME);
+ if (ret < 0)
+ dev_warn(&ndev->nfc_dev->dev,
+ "RF calibration data update failed: %d\n", ret);
+ return 0;
+ }
+
if (s3fwrn5_firmware_init(info)) {
//skip bootloader mode
return 0;
@@ -149,6 +177,7 @@ static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
}
static const struct nci_ops s3fwrn5_nci_ops = {
+ .init = s3fwrn5_nci_init,
.open = s3fwrn5_nci_open,
.close = s3fwrn5_nci_close,
.send = s3fwrn5_nci_send,
@@ -158,7 +187,7 @@ static const struct nci_ops s3fwrn5_nci_ops = {
};
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops)
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
{
struct s3fwrn5_info *info;
int ret;
@@ -170,6 +199,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
info->phy_id = phy_id;
info->pdev = pdev;
info->phy_ops = phy_ops;
+ info->variant = variant;
mutex_init(&info->mutex);
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
@@ -225,3 +255,5 @@ EXPORT_SYMBOL(s3fwrn5_recv_frame);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver");
MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>");
+MODULE_FIRMWARE(S3NRN4V_HWREG_NAME);
+MODULE_FIRMWARE(S3NRN4V_SWREG_NAME);
diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c
index 4ba7626117118..884420ac83b5b 100644
--- a/drivers/nfc/s3fwrn5/i2c.c
+++ b/drivers/nfc/s3fwrn5/i2c.c
@@ -11,13 +11,12 @@
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/module.h>
+#include <linux/regulator/consumer.h>
#include <net/nfc/nfc.h>
#include "phy_common.h"
-#define S3FWRN5_I2C_DRIVER_NAME "s3fwrn5_i2c"
-
struct s3fwrn5_i2c_phy {
struct phy_common common;
struct i2c_client *i2c_dev;
@@ -146,6 +145,7 @@ static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)
static int s3fwrn5_i2c_probe(struct i2c_client *client)
{
+ enum s3fwrn5_variant variant;
struct s3fwrn5_i2c_phy *phy;
int ret;
@@ -160,6 +160,11 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
phy->i2c_dev = client;
i2c_set_clientdata(client, phy);
+ ret = devm_regulator_get_enable(&client->dev, "pvdd");
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "failed to enable pvdd\n");
+
phy->common.gpio_en = devm_gpiod_get(&client->dev, "en", GPIOD_OUT_HIGH);
if (IS_ERR(phy->common.gpio_en))
return PTR_ERR(phy->common.gpio_en);
@@ -179,14 +184,15 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
"failed to get clock\n");
+ variant = (uintptr_t)i2c_get_match_data(client);
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
- &i2c_phy_ops);
+ &i2c_phy_ops, variant);
if (ret < 0)
return ret;
ret = devm_request_threaded_irq(&client->dev, phy->i2c_dev->irq, NULL,
s3fwrn5_i2c_irq_thread_fn, IRQF_ONESHOT,
- S3FWRN5_I2C_DRIVER_NAME, phy);
+ "s3fwrn5_i2c", phy);
if (ret)
goto s3fwrn5_remove;
@@ -205,21 +211,25 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
}
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
- { .name = S3FWRN5_I2C_DRIVER_NAME },
+ { .name = "s3fwrn5_i2c", .driver_data = S3FWRN5_VARIANT_FWDL },
+ { .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
- { .compatible = "samsung,s3fwrn5-i2c" },
+ { .compatible = "samsung,s3fwrn5-i2c",
+ .data = (void *)S3FWRN5_VARIANT_FWDL },
+ { .compatible = "samsung,s3nrn4v",
+ .data = (void *)S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
static struct i2c_driver s3fwrn5_i2c_driver = {
.driver = {
- .name = S3FWRN5_I2C_DRIVER_NAME,
- .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
+ .name = "s3fwrn5_i2c",
+ .of_match_table = of_s3fwrn5_i2c_match,
},
.probe = s3fwrn5_i2c_probe,
.remove = s3fwrn5_i2c_remove,
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c
index 5a9de11bbecea..381aa7bf29a23 100644
--- a/drivers/nfc/s3fwrn5/nci.c
+++ b/drivers/nfc/s3fwrn5/nci.c
@@ -8,6 +8,9 @@
#include <linux/completion.h>
#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "s3fwrn5.h"
#include "nci.h"
@@ -20,7 +23,24 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
-const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
+/*
+ * DUAL_OPTION responses are not uniform: GET_VER answers with the chip's
+ * calibration versions instead of a status byte, so stash the payload for
+ * the caller before completing the request.
+ */
+static int s3fwrn5_nci_dual_rsp(struct nci_dev *ndev, struct sk_buff *skb)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+
+ info->dual_rsp_len = min_t(unsigned int, skb->len,
+ sizeof(info->dual_rsp));
+ memcpy(info->dual_rsp, skb->data, info->dual_rsp_len);
+
+ nci_req_complete(ndev, skb->data[0]);
+ return 0;
+}
+
+const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
NCI_PROP_SET_RFREG),
@@ -41,6 +61,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
NCI_PROP_FW_CFG),
.rsp = s3fwrn5_nci_prop_rsp,
},
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
+ NCI_PROP_DUAL_OPTION),
+ .rsp = s3fwrn5_nci_dual_rsp,
+ },
};
#define S3FWRN5_RFREG_SECTION_SIZE 252
@@ -117,3 +142,154 @@ int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)
release_firmware(fw);
return ret;
}
+
+/*
+ * The S3NRN4V expects the single-byte FW_CFG form (just the clock-speed
+ * selector).
+ */
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
+{
+ u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
+
+ return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
+}
+
+/*
+ * An 8-byte calibration version: 5 bytes of date stamp and a 3-byte CSC
+ * code, at fixed offsets both in a blob's 16-byte tail and in each half of
+ * the GET_VER response (HW at offset 0, SW at offset 15).
+ */
+static bool s3fwrn5_nci_dual_version_eq(const u8 *a, const u8 *b)
+{
+ return !memcmp(a + 5, b + 5, 5) && !memcmp(a + 12, b + 12, 3);
+}
+
+static bool s3fwrn5_nci_dual_cal_current(struct s3fwrn5_info *info,
+ const struct firmware *hw_fw,
+ const struct firmware *sw_fw)
+{
+ if (info->dual_rsp_len < 30)
+ return false;
+ if (hw_fw->size < 16 || sw_fw->size < 16)
+ return false;
+
+ return s3fwrn5_nci_dual_version_eq(info->dual_rsp,
+ hw_fw->data + hw_fw->size - 16) &&
+ s3fwrn5_nci_dual_version_eq(info->dual_rsp + 15,
+ sw_fw->data + sw_fw->size - 16);
+}
+
+/*
+ * S3NRN4V RF calibration data update: the HW and SW blobs merged into one
+ * stream (HW first), pushed as START_UPDATE, one SET_OPTION per 252-byte
+ * section, then STOP_UPDATE carrying a 16-bit checksum (running sum of the
+ * merged stream as 32-bit words).
+ */
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name)
+{
+ struct nci_prop_dual_set_option_cmd set_option;
+ struct device *dev = &info->ndev->nfc_dev->dev;
+ const struct firmware *hw_fw, *sw_fw;
+ size_t merged_size, i, len;
+ u8 *merged;
+ u8 stop_cmd[3];
+ u32 checksum;
+ u8 sub_oid;
+ int ret;
+
+ ret = firmware_request_nowarn(&hw_fw, hw_name, dev);
+ if (ret < 0)
+ return ret;
+ ret = firmware_request_nowarn(&sw_fw, sw_name, dev);
+ if (ret < 0)
+ goto out_hw;
+
+ merged_size = hw_fw->size + sw_fw->size;
+
+ /*
+ * The stream is checksummed as 32-bit words and pushed in at most 256
+ * sections (the section index is a single byte); reject blobs that
+ * would silently break either.
+ */
+ if (!merged_size || merged_size % 4 ||
+ merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
+ dev_err(dev, "invalid calibration data size: %zu\n", merged_size);
+ ret = -EINVAL;
+ goto out_sw;
+ }
+
+ /*
+ * Ask the chip for its current calibration versions and skip the
+ * upload when both already match the blobs; a mismatch or an
+ * unparseable answer means the upload proceeds. GET_VER answers with
+ * versions, not a status byte, so nci_prop_cmd()'s return carries no
+ * meaning here.
+ */
+ sub_oid = NCI_PROP_DUAL_SUB_GET_VER;
+ info->dual_rsp_len = 0;
+ nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (s3fwrn5_nci_dual_cal_current(info, hw_fw, sw_fw)) {
+ dev_dbg(dev, "calibration data already current\n");
+ ret = 0;
+ goto out_sw;
+ }
+
+ merged = kvmalloc(merged_size, GFP_KERNEL);
+ if (!merged) {
+ ret = -ENOMEM;
+ goto out_sw;
+ }
+ memcpy(merged, hw_fw->data, hw_fw->size);
+ memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
+
+ checksum = 0;
+ for (i = 0; i + 4 <= merged_size; i += 4)
+ checksum += get_unaligned_le32(merged + i);
+
+ /* START_UPDATE */
+ sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (ret < 0) {
+ dev_err(dev, "Unable to start calibration data update\n");
+ goto out;
+ }
+
+ /* SET_OPTION per section */
+ set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
+ set_option.index = 0;
+ for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
+ len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
+ memcpy(set_option.data, merged + i, len);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
+ len + 2, (__u8 *)&set_option);
+ if (ret < 0) {
+ dev_err(dev, "calibration data update error: %d\n",
+ ret);
+ /* Abort form: STOP_UPDATE with the sub-OID alone. */
+ sub_oid = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1,
+ &sub_oid);
+ goto out;
+ }
+ set_option.index++;
+ }
+
+ /* STOP_UPDATE with checksum */
+ stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ put_unaligned_le16(checksum, &stop_cmd[1]);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to stop calibration data update\n");
+ goto out;
+ }
+
+ dev_dbg(dev, "calibration data update: success\n");
+out:
+ kvfree(merged);
+out_sw:
+ release_firmware(sw_fw);
+out_hw:
+ release_firmware(hw_fw);
+ return ret;
+}
diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h
index bc4bce2bbc4d9..453a1c9d65eb0 100644
--- a/drivers/nfc/s3fwrn5/nci.h
+++ b/drivers/nfc/s3fwrn5/nci.h
@@ -40,6 +40,9 @@ struct nci_prop_stop_rfreg_rsp {
#define NCI_PROP_FW_CFG 0x28
+/* S3NRN4V single-byte FW_CFG payload; 0x11 selects the 19.2 MHz reference. */
+#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
+
struct nci_prop_fw_cfg_cmd {
__u8 clk_type;
__u8 clk_speed;
@@ -50,7 +53,25 @@ struct nci_prop_fw_cfg_rsp {
__u8 status;
};
-extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
+#define NCI_PROP_DUAL_OPTION 0x2a
+
+#define NCI_PROP_DUAL_SUB_GET_VER 0x00
+#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
+#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
+#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
+
+#define NCI_PROP_DUAL_SECTION_SIZE 252
+
+struct nci_prop_dual_set_option_cmd {
+ __u8 sub_oid;
+ __u8 index;
+ __u8 data[NCI_PROP_DUAL_SECTION_SIZE];
+};
+
+extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name);
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h
index 2b492236090b8..63f679f171666 100644
--- a/drivers/nfc/s3fwrn5/s3fwrn5.h
+++ b/drivers/nfc/s3fwrn5/s3fwrn5.h
@@ -21,6 +21,13 @@ enum s3fwrn5_mode {
S3FWRN5_MODE_FW,
};
+enum s3fwrn5_variant {
+ /* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
+ S3FWRN5_VARIANT_FWDL,
+ /* S3NRN4V: no firmware download; FW_CFG + DUAL_OPTION bring-up */
+ S3FWRN5_VARIANT_S3NRN4V,
+};
+
struct s3fwrn5_phy_ops {
void (*set_wake)(void *id, bool sleep);
void (*set_mode)(void *id, enum s3fwrn5_mode);
@@ -36,6 +43,11 @@ struct s3fwrn5_info {
const struct s3fwrn5_phy_ops *phy_ops;
struct s3fwrn5_fw_info fw_info;
+ enum s3fwrn5_variant variant;
+
+ /* Last DUAL_OPTION response payload (GET_VER carries versions). */
+ u8 dual_rsp[32];
+ u8 dual_rsp_len;
struct mutex mutex;
};
@@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
}
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops);
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
void s3fwrn5_remove(struct nci_dev *ndev);
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c
index 8f142a2551016..75397c3dbc58b 100644
--- a/drivers/nfc/s3fwrn5/uart.c
+++ b/drivers/nfc/s3fwrn5/uart.c
@@ -136,7 +136,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
}
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
- &uart_phy_ops);
+ &uart_phy_ops, S3FWRN5_VARIANT_FWDL);
if (ret < 0)
goto err_serdev;
|
| 21/3 | 2026/08/11 22:36 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9981 output: 247 thoughts: 2776 Thoughts: **KMSAN Review: NFC Driver Patch - S3NRN4V Support** Okay, let's break this down. This patch is introducing support for a new NFC chip, S3NRN4V, into the `s3fwrn5` driver. My initial concern is always uninitialized memory, so that's where I'll focus. The core of the patch involves adding device tree bindings, updating the driver logic, introducing new NCI commands (FW_CFG, DUAL_OPTION), and handling firmware loading and calibration data. I'm seeing `s3fwrn5_nci_dual_rsp` which copies data from an `skb` into `info->dual_rsp`. The code uses `skb->data[0]` for status, and I immediately note that if `skb->len` is zero, we *could* have an out-of-bounds read, but that's an *out-of-bounds* read, not uninitialized memory. KASAN should flag that one, and if it's hit, it's not a KMSAN problem. Next, `s3fwrn5_nci_rf_configure_dual` uses `info->dual_rsp`, but I'm relieved to see that the code checks `info->dual_rsp_len` before accessing the data, avoiding any uninitialized read concerns. Looking closer at `s3fwrn5_nci_rf_configure_dual` again, I see the stack allocated `set_option` struct, which is not zero-initialized. I'm checking it carefully, as stack allocation is usually a source of problems. The code then copies `len` bytes from `merged` into `set_option.data`. Importantly, it appears only `len + 2` bytes of `set_option` are sent to the device, the code checks to ensure only initialized data is sent, preventing any potential leak. I confirmed that the structure is packed, which means no padding is added, so it's all good. I'm checking the `stop_cmd`, `s3fwrn5_nci_clk_cfg` and the other parts of the routine, and they appear fully initialized. I'm running through the potential problematic spots again. I've checked the memory sent to the device and confirmed it's fully initialized, I checked that if the code reads memory it's always handled safely, I've checked the usage of `info->dual_rsp`, which is used, and length checked. In summary, I can't identify any uninitialized memory vulnerabilities in this patch. The code seems well-structured, and all memory transfers appear safe. The OOB potential in `skb->data[0]` is caught by KASAN. Therefore, a specialized KMSAN fuzzing campaign isn't needed here. |
| 22/3 | 2026/08/11 22:36 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces support for a new NFC chip variant (S3NRN4V) and adds new NCI commands for firmware configuration and calibration data updates. All data structures sent to the device (such as `set_option`, `stop_cmd`, and `sub_oid`) are fully initialized before being passed to `nci_prop_cmd`. The length of the data sent is carefully calculated to match the initialized bytes, preventing any uninitialized stack memory from being sent. Data received from the device is copied into `info-\u003edual_rsp` with its length tracked by `info-\u003edual_rsp_len`, and bounds checks are performed before accessing it. While there is a potential out-of-bounds read if `skb-\u003elen` is 0 in `s3fwrn5_nci_dual_rsp` (`skb-\u003edata[0]`), this would be caught by KASAN. There are no uninitialized memory leaks to user space or uninitialized variables used in control flow. Standard KASAN is sufficient to test these changes.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces support for a new NFC chip variant (S3NRN4V) and adds new NCI commands for firmware configuration and calibration data updates. All data structures sent to the device (such as `set_option`, `stop_cmd`, and `sub_oid`) are fully initialized before being passed to `nci_prop_cmd`. The length of the data sent is carefully calculated to match the initialized bytes, preventing any uninitialized stack memory from being sent. Data received from the device is copied into `info-\u003edual_rsp` with its length tracked by `info-\u003edual_rsp_len`, and bounds checks are performed before accessing it. While there is a potential out-of-bounds read if `skb-\u003elen` is 0 in `s3fwrn5_nci_dual_rsp` (`skb-\u003edata[0]`), this would be caught by KASAN. There are no uninitialized memory leaks to user space or uninitialized variables used in control flow. Standard KASAN is sufficient to test these changes.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|