AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series implements PTP (IEEE 1588) support and refactors host flood filtering for the NXP NETC switch family (drivers/net/dsa/netc/, drivers/ptp/ptp_netc.c, and net/dsa/tag_netc.c). The switch and timer drivers require physical or emulated PCIe devices specific to NXP SoCs (Freescale vendor ID with Device Tree node bindings) that are not emulated in standard QEMU/GCE amd64 virtualized environments. Furthermore, the DSA tagging driver (tag_netc) cannot be bound or exercised without the underlying hardware switch. Consequently, the modified code is structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/09/20 10:36 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 8c4cc14b4ddfdf7e900d5c519b9f2bbbceada636\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Sep 20 10:36:25 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig\nindex 793f7691a24f3..7c930579f1c94 100644\n--- a/drivers/net/dsa/netc/Kconfig\n+++ b/drivers/net/dsa/netc/Kconfig\n@@ -4,6 +4,8 @@ config NET_DSA_NETC_SWITCH\n \tdepends on ARM64 || COMPILE_TEST\n \tdepends on NET_DSA \u0026\u0026 PCI\n \tdepends on NET_VENDOR_FREESCALE\n+\tdepends on PTP_1588_CLOCK_OPTIONAL\n+\tdepends on PTP_NETC_V4_TIMER || PTP_NETC_V4_TIMER=n\n \tselect NET_DSA_TAG_NETC\n \tselect FSL_ENETC_MDIO\n \tselect NXP_NTMP\ndiff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile\nindex f40b13c702e07..572b833ad80fc 100644\n--- a/drivers/net/dsa/netc/Makefile\n+++ b/drivers/net/dsa/netc/Makefile\n@@ -1,3 +1,4 @@\n # SPDX-License-Identifier: GPL-2.0-only\n obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o\n-nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o\n+nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o \\\n+\t\t\tnetc_ptp.o\ndiff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c\nindex 77077352c1a57..af39a71aece0f 100644\n--- a/drivers/net/dsa/netc/netc_main.c\n+++ b/drivers/net/dsa/netc/netc_main.c\n@@ -65,6 +65,21 @@ netc_get_tag_protocol(struct dsa_switch *ds, int port,\n \treturn DSA_TAG_PROTO_NETC;\n }\n \n+static int netc_connect_tag_protocol(struct dsa_switch *ds,\n+\t\t\t\t     enum dsa_tag_protocol proto)\n+{\n+\tstruct netc_tagger_data *tagger_data;\n+\n+\tif (proto != DSA_TAG_PROTO_NETC)\n+\t\treturn -EPROTONOSUPPORT;\n+\n+\ttagger_data = ds-\u003etagger_data;\n+\ttagger_data-\u003etxtstamp_handler = netc_port_txtstamp_handler;\n+\ttagger_data-\u003eonestep_sync_enqueue = netc_port_onestep_sync_enqueue;\n+\n+\treturn 0;\n+}\n+\n static void netc_port_rmw(struct netc_port *np, u32 reg,\n \t\t\t  u32 mask, u32 val)\n {\n@@ -80,7 +95,7 @@ static void netc_port_rmw(struct netc_port *np, u32 reg,\n \tnetc_port_wr(np, reg, new);\n }\n \n-static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)\n+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)\n {\n \tif (is_netc_pseudo_port(np))\n \t\treturn;\n@@ -266,6 +281,11 @@ static int netc_init_all_ports(struct netc_switch *priv)\n \n \t\tnp-\u003eswitch_priv = priv;\n \t\tnp-\u003eiobase = priv-\u003eregs.port + PORT_IOBASE(i);\n+\t\t/* The ipft_hf_eid is initialized to an invalid entry\n+\t\t * ID because the host flood rule (IPFT entry) has not\n+\t\t * been created.\n+\t\t */\n+\t\tnp-\u003eipft_hf_eid = NTMP_NULL_ENTRY_ID;\n \t\tnetc_port_get_capability(np);\n \t\tpriv-\u003eports[i] = np;\n \t}\n@@ -286,6 +306,15 @@ static int netc_init_all_ports(struct netc_switch *priv)\n \t\t\t\tdev_err(dev, \"Failed to create MDIO bus\\n\");\n \t\t\t\treturn err;\n \t\t\t}\n+\n+\t\t\t/* Only the user port needs to support PTP feature, so\n+\t\t\t * PTP-related resources, such as tstamp_queue,\n+\t\t\t * tstamp_lock, etc., are initialized only for the user\n+\t\t\t * port.\n+\t\t\t */\n+\t\t\terr = netc_port_ptp_init(np);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n \t\t}\n \t}\n \n@@ -549,6 +578,12 @@ static void netc_port_fixed_config(struct netc_port *np)\n \tnetc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,\n \t\t      PCR_L2DOSE | PCR_L3DOSE);\n \n+\t/* Enable ingress port filter table lookup, if no match is found,\n+\t * the frame is allowed and passed to the next frame processing\n+\t * function.\n+\t */\n+\tnetc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);\n+\n \t/* Set the quanta value of TX PAUSE frame */\n \tnetc_mac_port_wr(np, NETC_PM_PAUSE_QUANTA(0), NETC_PAUSE_QUANTA);\n \n@@ -869,6 +904,15 @@ static int netc_switch_bpt_default_config(struct netc_switch *priv)\n \treturn 0;\n }\n \n+static struct pci_dev *netc_get_ptp_timer(struct netc_switch *priv)\n+{\n+\tstruct pci_bus *bus = priv-\u003epdev-\u003ebus;\n+\tu32 devfn = priv-\u003einfo-\u003etmr_devfn;\n+\n+\treturn pci_get_domain_bus_and_slot(pci_domain_nr(bus),\n+\t\t\t\t\t   bus-\u003enumber, devfn);\n+}\n+\n static int netc_setup(struct dsa_switch *ds)\n {\n \tstruct netc_switch *priv = ds-\u003epriv;\n@@ -881,13 +925,23 @@ static int netc_setup(struct dsa_switch *ds)\n \n \tnetc_get_switch_capabilities(priv);\n \n+\t/* The PTP timer sits on the same PCI bus as the switch. PCI creates\n+\t * every function's pci_dev during bus enumeration, before any driver\n+\t * probes, so we can grab the timer's pci_dev here even if the timer\n+\t * driver has not probed yet.\n+\t */\n+\tpriv-\u003etmr_dev = netc_get_ptp_timer(priv);\n+\tif (!priv-\u003etmr_dev)\n+\t\tdev_info(priv-\u003edev,\n+\t\t\t \"PTP timer PCI device not found\\n\");\n+\n \terr = netc_init_all_ports(priv);\n \tif (err)\n-\t\treturn err;\n+\t\tgoto put_ptp_timer;\n \n \terr = netc_init_ntmp_user(priv);\n \tif (err)\n-\t\treturn err;\n+\t\tgoto put_ptp_timer;\n \n \tINIT_HLIST_HEAD(\u0026priv-\u003efdb_list);\n \tmutex_init(\u0026priv-\u003efdbt_lock);\n@@ -926,6 +980,8 @@ static int netc_setup(struct dsa_switch *ds)\n \tmutex_destroy(\u0026priv-\u003efdbt_lock);\n \tmutex_destroy(\u0026priv-\u003evft_lock);\n \tnetc_free_ntmp_user(priv);\n+put_ptp_timer:\n+\tpci_dev_put(priv-\u003etmr_dev);\n \n \treturn err;\n }\n@@ -938,20 +994,18 @@ static void netc_destroy_all_lists(struct netc_switch *priv)\n \tmutex_destroy(\u0026priv-\u003evft_lock);\n }\n \n-static void netc_free_host_flood_rules(struct netc_switch *priv)\n+static void netc_free_ports_resources(struct netc_switch *priv)\n {\n \tstruct dsa_port *dp;\n \n-\tdsa_switch_for_each_user_port(dp, priv-\u003eds) {\n+\tdsa_switch_for_each_available_port(dp, priv-\u003eds) {\n \t\tstruct netc_port *np = priv-\u003eports[dp-\u003eindex];\n \n-\t\t/* No need to clear the hardware IPFT entry. Because PCIe\n-\t\t * FLR will be performed when the switch is re-registered,\n-\t\t * it will reset hardware state. So only need to free the\n-\t\t * memory to avoid memory leak.\n-\t\t */\n-\t\tkfree(np-\u003ehost_flood);\n-\t\tnp-\u003ehost_flood = NULL;\n+\t\tif (!dsa_port_is_user(dp))\n+\t\t\tcontinue;\n+\n+\t\tdisable_delayed_work_sync(\u0026np-\u003etstamp_timeout_work);\n+\t\tnetc_port_purge_tstamp_queue(np);\n \t}\n }\n \n@@ -961,8 +1015,9 @@ static void netc_teardown(struct dsa_switch *ds)\n \n \tdisable_delayed_work_sync(\u0026priv-\u003efdbt_ageing_work);\n \tnetc_destroy_all_lists(priv);\n-\tnetc_free_host_flood_rules(priv);\n \tnetc_free_ntmp_user(priv);\n+\tnetc_free_ports_resources(priv);\n+\tpci_dev_put(priv-\u003etmr_dev);\n }\n \n static bool netc_port_is_emdio_consumer(struct device_node *node)\n@@ -1517,6 +1572,7 @@ static int netc_port_enable(struct dsa_switch *ds, int port,\n \t\treturn err;\n \t}\n \n+\tnetc_port_enable_onestep(np);\n \tnp-\u003eenable = true;\n \n \treturn 0;\n@@ -1534,6 +1590,7 @@ static void netc_port_disable(struct dsa_switch *ds, int port)\n \tif (!np-\u003eenable)\n \t\treturn;\n \n+\tnetc_port_disable_onestep(np);\n \tclk_disable_unprepare(np-\u003eref_clk);\n \tnp-\u003eenable = false;\n }\n@@ -1719,14 +1776,8 @@ static int netc_port_add_host_flood_rule(struct netc_port *np,\n \tu32 cfg;\n \tint err;\n \n-\tif (!uc \u0026\u0026 !mc) {\n-\t\t/* Disable ingress port filter table lookup */\n-\t\tnetc_port_wr(np, NETC_PIPFCR, 0);\n-\t\tnp-\u003euc = false;\n-\t\tnp-\u003emc = false;\n-\n+\tif (!uc \u0026\u0026 !mc)\n \t\treturn 0;\n-\t}\n \n \thost_flood = kzalloc_obj(*host_flood);\n \tif (!host_flood)\n@@ -1759,48 +1810,43 @@ static int netc_port_add_host_flood_rule(struct netc_port *np,\n \tcfge-\u003ecfg = cpu_to_le32(cfg);\n \n \terr = ntmp_ipft_add_entry(\u0026priv-\u003entmp, host_flood);\n-\tif (err) {\n-\t\tkfree(host_flood);\n-\t\treturn err;\n-\t}\n+\tif (err)\n+\t\tgoto free_host_flood;\n \n \tnp-\u003euc = uc;\n \tnp-\u003emc = mc;\n-\tnp-\u003ehost_flood = host_flood;\n-\t/* Enable ingress port filter table lookup */\n-\tnetc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);\n+\tnp-\u003eipft_hf_eid = host_flood-\u003eentry_id;\n \n-\treturn 0;\n+free_host_flood:\n+\tkfree(host_flood);\n+\n+\treturn err;\n }\n \n-static void netc_port_remove_host_flood(struct netc_port *np,\n-\t\t\t\t\tstruct ipft_entry_data *host_flood)\n+static int netc_port_remove_host_flood(struct netc_port *np)\n {\n \tstruct netc_switch *priv = np-\u003eswitch_priv;\n-\tbool disable_host_flood = false;\n+\tu32 entry_id = np-\u003eipft_hf_eid;\n+\tint err;\n \n-\tif (!host_flood)\n-\t\treturn;\n+\tif (entry_id == NTMP_NULL_ENTRY_ID)\n+\t\treturn 0;\n \n-\tif (np-\u003ehost_flood == host_flood)\n-\t\tdisable_host_flood = true;\n+\terr = ntmp_ipft_delete_entry(\u0026priv-\u003entmp, entry_id);\n+\tif (err)\n+\t\treturn err;\n \n-\tntmp_ipft_delete_entry(\u0026priv-\u003entmp, host_flood-\u003eentry_id);\n-\tkfree(host_flood);\n+\tnp-\u003eipft_hf_eid = NTMP_NULL_ENTRY_ID;\n+\tnp-\u003euc = false;\n+\tnp-\u003emc = false;\n \n-\tif (disable_host_flood) {\n-\t\tnp-\u003ehost_flood = NULL;\n-\t\tnp-\u003euc = false;\n-\t\tnp-\u003emc = false;\n-\t\tnetc_port_wr(np, NETC_PIPFCR, 0);\n-\t}\n+\treturn 0;\n }\n \n static void netc_port_set_host_flood(struct dsa_switch *ds, int port,\n \t\t\t\t     bool uc, bool mc)\n {\n \tstruct netc_port *np = NETC_PORT(ds, port);\n-\tstruct ipft_entry_data *old_host_flood;\n \n \t/* Do not add host flood rule to ingress port filter table when\n \t * the port has joined a bridge. Otherwise, the ingress frames\n@@ -1808,7 +1854,12 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,\n \t * will be redirected directly to the CPU port.\n \t */\n \tif (dsa_port_bridge_dev_get(np-\u003edp)) {\n-\t\tnetc_port_remove_host_flood(np, np-\u003ehost_flood);\n+\t\tif (!netc_port_remove_host_flood(np))\n+\t\t\treturn;\n+\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"Failed to delete host flood rule on bridge port %d\\n\",\n+\t\t\tport);\n \n \t\treturn;\n \t}\n@@ -1817,21 +1868,24 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,\n \t\treturn;\n \n \t/* IPFT does not support in-place updates to the KEYE element,\n-\t * we need to add a new entry and then delete the old one. So\n-\t * save the old entry first.\n+\t * we need to delete the old one and then add the new rule. If\n+\t * the deletion fails, return immediately.\n \t */\n-\told_host_flood = np-\u003ehost_flood;\n-\tnp-\u003ehost_flood = NULL;\n-\n-\tif (netc_port_add_host_flood_rule(np, uc, mc)) {\n-\t\tnp-\u003ehost_flood = old_host_flood;\n-\t\tdev_err(ds-\u003edev, \"Failed to add host flood rule on port %d\\n\",\n+\tif (netc_port_remove_host_flood(np)) {\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"Failed to delete old host flood rule on port %d\\n\",\n \t\t\tport);\n+\n \t\treturn;\n \t}\n \n-\t/* Remove the old host flood entry */\n-\tnetc_port_remove_host_flood(np, old_host_flood);\n+\t/* Restoring the previous configuration is pointless because\n+\t * .port_set_host_flood() returns void, so the upper layer cannot\n+\t * detect the error and the RX flags have changed.\n+\t */\n+\tif (netc_port_add_host_flood_rule(np, uc, mc))\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"Failed to add host flood rule on port %d\\n\", port);\n }\n \n static int netc_single_vlan_aware_bridge(struct dsa_switch *ds,\n@@ -1996,6 +2050,8 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,\n \tstruct netc_port *np = NETC_PORT(ds, port);\n \tstruct netc_switch *priv = ds-\u003epriv;\n \tu16 vlan_unaware_pvid;\n+\tbool uc = np-\u003euc;\n+\tbool mc = np-\u003emc;\n \tint err;\n \n \tif (!bridge.num) {\n@@ -2007,6 +2063,12 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,\n \tif (err)\n \t\treturn err;\n \n+\terr = netc_port_remove_host_flood(np);\n+\tif (err) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"Failed to delete host flood rule\");\n+\t\treturn err;\n+\t}\n+\n \tnetc_port_set_mlo(np, MLO_NOT_OVERRIDE);\n \n \tif (br_vlan_enabled(bridge.dev))\n@@ -2020,8 +2082,6 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,\n \tnetc_port_set_pvid(np, vlan_unaware_pvid);\n \n out:\n-\tnetc_port_remove_host_flood(np, np-\u003ehost_flood);\n-\n \tif (atomic_inc_return(\u0026priv-\u003ebr_cnt) == 1)\n \t\tschedule_delayed_work(\u0026priv-\u003efdbt_ageing_work,\n \t\t\t\t      READ_ONCE(priv-\u003efdbt_ageing_delay));\n@@ -2031,6 +2091,11 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,\n disable_mlo:\n \tnetc_port_set_mlo(np, MLO_DISABLE);\n \n+\tif (netc_port_add_host_flood_rule(np, uc, mc))\n+\t\tdev_err(ds-\u003edev,\n+\t\t\t\"Failed to restore host flood rule on port %u\\n\",\n+\t\t\tport);\n+\n \treturn err;\n }\n \n@@ -2379,6 +2444,7 @@ static void netc_mac_link_up(struct phylink_config *config,\n \tnetc_port_set_rx_pause(np, rx_pause);\n \tnetc_port_mac_tx_enable(np);\n \tnetc_port_mac_rx_enable(np);\n+\tnetc_port_enable_onestep(np);\n }\n \n static void netc_mac_link_down(struct phylink_config *config,\n@@ -2389,6 +2455,7 @@ static void netc_mac_link_down(struct phylink_config *config,\n \tstruct netc_port *np;\n \n \tnp = NETC_PORT(dp-\u003eds, dp-\u003eindex);\n+\tnetc_port_disable_onestep(np);\n \tnetc_port_mac_rx_graceful_stop(np);\n \tnetc_port_mac_tx_graceful_stop(np);\n \tnetc_port_remove_dynamic_entries(np);\n@@ -2402,6 +2469,7 @@ static const struct phylink_mac_ops netc_phylink_mac_ops = {\n \n static const struct dsa_switch_ops netc_switch_ops = {\n \t.get_tag_protocol\t\t= netc_get_tag_protocol,\n+\t.connect_tag_protocol\t\t= netc_connect_tag_protocol,\n \t.setup\t\t\t\t= netc_setup,\n \t.teardown\t\t\t= netc_teardown,\n \t.phylink_get_caps\t\t= netc_phylink_get_caps,\n@@ -2430,6 +2498,11 @@ static const struct dsa_switch_ops netc_switch_ops = {\n \t.get_sset_count\t\t\t= netc_port_get_sset_count,\n \t.get_strings\t\t\t= netc_port_get_strings,\n \t.get_ethtool_stats\t\t= netc_port_get_ethtool_stats,\n+\t.get_ts_info\t\t\t= netc_get_ts_info,\n+\t.port_hwtstamp_set\t\t= netc_port_hwtstamp_set,\n+\t.port_hwtstamp_get\t\t= netc_port_hwtstamp_get,\n+\t.port_rxtstamp\t\t\t= netc_port_rxtstamp,\n+\t.port_txtstamp\t\t\t= netc_port_txtstamp,\n };\n \n static int netc_switch_probe(struct pci_dev *pdev,\ndiff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c\nindex 34aeb6fceb3cb..4fd0ce6770c33 100644\n--- a/drivers/net/dsa/netc/netc_platform.c\n+++ b/drivers/net/dsa/netc/netc_platform.c\n@@ -50,6 +50,7 @@ static void imx94_switch_phylink_get_caps(int port,\n \n static const struct netc_switch_info imx94_info = {\n \t.num_ports = 4,\n+\t.tmr_devfn = PCI_DEVFN(0, 1),\n \t.phylink_get_caps = imx94_switch_phylink_get_caps,\n };\n \ndiff --git a/drivers/net/dsa/netc/netc_ptp.c b/drivers/net/dsa/netc/netc_ptp.c\nnew file mode 100644\nindex 0000000000000..1bc0f09562062\n--- /dev/null\n+++ b/drivers/net/dsa/netc/netc_ptp.c\n@@ -0,0 +1,849 @@\n+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)\n+/*\n+ * NXP NETC switch driver\n+ * Copyright 2025-2026 NXP\n+ */\n+\n+#include \u003clinux/ptp_classify.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n+\n+#include \"netc_switch.h\"\n+\n+#define NETC_NUM_TS_REQ_ID\t\t16\n+#define NETC_TSTAMP_TIMEOUT\t\t(5 * HZ)\n+#define NETC_MAX_STEP_OFFSET\t\t0x1ff\n+#define NETC_ONESTEP_QTH\t\t512\n+/* The 30-bit timestamp of the To_Port subtype 3 tag lets the hardware\n+ * account for a single wrap, so the correction field of a one-step Sync\n+ * frame is only correct if it is sent out within 2^30 ns after the\n+ * software timestamp is read. Past this window the frame is beyond\n+ * repair, and PM_SINGLE_STEP becomes safe to reprogram.\n+ */\n+#define NETC_ONESTEP_VALID_WINDOW\t0x40000000 /* ns */\n+\n+static void netc_port_tstamp_timeout_work(struct work_struct *work)\n+{\n+\tstruct netc_port *np = container_of(work, struct netc_port,\n+\t\t\t\t\t    tstamp_timeout_work.work);\n+\tstruct sk_buff_head free_list;\n+\tstruct sk_buff *skb, *skb_tmp;\n+\n+\t__skb_queue_head_init(\u0026free_list);\n+\n+\tspin_lock_bh(\u0026np-\u003etstamp_lock);\n+\tskb_queue_walk_safe(\u0026np-\u003etstamp_queue, skb, skb_tmp) {\n+\t\tif (time_before64(jiffies_64, NETC_SKB_CB(skb)-\u003eptp_tx_time +\n+\t\t\t\t  NETC_TSTAMP_TIMEOUT))\n+\t\t\tcontinue;\n+\n+\t\tdev_dbg_ratelimited(np-\u003eswitch_priv-\u003edev,\n+\t\t\t\t    \"Port %d ts_req_id %u which seems lost\\n\",\n+\t\t\t\t    np-\u003edp-\u003eindex, NETC_SKB_CB(skb)-\u003ets_req_id);\n+\n+\t\t__skb_unlink(skb, \u0026np-\u003etstamp_queue);\n+\t\t__skb_queue_tail(\u0026free_list, skb);\n+\t}\n+\n+\t/* Reschedule if there are still pending clones that have not\n+\t * timed out yet.\n+\t */\n+\tif (!skb_queue_empty(\u0026np-\u003etstamp_queue))\n+\t\tschedule_delayed_work(\u0026np-\u003etstamp_timeout_work,\n+\t\t\t\t      NETC_TSTAMP_TIMEOUT);\n+\n+\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\t__skb_queue_purge(\u0026free_list);\n+}\n+\n+static int netc_get_ts_req_id(struct netc_port *np)\n+{\n+\tDECLARE_BITMAP(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);\n+\tstruct sk_buff *skb, *skb_tmp;\n+\tunsigned long ts_req_id;\n+\n+\tbitmap_zero(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);\n+\n+\tskb_queue_walk_safe(\u0026np-\u003etstamp_queue, skb, skb_tmp)\n+\t\t__set_bit(NETC_SKB_CB(skb)-\u003ets_req_id, ts_req_id_bitmap);\n+\n+\tts_req_id = find_first_zero_bit(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);\n+\tif (ts_req_id == NETC_NUM_TS_REQ_ID) {\n+\t\tdev_dbg_ratelimited(np-\u003eswitch_priv-\u003edev,\n+\t\t\t\t    \"Port %d has no available ts_req_id\\n\",\n+\t\t\t\t    np-\u003edp-\u003eindex);\n+\t\treturn -ENOSPC;\n+\t}\n+\n+\treturn ts_req_id;\n+}\n+\n+static int netc_get_phc_time(struct netc_switch *priv, u64 *ns)\n+{\n+\tif (unlikely(!priv-\u003etmr_dev))\n+\t\treturn -ENODEV;\n+\n+\treturn netc_timer_get_current_time(priv-\u003etmr_dev, ns);\n+}\n+\n+static void netc_port_set_onestep_control(struct netc_port *np,\n+\t\t\t\t\t  bool csum_update, int offset)\n+{\n+\tu32 val;\n+\n+\tval = PM_SINGLE_STEP_EN | FIELD_PREP(PM_SINGLE_STEP_OFFSET, offset);\n+\tif (csum_update)\n+\t\tval |= PM_SINGLE_STEP_CH;\n+\tnetc_mac_port_wr(np, NETC_PM_SINGLE_STEP(0), val);\n+}\n+\n+static void netc_port_program_onestep(struct netc_port *np,\n+\t\t\t\t      struct sk_buff *skb)\n+{\n+\tu16 correction_offset = NETC_SKB_CB(skb)-\u003ecorrection_offset;\n+\tu16 tstamp_offset = NETC_SKB_CB(skb)-\u003etimestamp_offset;\n+\tu64 tstamp = NETC_SKB_CB(skb)-\u003etstamp;\n+\tu8 *hdr = skb_mac_header(skb);\n+\tbool csum_update = false;\n+\t__be32 new_sec_l, new_ns;\n+\t__be16 new_sec_h;\n+\tu64 sec;\n+\tu32 ns;\n+\n+\t/* Update originTimestamp field of Sync packet\n+\t * - 48 bits seconds field\n+\t * - 32 bits nanoseconds field\n+\t */\n+\tsec = div_u64_rem(tstamp, NSEC_PER_SEC, \u0026ns);\n+\tnew_sec_h = htons((sec \u003e\u003e 32) \u0026 0xffff);\n+\tnew_sec_l = htonl(sec \u0026 0xffffffff);\n+\tnew_ns = htonl(ns);\n+\n+\tif (NETC_SKB_CB(skb)-\u003eis_udp) {\n+\t\t__be32 old_sec_l, old_ns;\n+\t\tstruct udphdr *uh;\n+\t\t__be16 old_sec_h;\n+\n+\t\tif (skb-\u003eip_summed == CHECKSUM_PARTIAL) {\n+\t\t\tcsum_update = true;\n+\t\t\tgoto update_timestamp;\n+\t\t}\n+\n+\t\tif (unlikely(!skb_transport_header_was_set(skb)))\n+\t\t\tuh = (struct udphdr *)(hdr + tstamp_offset -\n+\t\t\t\t\t       sizeof(struct ptp_header) -\n+\t\t\t\t\t       sizeof(struct udphdr));\n+\t\telse\n+\t\t\tuh = udp_hdr(skb);\n+\n+\t\t/* For IPv4, a UDP checksum of zero on the wire means \"no\n+\t\t * checksum\". For IPv6, its UDP checksum is mandatory and\n+\t\t * never zero.\n+\t\t */\n+\t\tif (!uh-\u003echeck)\n+\t\t\tgoto update_timestamp;\n+\n+\t\told_sec_h = __get_unaligned_t(__be16, hdr + tstamp_offset);\n+\t\told_sec_l = __get_unaligned_t(__be32, hdr + tstamp_offset + 2);\n+\t\told_ns = __get_unaligned_t(__be32, hdr + tstamp_offset + 6);\n+\t\tinet_proto_csum_replace2(\u0026uh-\u003echeck, skb, old_sec_h,\n+\t\t\t\t\t new_sec_h, false);\n+\t\tinet_proto_csum_replace4(\u0026uh-\u003echeck, skb, old_sec_l,\n+\t\t\t\t\t new_sec_l, false);\n+\t\tinet_proto_csum_replace4(\u0026uh-\u003echeck, skb, old_ns,\n+\t\t\t\t\t new_ns, false);\n+\t\tcsum_update = true;\n+\t}\n+\n+update_timestamp:\n+\t__put_unaligned_t(__be16, new_sec_h, hdr + tstamp_offset);\n+\t__put_unaligned_t(__be32, new_sec_l, hdr + tstamp_offset + 2);\n+\t__put_unaligned_t(__be32, new_ns, hdr + tstamp_offset + 6);\n+\n+\tnetc_port_set_onestep_control(np, csum_update, correction_offset);\n+}\n+\n+void netc_port_disable_onestep(struct netc_port *np)\n+{\n+\tstruct sk_buff_head free_list;\n+\n+\tif (!dsa_port_is_user(np-\u003edp))\n+\t\treturn;\n+\n+\t__skb_queue_head_init(\u0026free_list);\n+\n+\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\tskb_queue_splice_init(\u0026np-\u003eonestep_queue, \u0026free_list);\n+\tnp-\u003eonestep_state = NETC_ONESTEP_PORT_INACTIVE;\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+\n+\tcancel_work_sync(\u0026np-\u003eonestep_work);\n+\t__skb_queue_purge(\u0026free_list);\n+}\n+\n+void netc_port_enable_onestep(struct netc_port *np)\n+{\n+\tif (!dsa_port_is_user(np-\u003edp))\n+\t\treturn;\n+\n+\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\tnp-\u003eonestep_state = NETC_ONESTEP_IDLE;\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+}\n+\n+static void netc_port_purge_onestep_queue(struct netc_port *np)\n+{\n+\tstruct sk_buff_head free_list;\n+\n+\t__skb_queue_head_init(\u0026free_list);\n+\n+\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\tskb_queue_splice_init(\u0026np-\u003eonestep_queue, \u0026free_list);\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+\n+\t__skb_queue_purge(\u0026free_list);\n+}\n+\n+static void netc_port_onestep_work(struct work_struct *work)\n+{\n+\tstruct netc_port *np = container_of(work, struct netc_port,\n+\t\t\t\t\t    onestep_work);\n+\tstruct netc_switch *priv = np-\u003eswitch_priv;\n+\tstruct netc_tagger_data *tagger_data;\n+\tstruct sk_buff *clone = NULL;\n+\tstruct sk_buff *skb = NULL;\n+\tint ts_req_id;\n+\tu64 tstamp;\n+\n+\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\n+\tif (unlikely(np-\u003eonestep_state == NETC_ONESTEP_PORT_INACTIVE))\n+\t\tgoto purge_onestep_queue;\n+\n+skb_dequeue:\n+\tskb = __skb_dequeue(\u0026np-\u003eonestep_queue);\n+\tif (!skb)\n+\t\tgoto set_onestep_state_idle;\n+\n+\t/* Clone is a ts_req_id token only; its payload is never read, so\n+\t * sharing the buffer with the mutated original is fine.\n+\t */\n+\tclone = skb_clone(skb, GFP_ATOMIC);\n+\tif (unlikely(!clone)) {\n+\t\tkfree_skb(skb);\n+\t\tgoto skb_dequeue;\n+\t}\n+\n+\tspin_lock_bh(\u0026np-\u003etstamp_lock);\n+\tts_req_id = netc_get_ts_req_id(np);\n+\tif (unlikely(ts_req_id \u003c 0)) {\n+\t\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\n+\t\t/* Re-queuing the frame and immediately rescheduling the work\n+\t\t * would busy-loop on system_percpu_wq and burn CPU until an\n+\t\t * ID is freed, so drop this frame and move on to the next one\n+\t\t * in the queue instead.\n+\t\t */\n+\t\tnp-\u003eonestep_state = NETC_ONESTEP_SCHEDULED;\n+\t\tschedule_work(\u0026np-\u003eonestep_work);\n+\n+\t\tgoto onestep_unlock;\n+\t}\n+\n+\t/* PHC is unavailable, drop the whole queue */\n+\tif (unlikely(netc_get_phc_time(priv, \u0026tstamp))) {\n+\t\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\t\tgoto set_onestep_state_idle;\n+\t}\n+\n+\tNETC_SKB_CB(skb)-\u003etstamp = tstamp;\n+\tNETC_SKB_CB(skb)-\u003ets_req_id = ts_req_id;\n+\tNETC_SKB_CB(skb)-\u003eptp_flag = NETC_PTP_FLAG_ONESTEP;\n+\tNETC_SKB_CB(clone)-\u003ets_req_id = ts_req_id;\n+\tNETC_SKB_CB(clone)-\u003eptp_tx_time = jiffies_64;\n+\tNETC_SKB_CB(clone)-\u003eptp_flag = NETC_PTP_FLAG_ONESTEP;\n+\tnp-\u003eonestep_tx_time = NETC_SKB_CB(clone)-\u003eptp_tx_time;\n+\tnp-\u003eonestep_ts_req_id = ts_req_id;\n+\n+\t__skb_queue_tail(\u0026np-\u003etstamp_queue, clone);\n+\tif (!delayed_work_pending(\u0026np-\u003etstamp_timeout_work))\n+\t\tschedule_delayed_work(\u0026np-\u003etstamp_timeout_work,\n+\t\t\t\t      NETC_TSTAMP_TIMEOUT);\n+\n+\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\n+\tnp-\u003eonestep_state = NETC_ONESTEP_IN_FLIGHT;\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+\n+\tnetc_port_program_onestep(np, skb);\n+\ttagger_data = priv-\u003eds-\u003etagger_data;\n+\ttagger_data-\u003eonestep_sync_xmit(skb, np-\u003edp-\u003euser);\n+\n+\treturn;\n+\n+set_onestep_state_idle:\n+\tnp-\u003eonestep_state = NETC_ONESTEP_IDLE;\n+purge_onestep_queue:\n+\t__skb_queue_purge(\u0026np-\u003eonestep_queue);\n+onestep_unlock:\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+\tkfree_skb(skb);\n+\tkfree_skb(clone);\n+}\n+\n+static bool netc_onestep_timeout(struct netc_port *np)\n+{\n+\tu64 expire_time;\n+\n+\t/* Use monotonic jiffies_64, as the PHC may be stepped backwards.\n+\t * Add one tick since the ns-to-jiffies conversion rounds down, so\n+\t * the software window is never shorter than the hardware window.\n+\t */\n+\texpire_time = np-\u003eonestep_tx_time + 1 +\n+\t\t      nsecs_to_jiffies64(NETC_ONESTEP_VALID_WINDOW);\n+\tif (np-\u003eonestep_state == NETC_ONESTEP_IN_FLIGHT \u0026\u0026\n+\t    time_after64(jiffies_64, expire_time))\n+\t\treturn true;\n+\n+\treturn false;\n+}\n+\n+void netc_port_onestep_sync_enqueue(struct dsa_switch *ds, int port,\n+\t\t\t\t    struct sk_buff *skb)\n+{\n+\tstruct netc_port *np = NETC_PORT(ds, port);\n+\n+\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\tif (unlikely(np-\u003eonestep_state == NETC_ONESTEP_PORT_INACTIVE)) {\n+\t\tkfree_skb(skb);\n+\t\tgoto onestep_unlock;\n+\t}\n+\n+\tif (unlikely(skb_queue_len(\u0026np-\u003eonestep_queue) \u003e= NETC_ONESTEP_QTH)) {\n+\t\tdev_dbg_ratelimited(np-\u003eswitch_priv-\u003edev,\n+\t\t\t\t    \"The onestep_queue of port %d is full\\n\",\n+\t\t\t\t    port);\n+\t\tkfree_skb(skb);\n+\t\tgoto onestep_unlock;\n+\t}\n+\n+\t__skb_queue_tail(\u0026np-\u003eonestep_queue, skb);\n+\tif (likely(np-\u003eonestep_state == NETC_ONESTEP_IDLE) ||\n+\t    netc_onestep_timeout(np)) {\n+\t\tnp-\u003eonestep_state = NETC_ONESTEP_SCHEDULED;\n+\t\tschedule_work(\u0026np-\u003eonestep_work);\n+\t}\n+\n+onestep_unlock:\n+\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+}\n+\n+int netc_port_ptp_init(struct netc_port *np)\n+{\n+\t/* Initialize to invalid entry IDs */\n+\tfor (int i = 0; i \u003c NETC_PTP_MAX; i++)\n+\t\tnp-\u003eptp_ipft_eid[i] = NTMP_NULL_ENTRY_ID;\n+\n+\tspin_lock_init(\u0026np-\u003eonestep_lock);\n+\t__skb_queue_head_init(\u0026np-\u003eonestep_queue);\n+\tINIT_WORK(\u0026np-\u003eonestep_work, netc_port_onestep_work);\n+\n+\tspin_lock_init(\u0026np-\u003etstamp_lock);\n+\t__skb_queue_head_init(\u0026np-\u003etstamp_queue);\n+\tINIT_DELAYED_WORK(\u0026np-\u003etstamp_timeout_work,\n+\t\t\t  netc_port_tstamp_timeout_work);\n+\n+\treturn 0;\n+}\n+\n+void netc_port_purge_tstamp_queue(struct netc_port *np)\n+{\n+\tstruct sk_buff_head free_list;\n+\n+\t__skb_queue_head_init(\u0026free_list);\n+\n+\tspin_lock_bh(\u0026np-\u003etstamp_lock);\n+\tskb_queue_splice_init(\u0026np-\u003etstamp_queue, \u0026free_list);\n+\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\n+\t__skb_queue_purge(\u0026free_list);\n+}\n+\n+static int netc_get_phc_index(struct netc_switch *priv)\n+{\n+\tif (!priv-\u003etmr_dev)\n+\t\treturn -1;\n+\n+\treturn ptp_clock_index_by_dev(\u0026priv-\u003etmr_dev-\u003edev);\n+}\n+\n+int netc_get_ts_info(struct dsa_switch *ds, int port,\n+\t\t     struct kernel_ethtool_ts_info *info)\n+{\n+\tstruct netc_switch *priv = ds-\u003epriv;\n+\n+\tinfo-\u003ephc_index = netc_get_phc_index(priv);\n+\tif (info-\u003ephc_index \u003c 0)\n+\t\treturn 0;\n+\n+\tinfo-\u003eso_timestamping |= SOF_TIMESTAMPING_TX_HARDWARE |\n+\t\t\t\t SOF_TIMESTAMPING_RX_HARDWARE |\n+\t\t\t\t SOF_TIMESTAMPING_RAW_HARDWARE;\n+\n+\tinfo-\u003etx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON) |\n+\t\t\t BIT(HWTSTAMP_TX_ONESTEP_SYNC);\n+\n+\tinfo-\u003erx_filters = BIT(HWTSTAMP_FILTER_NONE) |\n+\t\t\t   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |\n+\t\t\t   BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |\n+\t\t\t   BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT);\n+\n+\treturn 0;\n+}\n+\n+static int netc_port_del_ptp_filter(struct netc_port *np)\n+{\n+\tstruct netc_switch *priv = np-\u003eswitch_priv;\n+\tint ret = 0;\n+\tint err;\n+\n+\tfor (int i = 0; i \u003c NETC_PTP_MAX; i++) {\n+\t\tif (np-\u003eptp_ipft_eid[i] == NTMP_NULL_ENTRY_ID)\n+\t\t\tcontinue;\n+\n+\t\t/* No -ETIMEDOUT here: with the command BD ring enabled, the\n+\t\t * hardware never times out on a command. Any remaining error\n+\t\t * means the entry is still present in the table.\n+\t\t */\n+\t\terr = ntmp_ipft_delete_entry(\u0026priv-\u003entmp,\n+\t\t\t\t\t     np-\u003eptp_ipft_eid[i]);\n+\t\tif (likely(!err)) {\n+\t\t\tnp-\u003eptp_ipft_eid[i] = NTMP_NULL_ENTRY_ID;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tret = err;\n+\t\tdev_err(priv-\u003edev,\n+\t\t\t\"Delete PTP entry 0x%x (type %d) on port %d failed\\n\",\n+\t\t\tnp-\u003eptp_ipft_eid[i], i, np-\u003edp-\u003eindex);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int netc_build_ptp_ipft_keye(struct ipft_keye_data *keye, int port,\n+\t\t\t\t    enum netc_ptp_type type)\n+{\n+\tu16 src_port, frm_attr_flags;\n+\n+\tkeye-\u003eprecedence = cpu_to_le16(NETC_IPFT_PTP_PRECEDENCE);\n+\tsrc_port = FIELD_PREP(IPFT_SRC_PORT, port);\n+\tsrc_port |= IPFT_SRC_PORT_MASK;\n+\tkeye-\u003esrc_port = cpu_to_le16(src_port);\n+\n+\tswitch (type) {\n+\tcase NETC_PTP_L2:\n+\t\tkeye-\u003eethertype = htons(ETH_P_1588);\n+\t\tkeye-\u003eethertype_mask = htons(0xffff);\n+\t\tbreak;\n+\tcase NETC_PTP_L4_IPV4_EVENT:\n+\tcase NETC_PTP_L4_IPV4_GENERAL:\n+\tcase NETC_PTP_L4_IPV6_EVENT:\n+\tcase NETC_PTP_L4_IPV6_GENERAL:\n+\t\tfrm_attr_flags = IPFT_FAF_IP_HDR | FIELD_PREP(IPFT_FAF_L4_CODE,\n+\t\t\t\t IPFT_FAF_UDP_HDR);\n+\t\tif (type == NETC_PTP_L4_IPV6_EVENT ||\n+\t\t    type == NETC_PTP_L4_IPV6_GENERAL)\n+\t\t\tfrm_attr_flags |= IPFT_FAF_IP_VER6;\n+\n+\t\tkeye-\u003efrm_attr_flags = cpu_to_le16(frm_attr_flags);\n+\n+\t\t/* Set IP version bit in flags_mask to match IPv4 or IPv6\n+\t\t * packets\n+\t\t */\n+\t\tfrm_attr_flags |= IPFT_FAF_IP_VER6;\n+\t\tkeye-\u003efrm_attr_flags_mask = cpu_to_le16(frm_attr_flags);\n+\t\tkeye-\u003eip_protocol = IPPROTO_UDP;\n+\t\tkeye-\u003eip_protocol_mask = 0xff;\n+\n+\t\tif (type == NETC_PTP_L4_IPV4_EVENT ||\n+\t\t    type == NETC_PTP_L4_IPV6_EVENT)\n+\t\t\tkeye-\u003el4_dst_port = htons(PTP_EV_PORT);\n+\t\telse\n+\t\t\tkeye-\u003el4_dst_port = htons(PTP_GEN_PORT);\n+\n+\t\tkeye-\u003el4_dst_port_mask = htons(0xffff);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ERANGE;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int netc_port_add_ipft_ptp_entry(struct netc_port *np,\n+\t\t\t\t\tenum netc_ptp_type type)\n+{\n+\tstruct netc_switch *priv = np-\u003eswitch_priv;\n+\tstruct ipft_entry_data *entry;\n+\tstruct ipft_keye_data *keye;\n+\tu32 cfg;\n+\tint err;\n+\n+\tentry = kzalloc_obj(*entry);\n+\tif (!entry)\n+\t\treturn -ENOMEM;\n+\n+\tkeye = \u0026entry-\u003ekeye;\n+\terr = netc_build_ptp_ipft_keye(keye, np-\u003edp-\u003eindex, type);\n+\tif (err)\n+\t\tgoto free_entry;\n+\n+\tcfg = FIELD_PREP(IPFT_FLTFA, IPFT_FLTFA_REDIRECT);\n+\tcfg |= FIELD_PREP(IPFT_HR, NETC_HR_PTP_TRAP);\n+\tcfg |= IPFT_TIMECAPE | IPFT_RRT;\n+\tentry-\u003ecfge.cfg = cpu_to_le32(cfg);\n+\n+\terr = ntmp_ipft_add_entry(\u0026priv-\u003entmp, entry);\n+\tif (err)\n+\t\tgoto free_entry;\n+\n+\tnp-\u003eptp_ipft_eid[type] = entry-\u003eentry_id;\n+\n+free_entry:\n+\tkfree(entry);\n+\n+\treturn err;\n+}\n+\n+static int netc_port_add_l2_ptp_filter(struct netc_port *np)\n+{\n+\treturn netc_port_add_ipft_ptp_entry(np, NETC_PTP_L2);\n+}\n+\n+static int netc_port_add_l4_ptp_filter(struct netc_port *np)\n+{\n+\tint err;\n+\n+\terr = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV4_EVENT);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV4_GENERAL);\n+\tif (err)\n+\t\tgoto del_ptp_filter;\n+\n+\terr = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV6_EVENT);\n+\tif (err)\n+\t\tgoto del_ptp_filter;\n+\n+\terr = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV6_GENERAL);\n+\tif (err)\n+\t\tgoto del_ptp_filter;\n+\n+\treturn 0;\n+\n+del_ptp_filter:\n+\tnetc_port_del_ptp_filter(np);\n+\n+\treturn err;\n+}\n+\n+static int netc_port_add_l2_l4_ptp_filter(struct netc_port *np)\n+{\n+\tint err;\n+\n+\terr = netc_port_add_l2_ptp_filter(np);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = netc_port_add_l4_ptp_filter(np);\n+\tif (err)\n+\t\tgoto del_ptp_filter;\n+\n+\treturn 0;\n+\n+del_ptp_filter:\n+\tnetc_port_del_ptp_filter(np);\n+\n+\treturn err;\n+}\n+\n+static int netc_port_set_ptp_filter(struct netc_port *np, int rx_filter)\n+{\n+\tint err;\n+\n+\terr = netc_port_del_ptp_filter(np);\n+\tif (err)\n+\t\treturn err;\n+\n+\tnp-\u003eptp_rx_filter = HWTSTAMP_FILTER_NONE;\n+\n+\tswitch (rx_filter) {\n+\tcase HWTSTAMP_FILTER_NONE:\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_EVENT:\n+\t\terr = netc_port_add_l2_ptp_filter(np);\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_EVENT:\n+\t\terr = netc_port_add_l4_ptp_filter(np);\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_EVENT:\n+\t\terr = netc_port_add_l2_l4_ptp_filter(np);\n+\t\tbreak;\n+\tdefault:\n+\t\terr = -ERANGE;\n+\t}\n+\n+\tif (err)\n+\t\treturn err;\n+\n+\tnp-\u003eptp_rx_filter = rx_filter;\n+\n+\treturn 0;\n+}\n+\n+int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,\n+\t\t\t   struct kernel_hwtstamp_config *config,\n+\t\t\t   struct netlink_ext_ack *extack)\n+{\n+\tstruct netc_port *np = NETC_PORT(ds, port);\n+\tstruct netc_switch *priv = ds-\u003epriv;\n+\tint rx_filter, err;\n+\n+\tif ((config-\u003etx_type != HWTSTAMP_TX_OFF ||\n+\t     config-\u003erx_filter != HWTSTAMP_FILTER_NONE) \u0026\u0026\n+\t    netc_get_phc_index(priv) \u003c 0)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tswitch (config-\u003etx_type) {\n+\tcase HWTSTAMP_TX_ONESTEP_SYNC:\n+\tcase HWTSTAMP_TX_ON:\n+\tcase HWTSTAMP_TX_OFF:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ERANGE;\n+\t}\n+\n+\tswitch (config-\u003erx_filter) {\n+\tcase HWTSTAMP_FILTER_NONE:\n+\t\trx_filter = HWTSTAMP_FILTER_NONE;\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:\n+\t\trx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:\n+\t\trx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;\n+\t\tbreak;\n+\tcase HWTSTAMP_FILTER_PTP_V2_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:\n+\t\trx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ERANGE;\n+\t}\n+\n+\terr = netc_port_set_ptp_filter(np, rx_filter);\n+\tif (err) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"Failed to set PTP filter\");\n+\t\treturn err;\n+\t}\n+\n+\tWRITE_ONCE(np-\u003eptp_tx_type, config-\u003etx_type);\n+\tif (config-\u003etx_type != HWTSTAMP_TX_ONESTEP_SYNC)\n+\t\tnetc_port_purge_onestep_queue(np);\n+\n+\tconfig-\u003erx_filter = rx_filter;\n+\n+\treturn 0;\n+}\n+\n+int netc_port_hwtstamp_get(struct dsa_switch *ds, int port,\n+\t\t\t   struct kernel_hwtstamp_config *config)\n+{\n+\tstruct netc_port *np = NETC_PORT(ds, port);\n+\n+\tconfig-\u003etx_type = READ_ONCE(np-\u003eptp_tx_type);\n+\tconfig-\u003erx_filter = np-\u003eptp_rx_filter;\n+\n+\treturn 0;\n+}\n+\n+static void netc_port_prepare_onestep_sync(struct netc_port *np,\n+\t\t\t\t\t   struct sk_buff *skb,\n+\t\t\t\t\t   u32 ptp_class, bool *twostep)\n+{\n+\tstruct netc_switch *priv = np-\u003eswitch_priv;\n+\tu16 correction_offset, tstamp_offset;\n+\tstruct ptp_header *ptp_hdr;\n+\tu8 msg_type, twostep_flag;\n+\tbool is_udp = false;\n+\tu32 pkt_type;\n+\tu8 *pkt_hdr;\n+\n+\tif (unlikely(skb_linearize_cow(skb)))\n+\t\tgoto set_ptp_flag_drop;\n+\n+\tptp_hdr = ptp_parse_header(skb, ptp_class);\n+\tif (unlikely(!ptp_hdr))\n+\t\tgoto set_ptp_flag_drop;\n+\n+\tmsg_type = ptp_get_msgtype(ptp_hdr, ptp_class);\n+\ttwostep_flag = ptp_hdr-\u003eflag_field[0] \u0026 0x2;\n+\tif (msg_type != PTP_MSGTYPE_SYNC || twostep_flag != 0) {\n+\t\t*twostep = true;\n+\t\treturn;\n+\t}\n+\n+\tpkt_hdr = skb_mac_header(skb);\n+\tcorrection_offset = (u8 *)\u0026ptp_hdr-\u003ecorrection - pkt_hdr;\n+\ttstamp_offset = (u8 *)ptp_hdr + sizeof(*ptp_hdr) - pkt_hdr;\n+\n+\t/* Ensure that the entire originTimestamp field is present in the\n+\t * linear buffer of the skb and the correction_offset must be within\n+\t * the hardware capability.\n+\t */\n+\tif (unlikely(tstamp_offset + 10 \u003e skb_headlen(skb) ||\n+\t\t     correction_offset \u003e NETC_MAX_STEP_OFFSET))\n+\t\tgoto set_ptp_flag_drop;\n+\n+\tpkt_type = ptp_class \u0026 PTP_CLASS_PMASK;\n+\tif (pkt_type == PTP_CLASS_IPV4 || pkt_type == PTP_CLASS_IPV6)\n+\t\tis_udp = true;\n+\n+\tNETC_SKB_CB(skb)-\u003ecorrection_offset = correction_offset;\n+\tNETC_SKB_CB(skb)-\u003etimestamp_offset = tstamp_offset;\n+\tNETC_SKB_CB(skb)-\u003eis_udp = is_udp;\n+\tNETC_SKB_CB(skb)-\u003eptp_flag = NETC_PTP_FLAG_ONESTEP;\n+\n+\treturn;\n+\n+set_ptp_flag_drop:\n+\t/* Drop instead of falling back to two-step: if it is a Sync,\n+\t * one-step offload will not be executed, the timestamp in the\n+\t * frame is inaccurate, which may affect PTP synchronization.\n+\t */\n+\tNETC_SKB_CB(skb)-\u003eptp_flag = NETC_PTP_FLAG_DROP;\n+\tdev_dbg_ratelimited(priv-\u003edev,\n+\t\t\t    \"Port %d: PTP frame dropped in error\\n\",\n+\t\t\t    np-\u003edp-\u003eindex);\n+}\n+\n+static void netc_port_prepare_twostep(struct netc_port *np,\n+\t\t\t\t      struct sk_buff *nskb)\n+{\n+\tstruct sk_buff *clone = skb_clone_sk(nskb);\n+\tint ts_req_id;\n+\n+\tif (unlikely(!clone))\n+\t\treturn;\n+\n+\tspin_lock_bh(\u0026np-\u003etstamp_lock);\n+\n+\tts_req_id = netc_get_ts_req_id(np);\n+\tif (ts_req_id \u003c 0) {\n+\t\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\t\tkfree_skb(clone);\n+\t\treturn;\n+\t}\n+\n+\tNETC_SKB_CB(nskb)-\u003eptp_flag = NETC_PTP_FLAG_TWOSTEP;\n+\tNETC_SKB_CB(nskb)-\u003ets_req_id = ts_req_id;\n+\tNETC_SKB_CB(clone)-\u003ets_req_id = ts_req_id;\n+\tNETC_SKB_CB(clone)-\u003eptp_tx_time = jiffies_64;\n+\tNETC_SKB_CB(clone)-\u003eptp_flag = NETC_PTP_FLAG_TWOSTEP;\n+\tskb_shinfo(clone)-\u003etx_flags |= SKBTX_IN_PROGRESS;\n+\t__skb_queue_tail(\u0026np-\u003etstamp_queue, clone);\n+\tif (!delayed_work_pending(\u0026np-\u003etstamp_timeout_work))\n+\t\tschedule_delayed_work(\u0026np-\u003etstamp_timeout_work,\n+\t\t\t\t      NETC_TSTAMP_TIMEOUT);\n+\n+\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+}\n+\n+void netc_port_txtstamp_handler(struct dsa_switch *ds, int port,\n+\t\t\t\tu8 ts_req_id, u64 ts)\n+{\n+\tstruct sk_buff *skb, *skb_tmp, *skb_match = NULL;\n+\tstruct netc_port *np = NETC_PORT(ds, port);\n+\tstruct skb_shared_hwtstamps hwtstamps;\n+\n+\tspin_lock_bh(\u0026np-\u003etstamp_lock);\n+\tskb_queue_walk_safe(\u0026np-\u003etstamp_queue, skb, skb_tmp) {\n+\t\tif (NETC_SKB_CB(skb)-\u003ets_req_id != ts_req_id)\n+\t\t\tcontinue;\n+\n+\t\t__skb_unlink(skb, \u0026np-\u003etstamp_queue);\n+\t\tskb_match = skb;\n+\t\tbreak;\n+\t}\n+\tspin_unlock_bh(\u0026np-\u003etstamp_lock);\n+\n+\tif (!skb_match) {\n+\t\tdev_dbg_ratelimited(np-\u003eswitch_priv-\u003edev,\n+\t\t\t\t    \"Port %d ts_req_id %u which seems lost\\n\",\n+\t\t\t\t    port, ts_req_id);\n+\n+\t\treturn;\n+\t}\n+\n+\tif (NETC_SKB_CB(skb_match)-\u003eptp_flag == NETC_PTP_FLAG_ONESTEP) {\n+\t\tspin_lock_bh(\u0026np-\u003eonestep_lock);\n+\t\tif (likely(np-\u003eonestep_state == NETC_ONESTEP_IN_FLIGHT \u0026\u0026\n+\t\t\t   np-\u003eonestep_ts_req_id == ts_req_id) ||\n+\t\t    np-\u003eonestep_state == NETC_ONESTEP_IDLE) {\n+\t\t\tnp-\u003eonestep_state = NETC_ONESTEP_SCHEDULED;\n+\t\t\tschedule_work(\u0026np-\u003eonestep_work);\n+\t\t}\n+\t\tspin_unlock_bh(\u0026np-\u003eonestep_lock);\n+\t\tconsume_skb(skb_match);\n+\n+\t\treturn;\n+\t}\n+\n+\thwtstamps.hwtstamp = ns_to_ktime(ts);\n+\tskb_complete_tx_timestamp(skb_match, \u0026hwtstamps);\n+}\n+\n+bool netc_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,\n+\t\t\tunsigned int type)\n+{\n+\tstruct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb);\n+\n+\tif (!NETC_SKB_CB(skb)-\u003erx_tstamp_valid)\n+\t\treturn false;\n+\n+\thwtstamps-\u003ehwtstamp = ns_to_ktime(NETC_SKB_CB(skb)-\u003etstamp);\n+\n+\treturn false;\n+}\n+\n+void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)\n+{\n+\tstruct netc_port *np = NETC_PORT(ds, port);\n+\tbool twostep = false;\n+\tu32 ptp_class;\n+\tint tx_type;\n+\n+\tNETC_SKB_CB(skb)-\u003eptp_flag = 0;\n+\tptp_class = ptp_classify_raw(skb);\n+\tif (ptp_class == PTP_CLASS_NONE)\n+\t\treturn;\n+\n+\t/* The rx_filters in netc_get_ts_info() has already declared that\n+\t * it only supports PTP v2, so TX only supports v2 as well.\n+\t */\n+\tif (unlikely(ptp_class \u0026 PTP_CLASS_V1))\n+\t\treturn;\n+\n+\ttx_type = READ_ONCE(np-\u003eptp_tx_type);\n+\tif (tx_type == HWTSTAMP_TX_ONESTEP_SYNC)\n+\t\tnetc_port_prepare_onestep_sync(np, skb, ptp_class, \u0026twostep);\n+\n+\tif (tx_type == HWTSTAMP_TX_ON || twostep)\n+\t\tnetc_port_prepare_twostep(np, skb);\n+}\ndiff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h\nindex 305f2a92e2f9a..624cf323c9a62 100644\n--- a/drivers/net/dsa/netc/netc_switch.h\n+++ b/drivers/net/dsa/netc/netc_switch.h\n@@ -53,10 +53,16 @@\n #define NETC_FDBT_AGEING_DELAY\t\t(3 * HZ)\n #define NETC_FDBT_AGEING_THRESH\t\t100\n \n+/* PTP frames have a higher priority, so a higher priority is defined to\n+ * prioritize matching (The higher the value, the higher the priority).\n+ */\n+#define NETC_IPFT_PTP_PRECEDENCE\t0xf000\n+\n struct netc_switch;\n \n struct netc_switch_info {\n \tu32 num_ports;\n+\tu32 tmr_devfn;\n \tvoid (*phylink_get_caps)(int port, struct phylink_config *config);\n };\n \n@@ -66,9 +72,26 @@ struct netc_port_caps {\n \tu32 pseudo_link:1;\n };\n \n+enum netc_ptp_type {\n+\tNETC_PTP_L2,\n+\tNETC_PTP_L4_IPV4_EVENT,\n+\tNETC_PTP_L4_IPV4_GENERAL,\n+\tNETC_PTP_L4_IPV6_EVENT,\n+\tNETC_PTP_L4_IPV6_GENERAL,\n+\tNETC_PTP_MAX,\n+};\n+\n enum netc_host_reason {\n \t/* Software defined host reasons */\n \tNETC_HR_HOST_FLOOD = 8,\n+\tNETC_HR_PTP_TRAP   = 9,\n+};\n+\n+enum netc_onestep_state {\n+\tNETC_ONESTEP_IDLE\t= 0,\n+\tNETC_ONESTEP_SCHEDULED,\n+\tNETC_ONESTEP_IN_FLIGHT,\n+\tNETC_ONESTEP_PORT_INACTIVE,\n };\n \n struct netc_port {\n@@ -84,7 +107,27 @@ struct netc_port {\n \tu16 uc:1;\n \tu16 mc:1;\n \tu16 pvid;\n-\tstruct ipft_entry_data *host_flood;\n+\tu32 ipft_hf_eid; /* Must be initialized to NTMP_NULL_ENTRY_ID */\n+\n+\t/* Serialize access to onestep_queue, onestep_state and\n+\t * onestep_tx_time\n+\t */\n+\tspinlock_t onestep_lock;\n+\tu8 onestep_state;\n+\tu8 onestep_ts_req_id;\n+\tu64 onestep_tx_time;\n+\t/* skb queue for one-step Sync frames */\n+\tstruct sk_buff_head onestep_queue;\n+\tstruct work_struct onestep_work;\n+\n+\t/* Serialize access to tstamp_queue */\n+\tspinlock_t tstamp_lock;\n+\t/* skb queue for TX timestamp frames */\n+\tstruct sk_buff_head tstamp_queue;\n+\tstruct delayed_work tstamp_timeout_work;\n+\tint ptp_tx_type;\n+\tint ptp_rx_filter;\n+\tu32 ptp_ipft_eid[NETC_PTP_MAX];\n };\n \n struct netc_switch_regs {\n@@ -139,6 +182,7 @@ struct netc_switch {\n \tu32 num_bp;\n \n \tstruct bpt_cfge_data *bpt_list;\n+\tstruct pci_dev *tmr_dev; /* The PTP Timer PCI device */\n };\n \n #define NETC_PRIV(ds)\t\t\t((struct netc_switch *)((ds)-\u003epriv))\n@@ -187,6 +231,7 @@ static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)\n }\n \n int netc_switch_platform_probe(struct netc_switch *priv);\n+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val);\n \n /* ethtool APIs */\n void netc_port_get_pause_stats(struct dsa_switch *ds, int port,\n@@ -203,4 +248,24 @@ void netc_port_get_strings(struct dsa_switch *ds, int port,\n \t\t\t   u32 sset, u8 *data);\n void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);\n \n+/* PTP APIs */\n+int netc_port_ptp_init(struct netc_port *np);\n+int netc_get_ts_info(struct dsa_switch *ds, int port,\n+\t\t     struct kernel_ethtool_ts_info *info);\n+void netc_port_purge_tstamp_queue(struct netc_port *np);\n+int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,\n+\t\t\t   struct kernel_hwtstamp_config *config,\n+\t\t\t   struct netlink_ext_ack *extack);\n+int netc_port_hwtstamp_get(struct dsa_switch *ds, int port,\n+\t\t\t   struct kernel_hwtstamp_config *config);\n+void netc_port_txtstamp_handler(struct dsa_switch *ds, int port,\n+\t\t\t\tu8 ts_req_id, u64 ts);\n+bool netc_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,\n+\t\t\tunsigned int type);\n+void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);\n+void netc_port_disable_onestep(struct netc_port *np);\n+void netc_port_enable_onestep(struct netc_port *np);\n+void netc_port_onestep_sync_enqueue(struct dsa_switch *ds, int port,\n+\t\t\t\t    struct sk_buff *skb);\n+\n #endif\ndiff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h\nindex 1404ae41c7bcb..37d1dd7ec2c7f 100644\n--- a/drivers/net/dsa/netc/netc_switch_hw.h\n+++ b/drivers/net/dsa/netc/netc_switch_hw.h\n@@ -203,6 +203,11 @@ enum netc_stg_stage {\n #define   SSP_10M\t\t\t1\n #define   SSP_1G\t\t\t2\n \n+#define NETC_PM_SINGLE_STEP(a)\t\t(0x10c0 + (a) * 0x400)\n+#define  PM_SINGLE_STEP_CH\t\tBIT(6)\n+#define  PM_SINGLE_STEP_OFFSET\t\tGENMASK(15, 7)\n+#define  PM_SINGLE_STEP_EN\t\tBIT(31)\n+\n /* Port MAC 0/1 Receive Ethernet Octets Counter */\n #define NETC_PM_REOCT(a)\t\t(0x1100 + (a) * 0x400)\n \ndiff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c\nindex 19ca99d80e95b..1211aa9a37d3d 100644\n--- a/drivers/ptp/ptp_netc.c\n+++ b/drivers/ptp/ptp_netc.c\n@@ -1,7 +1,7 @@\n // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)\n /*\n  * NXP NETC V4 Timer driver\n- * Copyright 2025 NXP\n+ * Copyright 2025-2026 NXP\n  */\n \n #include \u003clinux/bitfield.h\u003e\n@@ -123,10 +123,25 @@ struct netc_timer {\n \tu8 fs_alarm_num;\n \tu8 fs_alarm_bitmap;\n \tstruct netc_pp pp[NETC_TMR_FIPER_NUM]; /* periodic pulse */\n+\tstruct list_head node;\n };\n \n+static LIST_HEAD(netc_timer_list);\n+static DEFINE_SPINLOCK(netc_timer_list_lock);\n+\n #define netc_timer_rd(p, o)\t\tnetc_read((p)-\u003ebase + (o))\n #define netc_timer_wr(p, o, v)\t\tnetc_write((p)-\u003ebase + (o), v)\n+\n+/* The 64-bit timer registers consist of a low (L) and high (H) register pair.\n+ * Hardware requires a strict access order: for writes, TMR_xxx_L must be\n+ * written first, which latches the value into a shadow register; the write\n+ * to TMR_xxx_H then atomically transfers both shadow registers into the live\n+ * counter. For reads, TMR_xxx_L must be read first to capture a coherent\n+ * snapshot. iowrite64_lo_hi() and ioread64_lo_hi() enforce this L-before-H\n+ * ordering.\n+ */\n+#define netc_timer_rd64(p, o)\t\tioread64_lo_hi((p)-\u003ebase + (o))\n+#define netc_timer_wr64(p, o, v)\tiowrite64_lo_hi(v, (p)-\u003ebase + (o))\n #define ptp_to_netc_timer(ptp)\t\tcontainer_of((ptp), struct netc_timer, caps)\n \n static const char *const timer_clk_src[] = {\n@@ -136,66 +151,28 @@ static const char *const timer_clk_src[] = {\n \n static void netc_timer_cnt_write(struct netc_timer *priv, u64 ns)\n {\n-\tu32 tmr_cnt_h = upper_32_bits(ns);\n-\tu32 tmr_cnt_l = lower_32_bits(ns);\n-\n-\t/* Writes to the TMR_CNT_L register copies the written value\n-\t * into the shadow TMR_CNT_L register. Writes to the TMR_CNT_H\n-\t * register copies the values written into the shadow TMR_CNT_H\n-\t * register. Contents of the shadow registers are copied into\n-\t * the TMR_CNT_L and TMR_CNT_H registers following a write into\n-\t * the TMR_CNT_H register. So the user must writes to TMR_CNT_L\n-\t * register first. Other H/L registers should have the same\n-\t * behavior.\n-\t */\n-\tnetc_timer_wr(priv, NETC_TMR_CNT_L, tmr_cnt_l);\n-\tnetc_timer_wr(priv, NETC_TMR_CNT_H, tmr_cnt_h);\n+\tnetc_timer_wr64(priv, NETC_TMR_CNT_L, ns);\n }\n \n static u64 netc_timer_offset_read(struct netc_timer *priv)\n {\n-\tu32 tmr_off_l, tmr_off_h;\n-\tu64 offset;\n-\n-\ttmr_off_l = netc_timer_rd(priv, NETC_TMR_OFF_L);\n-\ttmr_off_h = netc_timer_rd(priv, NETC_TMR_OFF_H);\n-\toffset = (((u64)tmr_off_h) \u003c\u003c 32) | tmr_off_l;\n-\n-\treturn offset;\n+\treturn netc_timer_rd64(priv, NETC_TMR_OFF_L);\n }\n \n static void netc_timer_offset_write(struct netc_timer *priv, u64 offset)\n {\n-\tu32 tmr_off_h = upper_32_bits(offset);\n-\tu32 tmr_off_l = lower_32_bits(offset);\n-\n-\tnetc_timer_wr(priv, NETC_TMR_OFF_L, tmr_off_l);\n-\tnetc_timer_wr(priv, NETC_TMR_OFF_H, tmr_off_h);\n+\tnetc_timer_wr64(priv, NETC_TMR_OFF_L, offset);\n }\n \n static u64 netc_timer_cur_time_read(struct netc_timer *priv)\n {\n-\tu32 time_h, time_l;\n-\tu64 ns;\n-\n-\t/* The user should read NETC_TMR_CUR_TIME_L first to\n-\t * get correct current time.\n-\t */\n-\ttime_l = netc_timer_rd(priv, NETC_TMR_CUR_TIME_L);\n-\ttime_h = netc_timer_rd(priv, NETC_TMR_CUR_TIME_H);\n-\tns = (u64)time_h \u003c\u003c 32 | time_l;\n-\n-\treturn ns;\n+\treturn netc_timer_rd64(priv, NETC_TMR_CUR_TIME_L);\n }\n \n static void netc_timer_alarm_write(struct netc_timer *priv,\n \t\t\t\t   u64 alarm, int index)\n {\n-\tu32 alarm_h = upper_32_bits(alarm);\n-\tu32 alarm_l = lower_32_bits(alarm);\n-\n-\tnetc_timer_wr(priv, NETC_TMR_ALARM_L(index), alarm_l);\n-\tnetc_timer_wr(priv, NETC_TMR_ALARM_H(index), alarm_h);\n+\tnetc_timer_wr64(priv, NETC_TMR_ALARM_L(index), alarm);\n }\n \n static u32 netc_timer_get_integral_period(struct netc_timer *priv)\n@@ -500,22 +477,19 @@ static void netc_timer_handle_etts_event(struct netc_timer *priv, int index,\n \t\t\t\t\t bool update_event)\n {\n \tstruct ptp_clock_event event;\n-\tu32 etts_l = 0, etts_h = 0;\n+\tu64 etts = 0;\n \n-\twhile (netc_timer_rd(priv, NETC_TMR_STAT) \u0026 TMR_STAT_ETS_VLD(index)) {\n-\t\tetts_l = netc_timer_rd(priv, NETC_TMR_ETTS_L(index));\n-\t\tetts_h = netc_timer_rd(priv, NETC_TMR_ETTS_H(index));\n-\t}\n+\twhile (netc_timer_rd(priv, NETC_TMR_STAT) \u0026 TMR_STAT_ETS_VLD(index))\n+\t\tetts = netc_timer_rd64(priv, NETC_TMR_ETTS_L(index));\n \n \t/* Invalid time stamp */\n-\tif (!etts_l \u0026\u0026 !etts_h)\n+\tif (!etts)\n \t\treturn;\n \n \tif (update_event) {\n \t\tevent.type = PTP_CLOCK_EXTTS;\n \t\tevent.index = index;\n-\t\tevent.timestamp = (u64)etts_h \u003c\u003c 32;\n-\t\tevent.timestamp |= etts_l;\n+\t\tevent.timestamp = etts;\n \t\tptp_clock_event(priv-\u003eclock, \u0026event);\n \t}\n }\n@@ -807,7 +781,6 @@ static int netc_timer_pci_probe(struct pci_dev *pdev)\n \tif (!priv)\n \t\treturn -ENOMEM;\n \n-\tpcie_flr(pdev);\n \terr = pci_enable_device_mem(pdev);\n \tif (err)\n \t\treturn dev_err_probe(dev, err, \"Failed to enable device\\n\");\n@@ -1016,6 +989,10 @@ static int netc_timer_probe(struct pci_dev *pdev,\n \n \tenable_irq(priv-\u003eirq);\n \n+\tspin_lock_bh(\u0026netc_timer_list_lock);\n+\tlist_add(\u0026priv-\u003enode, \u0026netc_timer_list);\n+\tspin_unlock_bh(\u0026netc_timer_list_lock);\n+\n \treturn 0;\n \n free_msix_irq:\n@@ -1030,6 +1007,10 @@ static void netc_timer_remove(struct pci_dev *pdev)\n {\n \tstruct netc_timer *priv = pci_get_drvdata(pdev);\n \n+\tspin_lock_bh(\u0026netc_timer_list_lock);\n+\tlist_del(\u0026priv-\u003enode);\n+\tspin_unlock_bh(\u0026netc_timer_list_lock);\n+\n \tdisable_irq(priv-\u003eirq);\n \tptp_clock_unregister(priv-\u003eclock);\n \tnetc_timer_wr(priv, NETC_TMR_TEMASK, 0);\n@@ -1052,5 +1033,54 @@ static struct pci_driver netc_timer_driver = {\n };\n module_pci_driver(netc_timer_driver);\n \n+/**\n+ * netc_timer_get_current_time - read the current PTP time from the NETC Timer\n+ * @pdev: PCI device of the NETC Timer\n+ * @ns: Output, the current PTP clock time in nanoseconds\n+ *\n+ * Read TMR_CUR_TIME from the NETC Timer bound to @pdev. The lookup and read\n+ * run under netc_timer_list_lock, so the Timer cannot be unbound and its priv\n+ * freed during the read.\n+ *\n+ * Context: Process or softirq context. Must not be called from hardirq.\n+ *\n+ * Return: 0 on success, -ENODEV if the Timer is not present (not yet probed\n+ *         or already removed).\n+ */\n+int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns)\n+{\n+\tstruct netc_timer *priv = NULL;\n+\tstruct netc_timer *tmp;\n+\tunsigned long flags;\n+\tint err = 0;\n+\n+\t/* Serialize against driver unbind, so holding it here ensures that\n+\t * priv remains valid for the entire duration of the register read.\n+\t */\n+\tspin_lock_bh(\u0026netc_timer_list_lock);\n+\n+\tlist_for_each_entry(tmp, \u0026netc_timer_list, node) {\n+\t\tif (tmp-\u003epdev == pdev) {\n+\t\t\tpriv = tmp;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (!priv) {\n+\t\terr = -ENODEV;\n+\t\tgoto netc_timer_list_unlock;\n+\t}\n+\n+\tspin_lock_irqsave(\u0026priv-\u003elock, flags);\n+\t*ns = netc_timer_cur_time_read(priv);\n+\tspin_unlock_irqrestore(\u0026priv-\u003elock, flags);\n+\n+netc_timer_list_unlock:\n+\tspin_unlock_bh(\u0026netc_timer_list_lock);\n+\n+\treturn err;\n+}\n+EXPORT_SYMBOL_GPL(netc_timer_get_current_time);\n+\n MODULE_DESCRIPTION(\"NXP NETC Timer PTP Driver\");\n MODULE_LICENSE(\"Dual BSD/GPL\");\ndiff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h\nindex fe964722e5b03..baf0f93bd9946 100644\n--- a/include/linux/dsa/tag_netc.h\n+++ b/include/linux/dsa/tag_netc.h\n@@ -10,5 +10,43 @@\n #include \u003cnet/dsa.h\u003e\n \n #define NETC_TAG_MAX_LEN\t\t\t14\n+#define NETC_PTP_FLAG_ONESTEP\t\t\tBIT(0)\n+#define NETC_PTP_FLAG_TWOSTEP\t\t\tBIT(1)\n+#define NETC_PTP_FLAG_DROP\t\t\tBIT(2)\n+\n+struct netc_skb_cb {\n+\tu64 ptp_tx_time;\n+\tu64 tstamp;\n+\tbool rx_tstamp_valid;\n+\tu8 ptp_flag;\n+\tu8 ts_req_id;\n+\tbool is_udp;\n+\tu16 correction_offset;\n+\tu16 timestamp_offset;\n+};\n+\n+#define NETC_SKB_CB(skb)\t((struct netc_skb_cb *)((skb)-\u003ecb))\n+\n+/**\n+ * struct netc_tagger_data - NETC tagger/switch-driver shared operations\n+ * @txtstamp_handler: Called by the tagger when a two-step transmit timestamp\n+ *\tresponse is received, to deliver the timestamp to the switch driver.\n+ * @onestep_sync_enqueue: Called from the tagger xmit path for a one-step Sync\n+ *\tframe. The switch driver takes ownership of the skb and queues it for\n+ *\tdeferred transmission from process context, where the shared\n+ *\tPM_SINGLE_STEP register can be programmed and the PTP timer read. The\n+ *\ttagger must not touch the skb after this call and returns NULL to\n+ *\tdsa_user_xmit().\n+ * @onestep_sync_xmit: Called by the switch driver to transmit a deferred\n+ *\tone-step Sync frame directly to the conduit, bypassing dsa_user_xmit().\n+ */\n+struct netc_tagger_data {\n+\tvoid (*txtstamp_handler)(struct dsa_switch *ds, int port,\n+\t\t\t\t u8 ts_req_id, u64 ts);\n+\tvoid (*onestep_sync_enqueue)(struct dsa_switch *ds, int port,\n+\t\t\t\t     struct sk_buff *skb);\n+\tnetdev_tx_t (*onestep_sync_xmit)(struct sk_buff *skb,\n+\t\t\t\t\t struct net_device *ndev);\n+};\n \n #endif\ndiff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h\nindex 5b8ff528d3699..d4a26c17f99a4 100644\n--- a/include/linux/fsl/netc_global.h\n+++ b/include/linux/fsl/netc_global.h\n@@ -6,6 +6,7 @@\n \n #include \u003clinux/io.h\u003e\n #include \u003clinux/io-64-nonatomic-lo-hi.h\u003e\n+#include \u003clinux/pci.h\u003e\n \n static inline u32 netc_read(void __iomem *reg)\n {\n@@ -22,4 +23,13 @@ static inline u64 netc_read64(void __iomem *reg)\n \treturn ioread64(reg);\n }\n \n+#if IS_REACHABLE(CONFIG_PTP_NETC_V4_TIMER)\n+int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns);\n+#else\n+static inline int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns)\n+{\n+\treturn -ENODEV;\n+}\n+#endif\n+\n #endif\ndiff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c\nindex df72a61796ad5..c1c0ffee75c13 100644\n--- a/net/dsa/tag_netc.c\n+++ b/net/dsa/tag_netc.c\n@@ -16,6 +16,10 @@\n #define NETC_TAG_TO_PORT\t\t1\n /* SubType0: No request to perform timestamping */\n #define NETC_TAG_TP_SUBTYPE0\t\t0\n+/* SubType2: Request to perform two-step timestamping */\n+#define NETC_TAG_TP_SUBTYPE2\t\t2\n+/* SubType3: Request to perform both one-step and two-step timestamping */\n+#define NETC_TAG_TP_SUBTYPE3\t\t3\n \n /* To_Host NXP switch tag */\n #define NETC_TAG_TO_HOST\t\t2\n@@ -29,6 +33,8 @@\n /* NETC switch tag lengths */\n #define NETC_TAG_FORWARD_LEN\t\t6\n #define NETC_TAG_TP_SUBTYPE0_LEN\t6\n+#define NETC_TAG_TP_SUBTYPE2_LEN\t6\n+#define NETC_TAG_TP_SUBTYPE3_LEN\t10\n #define NETC_TAG_TH_SUBTYPE0_LEN\t6\n #define NETC_TAG_TH_SUBTYPE1_LEN\t14\n #define NETC_TAG_TH_SUBTYPE2_LEN\t14\n@@ -40,6 +46,8 @@\n #define NETC_TAG_IPV\t\t\tGENMASK(4, 2)\n #define NETC_TAG_SWITCH\t\t\tGENMASK(2, 0)\n #define NETC_TAG_PORT\t\t\tGENMASK(7, 3)\n+#define NETC_TAG_TS_REQ_ID\t\tGENMASK(3, 0)\n+#define NETC_TAG_TIMESTAMP\t\tGENMASK(29, 0)\n \n struct netc_tag_cmn {\n \t__be16 tpid;\n@@ -48,6 +56,29 @@ struct netc_tag_cmn {\n \tu8 switch_port;\n } __packed;\n \n+struct netc_tag_tp_subtype2 {\n+\tstruct netc_tag_cmn cmn;\n+\tu8 ts_req_id;\n+} __packed;\n+\n+struct netc_tag_tp_subtype3 {\n+\tstruct netc_tag_cmn cmn;\n+\tu8 ts_req_id;\n+\t__be32 timestamp;\n+} __packed;\n+\n+struct netc_tag_th_subtype1 {\n+\tstruct netc_tag_cmn cmn;\n+\tu8 host_reason;\n+\t__be64 timestamp;\n+} __packed;\n+\n+struct netc_tag_th_subtype2 {\n+\tstruct netc_tag_cmn cmn;\n+\tu8 hr_tsreq_id;\n+\t__be64 timestamp;\n+} __packed;\n+\n static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type,\n \t\t\t\t u8 subtype, u8 sw_id, u8 port, u8 ipv)\n {\n@@ -97,15 +128,109 @@ static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,\n \t\t\t\tNETC_TAG_TP_SUBTYPE0_LEN);\n }\n \n-/* Currently only support To_Port tag, subtype 0 */\n+static void netc_fill_tp_tag_subtype2(struct sk_buff *skb,\n+\t\t\t\t      struct net_device *ndev)\n+{\n+\tu8 ts_req_id = NETC_SKB_CB(skb)-\u003ets_req_id;\n+\tstruct netc_tag_tp_subtype2 *tag;\n+\n+\ttag = netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE2,\n+\t\t\t\t      NETC_TAG_TP_SUBTYPE2_LEN);\n+\ttag-\u003ets_req_id = FIELD_PREP(NETC_TAG_TS_REQ_ID, ts_req_id);\n+}\n+\n+static void netc_fill_tp_tag_subtype3(struct sk_buff *skb,\n+\t\t\t\t      struct net_device *ndev)\n+{\n+\tu32 ts = FIELD_PREP(NETC_TAG_TIMESTAMP, NETC_SKB_CB(skb)-\u003etstamp);\n+\tu8 ts_req_id = NETC_SKB_CB(skb)-\u003ets_req_id;\n+\tstruct netc_tag_tp_subtype3 *tag;\n+\n+\ttag = netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE3,\n+\t\t\t\t      NETC_TAG_TP_SUBTYPE3_LEN);\n+\ttag-\u003ets_req_id = FIELD_PREP(NETC_TAG_TS_REQ_ID, ts_req_id);\n+\ttag-\u003etimestamp = htonl(ts);\n+}\n+\n+static void netc_onestep_sync_enqueue(struct sk_buff *skb,\n+\t\t\t\t      struct net_device *ndev)\n+{\n+\tstruct dsa_port *dp = dsa_user_to_port(ndev);\n+\tstruct netc_tagger_data *tagger_data;\n+\n+\ttagger_data = dp-\u003eds-\u003etagger_data;\n+\tif (unlikely(!tagger_data-\u003eonestep_sync_enqueue)) {\n+\t\tkfree_skb(skb);\n+\t\treturn;\n+\t}\n+\n+\t/* Hand the one-step Sync to the switch driver, which takes ownership\n+\t * and queues it for deferred transmission from its work. The tagger\n+\t * must not touch the skb after this point.\n+\t */\n+\ttagger_data-\u003eonestep_sync_enqueue(dp-\u003eds, dp-\u003eindex, skb);\n+}\n+\n+static netdev_tx_t netc_onestep_sync_xmit(struct sk_buff *skb,\n+\t\t\t\t\t  struct net_device *dev)\n+{\n+\tnetc_fill_tp_tag_subtype3(skb, dev);\n+\n+\treturn dsa_enqueue_skb(skb, dev);\n+}\n+\n static struct sk_buff *netc_xmit(struct sk_buff *skb,\n \t\t\t\t struct net_device *ndev)\n {\n-\tnetc_fill_tp_tag_subtype0(skb, ndev);\n+\tu8 ptp_flag = NETC_SKB_CB(skb)-\u003eptp_flag;\n+\n+\t/* Fast path: the overwhelming majority of frames are not PTP frames */\n+\tif (likely(!ptp_flag)) {\n+\t\tnetc_fill_tp_tag_subtype0(skb, ndev);\n+\t} else if (ptp_flag == NETC_PTP_FLAG_TWOSTEP) {\n+\t\tnetc_fill_tp_tag_subtype2(skb, ndev);\n+\t} else if (ptp_flag == NETC_PTP_FLAG_ONESTEP) {\n+\t\t/* The switch driver takes ownership of the one-step Sync and\n+\t\t * queues it for deferred TX; the deferred work tags it subtype\n+\t\t * 3 and transmits it directly to the conduit. Return NULL so\n+\t\t * dsa_user_xmit() stops processing this skb.\n+\t\t */\n+\t\tnetc_onestep_sync_enqueue(skb, ndev);\n+\t\tskb = NULL;\n+\t} else {\n+\t\t/* NETC_PTP_FLAG_DROP */\n+\t\tkfree_skb(skb);\n+\t\tskb = NULL;\n+\t}\n \n \treturn skb;\n }\n \n+static void netc_rx_tstamp_process(struct netc_tag_th_subtype1 *tag,\n+\t\t\t\t   struct sk_buff *skb)\n+{\n+\tu64 ts = get_unaligned_be64(\u0026tag-\u003etimestamp);\n+\n+\tNETC_SKB_CB(skb)-\u003erx_tstamp_valid = true;\n+\tNETC_SKB_CB(skb)-\u003etstamp = ts;\n+}\n+\n+static void netc_twostep_tstamp_process(struct netc_tag_th_subtype2 *tag,\n+\t\t\t\t\tstruct sk_buff *skb)\n+{\n+\tu8 ts_req_id = FIELD_GET(NETC_TAG_TS_REQ_ID, tag-\u003ehr_tsreq_id);\n+\tstruct dsa_port *dp = dsa_user_to_port(skb-\u003edev);\n+\tu64 ts = get_unaligned_be64(\u0026tag-\u003etimestamp);\n+\tstruct netc_tagger_data *tagger_data;\n+\tstruct dsa_switch *ds = dp-\u003eds;\n+\n+\ttagger_data = ds-\u003etagger_data;\n+\tif (unlikely(!tagger_data-\u003etxtstamp_handler))\n+\t\treturn;\n+\n+\ttagger_data-\u003etxtstamp_handler(ds, dp-\u003eindex, ts_req_id, ts);\n+}\n+\n static int netc_get_rx_tag_len(int type, int subtype)\n {\n \t/* Only NETC_TAG_TO_HOST and NETC_TAG_FORWARD are expected in RX,\n@@ -129,11 +254,22 @@ static struct sk_buff *netc_rcv(struct sk_buff *skb,\n \tstruct netc_tag_cmn *tag_cmn;\n \tint tag_len, sw_id, port;\n \tint type, subtype;\n+\tvoid *tag;\n \n-\tif (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN)))\n+\t/* eth_type_trans() pulled ETH_HLEN bytes, so skb-\u003edata sits 2 bytes\n+\t * past the start of the switch tag (past the TPID) and skb-\u003elen is\n+\t * ETH_HLEN bytes shorter than the original frame length. The longest\n+\t * switch tag is NETC_TAG_MAX_LEN (14) bytes, but since 2 of those\n+\t * bytes are already behind skb-\u003edata, only NETC_TAG_MAX_LEN - 2 bytes\n+\t * need to be in the linear buffer. For the To_Host subtype 2 response\n+\t * frame, whose total length is only 26 bytes with no payload after the\n+\t * tag, this check is the only guard against a too-short frame.\n+\t */\n+\tif (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN - 2)))\n \t\tgoto err_free_skb;\n \n-\ttag_cmn = dsa_etype_header_pos_rx(skb);\n+\ttag = dsa_etype_header_pos_rx(skb);\n+\ttag_cmn = tag;\n \tif (ntohs(tag_cmn-\u003etpid) != ETH_P_NXP_NETC) {\n \t\tdev_warn_ratelimited(\u0026ndev-\u003edev, \"Unknown TPID 0x%04x\\n\",\n \t\t\t\t     ntohs(tag_cmn-\u003etpid));\n@@ -156,17 +292,50 @@ static struct sk_buff *netc_rcv(struct sk_buff *skb,\n \tif (!skb-\u003edev)\n \t\tgoto err_free_skb;\n \n+\t/* skb-\u003ecb may be used to store hardware RX timestamp, so clear\n+\t * rx_tstamp_valid before processing to avoid data pollution from\n+\t * the previous layer.\n+\t */\n+\tNETC_SKB_CB(skb)-\u003erx_tstamp_valid = false;\n+\n \ttype = FIELD_GET(NETC_TAG_TYPE, tag_cmn-\u003etype);\n \tsubtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn-\u003etype);\n \tif (type == NETC_TAG_FORWARD) {\n \t\tdsa_default_offload_fwd_mark(skb);\n \t} else if (type == NETC_TAG_TO_HOST) {\n-\t\t/* Currently only subtype0 supported */\n-\t\tif (subtype != NETC_TAG_TH_SUBTYPE0)\n+\t\tswitch (subtype) {\n+\t\tcase NETC_TAG_TH_SUBTYPE0:\n+\t\t\tbreak;\n+\t\tcase NETC_TAG_TH_SUBTYPE1:\n+\t\t\t/* To_Host Subtype 1 tag is 14 bytes, ensure it and the\n+\t\t\t * EtherType behind it are fully present in the linear\n+\t\t\t * area before netc_rcv() calls dsa_strip_etype_header()\n+\t\t\t * to strip the tag.\n+\t\t\t */\n+\t\t\tif (unlikely(!pskb_may_pull(skb,\n+\t\t\t\t\t\t    NETC_TAG_TH_SUBTYPE1_LEN)))\n+\t\t\t\tgoto err_free_skb;\n+\n+\t\t\ttag = dsa_etype_header_pos_rx(skb);\n+\t\t\tnetc_rx_tstamp_process(tag, skb);\n+\t\t\tbreak;\n+\t\tcase NETC_TAG_TH_SUBTYPE2:\n+\t\t\t/* This skb is a hardware-generated response to a\n+\t\t\t * two-step transmit timestamp request. The tag\n+\t\t\t * driver must free the skb after processing.\n+\t\t\t */\n+\t\t\tnetc_twostep_tstamp_process(tag, skb);\n+\t\t\tconsume_skb(skb);\n+\t\t\treturn NULL;\n+\t\tdefault:\n+\t\t\tdev_warn_ratelimited(\u0026ndev-\u003edev,\n+\t\t\t\t\t     \"Unsupported To_Host subtype: %d\\n\",\n+\t\t\t\t\t     subtype);\n \t\t\tgoto err_free_skb;\n+\t\t}\n \t} else {\n \t\tdev_warn_ratelimited(\u0026ndev-\u003edev,\n-\t\t\t\t     \"Unexpected  tag type %d\\n\", type);\n+\t\t\t\t     \"Unexpected tag type %d\\n\", type);\n \t\tgoto err_free_skb;\n \t}\n \n@@ -190,16 +359,49 @@ static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto,\n \tint type = FIELD_GET(NETC_TAG_TYPE, tag_cmn-\u003etype);\n \tint tag_len = netc_get_rx_tag_len(type, subtype);\n \n-\t/* The RX minimum frame length of the NETC switch port is 64 bytes,\n-\t * and the frame is received by the ENETC driver. From the hardware\n-\t * perspective, the receive buffer of RX BD is at least 128 bytes,\n-\t * so the switch tag header is guaranteed to be in the linear region\n-\t * of the skb.\n+\t/* The CPU port of the switch is connected to the ENETC, so the frame\n+\t * is received by the ENETC driver. From the hardware perspective, the\n+\t * receive buffer of RX BD is at least 128 bytes, so the switch tag\n+\t * header is guaranteed to be in the linear region of the skb.\n+\t *\n+\t * When the subtype of the frame is NETC_TAG_TH_SUBTYPE2, it indicates\n+\t * the frame is a hardware generated timestamp response, which is only\n+\t * 26 bytes (DMAC + SMAC + tag), so the frame has no payload after the\n+\t * tag. Therefore, there is no need to parse the protocol and offset.\n+\t * For other types of the frames, they are all received from the switch\n+\t * ports, and the RX minimum frame length of the port is 64 bytes,\n+\t * frames shorter than 64 bytes will be discarded by the hardware and\n+\t * will not be received by the software.\n \t */\n+\tif (type == NETC_TAG_TO_HOST \u0026\u0026 subtype == NETC_TAG_TH_SUBTYPE2)\n+\t\treturn;\n+\n \t*offset = tag_len;\n \t*proto = ((__be16 *)skb-\u003edata)[(tag_len / 2) - 1];\n }\n \n+static int netc_connect(struct dsa_switch *ds)\n+{\n+\tstruct netc_tagger_data *tagger_data;\n+\n+\ttagger_data = kzalloc_obj(*tagger_data);\n+\tif (!tagger_data)\n+\t\treturn -ENOMEM;\n+\n+\ttagger_data-\u003eonestep_sync_xmit = netc_onestep_sync_xmit;\n+\tds-\u003etagger_data = tagger_data;\n+\n+\treturn 0;\n+}\n+\n+static void netc_disconnect(struct dsa_switch *ds)\n+{\n+\tstruct netc_tagger_data *tagger_data = ds-\u003etagger_data;\n+\n+\tkfree(tagger_data);\n+\tds-\u003etagger_data = NULL;\n+}\n+\n static const struct dsa_device_ops netc_netdev_ops = {\n \t.name\t\t\t= NETC_NAME,\n \t.proto\t\t\t= DSA_TAG_PROTO_NETC,\n@@ -207,6 +409,8 @@ static const struct dsa_device_ops netc_netdev_ops = {\n \t.rcv\t\t\t= netc_rcv,\n \t.needed_headroom\t= NETC_TAG_MAX_LEN,\n \t.flow_dissect\t\t= netc_flow_dissect,\n+\t.connect\t\t= netc_connect,\n+\t.disconnect\t\t= netc_disconnect,\n };\n \n MODULE_DESCRIPTION(\"DSA tag driver for NXP NETC switch family\");\n"
}

2/1 2026/09/20 10:36 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series implements PTP (IEEE 1588) support and refactors host flood filtering for the NXP NETC switch family (drivers/net/dsa/netc/, drivers/ptp/ptp_netc.c, and net/dsa/tag_netc.c). The switch and timer drivers require physical or emulated PCIe devices specific to NXP SoCs (Freescale vendor ID with Device Tree node bindings) that are not emulated in standard QEMU/GCE amd64 virtualized environments. Furthermore, the DSA tagging driver (tag_netc) cannot be bound or exercised without the underlying hardware switch. Consequently, the modified code is structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 8c4cc14b4ddfdf7e900d5c519b9f2bbbceada636
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 20 10:36:25 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig
index 793f7691a24f3..7c930579f1c94 100644
--- a/drivers/net/dsa/netc/Kconfig
+++ b/drivers/net/dsa/netc/Kconfig
@@ -4,6 +4,8 @@ config NET_DSA_NETC_SWITCH
 	depends on ARM64 || COMPILE_TEST
 	depends on NET_DSA && PCI
 	depends on NET_VENDOR_FREESCALE
+	depends on PTP_1588_CLOCK_OPTIONAL
+	depends on PTP_NETC_V4_TIMER || PTP_NETC_V4_TIMER=n
 	select NET_DSA_TAG_NETC
 	select FSL_ENETC_MDIO
 	select NXP_NTMP
diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile
index f40b13c702e07..572b833ad80fc 100644
--- a/drivers/net/dsa/netc/Makefile
+++ b/drivers/net/dsa/netc/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o
-nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o
+nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o \
+			netc_ptp.o
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 77077352c1a57..af39a71aece0f 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -65,6 +65,21 @@ netc_get_tag_protocol(struct dsa_switch *ds, int port,
 	return DSA_TAG_PROTO_NETC;
 }
 
+static int netc_connect_tag_protocol(struct dsa_switch *ds,
+				     enum dsa_tag_protocol proto)
+{
+	struct netc_tagger_data *tagger_data;
+
+	if (proto != DSA_TAG_PROTO_NETC)
+		return -EPROTONOSUPPORT;
+
+	tagger_data = ds->tagger_data;
+	tagger_data->txtstamp_handler = netc_port_txtstamp_handler;
+	tagger_data->onestep_sync_enqueue = netc_port_onestep_sync_enqueue;
+
+	return 0;
+}
+
 static void netc_port_rmw(struct netc_port *np, u32 reg,
 			  u32 mask, u32 val)
 {
@@ -80,7 +95,7 @@ static void netc_port_rmw(struct netc_port *np, u32 reg,
 	netc_port_wr(np, reg, new);
 }
 
-static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
 {
 	if (is_netc_pseudo_port(np))
 		return;
@@ -266,6 +281,11 @@ static int netc_init_all_ports(struct netc_switch *priv)
 
 		np->switch_priv = priv;
 		np->iobase = priv->regs.port + PORT_IOBASE(i);
+		/* The ipft_hf_eid is initialized to an invalid entry
+		 * ID because the host flood rule (IPFT entry) has not
+		 * been created.
+		 */
+		np->ipft_hf_eid = NTMP_NULL_ENTRY_ID;
 		netc_port_get_capability(np);
 		priv->ports[i] = np;
 	}
@@ -286,6 +306,15 @@ static int netc_init_all_ports(struct netc_switch *priv)
 				dev_err(dev, "Failed to create MDIO bus\n");
 				return err;
 			}
+
+			/* Only the user port needs to support PTP feature, so
+			 * PTP-related resources, such as tstamp_queue,
+			 * tstamp_lock, etc., are initialized only for the user
+			 * port.
+			 */
+			err = netc_port_ptp_init(np);
+			if (err)
+				return err;
 		}
 	}
 
@@ -549,6 +578,12 @@ static void netc_port_fixed_config(struct netc_port *np)
 	netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,
 		      PCR_L2DOSE | PCR_L3DOSE);
 
+	/* Enable ingress port filter table lookup, if no match is found,
+	 * the frame is allowed and passed to the next frame processing
+	 * function.
+	 */
+	netc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);
+
 	/* Set the quanta value of TX PAUSE frame */
 	netc_mac_port_wr(np, NETC_PM_PAUSE_QUANTA(0), NETC_PAUSE_QUANTA);
 
@@ -869,6 +904,15 @@ static int netc_switch_bpt_default_config(struct netc_switch *priv)
 	return 0;
 }
 
+static struct pci_dev *netc_get_ptp_timer(struct netc_switch *priv)
+{
+	struct pci_bus *bus = priv->pdev->bus;
+	u32 devfn = priv->info->tmr_devfn;
+
+	return pci_get_domain_bus_and_slot(pci_domain_nr(bus),
+					   bus->number, devfn);
+}
+
 static int netc_setup(struct dsa_switch *ds)
 {
 	struct netc_switch *priv = ds->priv;
@@ -881,13 +925,23 @@ static int netc_setup(struct dsa_switch *ds)
 
 	netc_get_switch_capabilities(priv);
 
+	/* The PTP timer sits on the same PCI bus as the switch. PCI creates
+	 * every function's pci_dev during bus enumeration, before any driver
+	 * probes, so we can grab the timer's pci_dev here even if the timer
+	 * driver has not probed yet.
+	 */
+	priv->tmr_dev = netc_get_ptp_timer(priv);
+	if (!priv->tmr_dev)
+		dev_info(priv->dev,
+			 "PTP timer PCI device not found\n");
+
 	err = netc_init_all_ports(priv);
 	if (err)
-		return err;
+		goto put_ptp_timer;
 
 	err = netc_init_ntmp_user(priv);
 	if (err)
-		return err;
+		goto put_ptp_timer;
 
 	INIT_HLIST_HEAD(&priv->fdb_list);
 	mutex_init(&priv->fdbt_lock);
@@ -926,6 +980,8 @@ static int netc_setup(struct dsa_switch *ds)
 	mutex_destroy(&priv->fdbt_lock);
 	mutex_destroy(&priv->vft_lock);
 	netc_free_ntmp_user(priv);
+put_ptp_timer:
+	pci_dev_put(priv->tmr_dev);
 
 	return err;
 }
@@ -938,20 +994,18 @@ static void netc_destroy_all_lists(struct netc_switch *priv)
 	mutex_destroy(&priv->vft_lock);
 }
 
-static void netc_free_host_flood_rules(struct netc_switch *priv)
+static void netc_free_ports_resources(struct netc_switch *priv)
 {
 	struct dsa_port *dp;
 
-	dsa_switch_for_each_user_port(dp, priv->ds) {
+	dsa_switch_for_each_available_port(dp, priv->ds) {
 		struct netc_port *np = priv->ports[dp->index];
 
-		/* No need to clear the hardware IPFT entry. Because PCIe
-		 * FLR will be performed when the switch is re-registered,
-		 * it will reset hardware state. So only need to free the
-		 * memory to avoid memory leak.
-		 */
-		kfree(np->host_flood);
-		np->host_flood = NULL;
+		if (!dsa_port_is_user(dp))
+			continue;
+
+		disable_delayed_work_sync(&np->tstamp_timeout_work);
+		netc_port_purge_tstamp_queue(np);
 	}
 }
 
@@ -961,8 +1015,9 @@ static void netc_teardown(struct dsa_switch *ds)
 
 	disable_delayed_work_sync(&priv->fdbt_ageing_work);
 	netc_destroy_all_lists(priv);
-	netc_free_host_flood_rules(priv);
 	netc_free_ntmp_user(priv);
+	netc_free_ports_resources(priv);
+	pci_dev_put(priv->tmr_dev);
 }
 
 static bool netc_port_is_emdio_consumer(struct device_node *node)
@@ -1517,6 +1572,7 @@ static int netc_port_enable(struct dsa_switch *ds, int port,
 		return err;
 	}
 
+	netc_port_enable_onestep(np);
 	np->enable = true;
 
 	return 0;
@@ -1534,6 +1590,7 @@ static void netc_port_disable(struct dsa_switch *ds, int port)
 	if (!np->enable)
 		return;
 
+	netc_port_disable_onestep(np);
 	clk_disable_unprepare(np->ref_clk);
 	np->enable = false;
 }
@@ -1719,14 +1776,8 @@ static int netc_port_add_host_flood_rule(struct netc_port *np,
 	u32 cfg;
 	int err;
 
-	if (!uc && !mc) {
-		/* Disable ingress port filter table lookup */
-		netc_port_wr(np, NETC_PIPFCR, 0);
-		np->uc = false;
-		np->mc = false;
-
+	if (!uc && !mc)
 		return 0;
-	}
 
 	host_flood = kzalloc_obj(*host_flood);
 	if (!host_flood)
@@ -1759,48 +1810,43 @@ static int netc_port_add_host_flood_rule(struct netc_port *np,
 	cfge->cfg = cpu_to_le32(cfg);
 
 	err = ntmp_ipft_add_entry(&priv->ntmp, host_flood);
-	if (err) {
-		kfree(host_flood);
-		return err;
-	}
+	if (err)
+		goto free_host_flood;
 
 	np->uc = uc;
 	np->mc = mc;
-	np->host_flood = host_flood;
-	/* Enable ingress port filter table lookup */
-	netc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);
+	np->ipft_hf_eid = host_flood->entry_id;
 
-	return 0;
+free_host_flood:
+	kfree(host_flood);
+
+	return err;
 }
 
-static void netc_port_remove_host_flood(struct netc_port *np,
-					struct ipft_entry_data *host_flood)
+static int netc_port_remove_host_flood(struct netc_port *np)
 {
 	struct netc_switch *priv = np->switch_priv;
-	bool disable_host_flood = false;
+	u32 entry_id = np->ipft_hf_eid;
+	int err;
 
-	if (!host_flood)
-		return;
+	if (entry_id == NTMP_NULL_ENTRY_ID)
+		return 0;
 
-	if (np->host_flood == host_flood)
-		disable_host_flood = true;
+	err = ntmp_ipft_delete_entry(&priv->ntmp, entry_id);
+	if (err)
+		return err;
 
-	ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id);
-	kfree(host_flood);
+	np->ipft_hf_eid = NTMP_NULL_ENTRY_ID;
+	np->uc = false;
+	np->mc = false;
 
-	if (disable_host_flood) {
-		np->host_flood = NULL;
-		np->uc = false;
-		np->mc = false;
-		netc_port_wr(np, NETC_PIPFCR, 0);
-	}
+	return 0;
 }
 
 static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
 				     bool uc, bool mc)
 {
 	struct netc_port *np = NETC_PORT(ds, port);
-	struct ipft_entry_data *old_host_flood;
 
 	/* Do not add host flood rule to ingress port filter table when
 	 * the port has joined a bridge. Otherwise, the ingress frames
@@ -1808,7 +1854,12 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
 	 * will be redirected directly to the CPU port.
 	 */
 	if (dsa_port_bridge_dev_get(np->dp)) {
-		netc_port_remove_host_flood(np, np->host_flood);
+		if (!netc_port_remove_host_flood(np))
+			return;
+
+		dev_err(ds->dev,
+			"Failed to delete host flood rule on bridge port %d\n",
+			port);
 
 		return;
 	}
@@ -1817,21 +1868,24 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
 		return;
 
 	/* IPFT does not support in-place updates to the KEYE element,
-	 * we need to add a new entry and then delete the old one. So
-	 * save the old entry first.
+	 * we need to delete the old one and then add the new rule. If
+	 * the deletion fails, return immediately.
 	 */
-	old_host_flood = np->host_flood;
-	np->host_flood = NULL;
-
-	if (netc_port_add_host_flood_rule(np, uc, mc)) {
-		np->host_flood = old_host_flood;
-		dev_err(ds->dev, "Failed to add host flood rule on port %d\n",
+	if (netc_port_remove_host_flood(np)) {
+		dev_err(ds->dev,
+			"Failed to delete old host flood rule on port %d\n",
 			port);
+
 		return;
 	}
 
-	/* Remove the old host flood entry */
-	netc_port_remove_host_flood(np, old_host_flood);
+	/* Restoring the previous configuration is pointless because
+	 * .port_set_host_flood() returns void, so the upper layer cannot
+	 * detect the error and the RX flags have changed.
+	 */
+	if (netc_port_add_host_flood_rule(np, uc, mc))
+		dev_err(ds->dev,
+			"Failed to add host flood rule on port %d\n", port);
 }
 
 static int netc_single_vlan_aware_bridge(struct dsa_switch *ds,
@@ -1996,6 +2050,8 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
 	struct netc_port *np = NETC_PORT(ds, port);
 	struct netc_switch *priv = ds->priv;
 	u16 vlan_unaware_pvid;
+	bool uc = np->uc;
+	bool mc = np->mc;
 	int err;
 
 	if (!bridge.num) {
@@ -2007,6 +2063,12 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
 	if (err)
 		return err;
 
+	err = netc_port_remove_host_flood(np);
+	if (err) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to delete host flood rule");
+		return err;
+	}
+
 	netc_port_set_mlo(np, MLO_NOT_OVERRIDE);
 
 	if (br_vlan_enabled(bridge.dev))
@@ -2020,8 +2082,6 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
 	netc_port_set_pvid(np, vlan_unaware_pvid);
 
 out:
-	netc_port_remove_host_flood(np, np->host_flood);
-
 	if (atomic_inc_return(&priv->br_cnt) == 1)
 		schedule_delayed_work(&priv->fdbt_ageing_work,
 				      READ_ONCE(priv->fdbt_ageing_delay));
@@ -2031,6 +2091,11 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
 disable_mlo:
 	netc_port_set_mlo(np, MLO_DISABLE);
 
+	if (netc_port_add_host_flood_rule(np, uc, mc))
+		dev_err(ds->dev,
+			"Failed to restore host flood rule on port %u\n",
+			port);
+
 	return err;
 }
 
@@ -2379,6 +2444,7 @@ static void netc_mac_link_up(struct phylink_config *config,
 	netc_port_set_rx_pause(np, rx_pause);
 	netc_port_mac_tx_enable(np);
 	netc_port_mac_rx_enable(np);
+	netc_port_enable_onestep(np);
 }
 
 static void netc_mac_link_down(struct phylink_config *config,
@@ -2389,6 +2455,7 @@ static void netc_mac_link_down(struct phylink_config *config,
 	struct netc_port *np;
 
 	np = NETC_PORT(dp->ds, dp->index);
+	netc_port_disable_onestep(np);
 	netc_port_mac_rx_graceful_stop(np);
 	netc_port_mac_tx_graceful_stop(np);
 	netc_port_remove_dynamic_entries(np);
@@ -2402,6 +2469,7 @@ static const struct phylink_mac_ops netc_phylink_mac_ops = {
 
 static const struct dsa_switch_ops netc_switch_ops = {
 	.get_tag_protocol		= netc_get_tag_protocol,
+	.connect_tag_protocol		= netc_connect_tag_protocol,
 	.setup				= netc_setup,
 	.teardown			= netc_teardown,
 	.phylink_get_caps		= netc_phylink_get_caps,
@@ -2430,6 +2498,11 @@ static const struct dsa_switch_ops netc_switch_ops = {
 	.get_sset_count			= netc_port_get_sset_count,
 	.get_strings			= netc_port_get_strings,
 	.get_ethtool_stats		= netc_port_get_ethtool_stats,
+	.get_ts_info			= netc_get_ts_info,
+	.port_hwtstamp_set		= netc_port_hwtstamp_set,
+	.port_hwtstamp_get		= netc_port_hwtstamp_get,
+	.port_rxtstamp			= netc_port_rxtstamp,
+	.port_txtstamp			= netc_port_txtstamp,
 };
 
 static int netc_switch_probe(struct pci_dev *pdev,
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
index 34aeb6fceb3cb..4fd0ce6770c33 100644
--- a/drivers/net/dsa/netc/netc_platform.c
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -50,6 +50,7 @@ static void imx94_switch_phylink_get_caps(int port,
 
 static const struct netc_switch_info imx94_info = {
 	.num_ports = 4,
+	.tmr_devfn = PCI_DEVFN(0, 1),
 	.phylink_get_caps = imx94_switch_phylink_get_caps,
 };
 
diff --git a/drivers/net/dsa/netc/netc_ptp.c b/drivers/net/dsa/netc/netc_ptp.c
new file mode 100644
index 0000000000000..1bc0f09562062
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_ptp.c
@@ -0,0 +1,849 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "netc_switch.h"
+
+#define NETC_NUM_TS_REQ_ID		16
+#define NETC_TSTAMP_TIMEOUT		(5 * HZ)
+#define NETC_MAX_STEP_OFFSET		0x1ff
+#define NETC_ONESTEP_QTH		512
+/* The 30-bit timestamp of the To_Port subtype 3 tag lets the hardware
+ * account for a single wrap, so the correction field of a one-step Sync
+ * frame is only correct if it is sent out within 2^30 ns after the
+ * software timestamp is read. Past this window the frame is beyond
+ * repair, and PM_SINGLE_STEP becomes safe to reprogram.
+ */
+#define NETC_ONESTEP_VALID_WINDOW	0x40000000 /* ns */
+
+static void netc_port_tstamp_timeout_work(struct work_struct *work)
+{
+	struct netc_port *np = container_of(work, struct netc_port,
+					    tstamp_timeout_work.work);
+	struct sk_buff_head free_list;
+	struct sk_buff *skb, *skb_tmp;
+
+	__skb_queue_head_init(&free_list);
+
+	spin_lock_bh(&np->tstamp_lock);
+	skb_queue_walk_safe(&np->tstamp_queue, skb, skb_tmp) {
+		if (time_before64(jiffies_64, NETC_SKB_CB(skb)->ptp_tx_time +
+				  NETC_TSTAMP_TIMEOUT))
+			continue;
+
+		dev_dbg_ratelimited(np->switch_priv->dev,
+				    "Port %d ts_req_id %u which seems lost\n",
+				    np->dp->index, NETC_SKB_CB(skb)->ts_req_id);
+
+		__skb_unlink(skb, &np->tstamp_queue);
+		__skb_queue_tail(&free_list, skb);
+	}
+
+	/* Reschedule if there are still pending clones that have not
+	 * timed out yet.
+	 */
+	if (!skb_queue_empty(&np->tstamp_queue))
+		schedule_delayed_work(&np->tstamp_timeout_work,
+				      NETC_TSTAMP_TIMEOUT);
+
+	spin_unlock_bh(&np->tstamp_lock);
+	__skb_queue_purge(&free_list);
+}
+
+static int netc_get_ts_req_id(struct netc_port *np)
+{
+	DECLARE_BITMAP(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);
+	struct sk_buff *skb, *skb_tmp;
+	unsigned long ts_req_id;
+
+	bitmap_zero(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);
+
+	skb_queue_walk_safe(&np->tstamp_queue, skb, skb_tmp)
+		__set_bit(NETC_SKB_CB(skb)->ts_req_id, ts_req_id_bitmap);
+
+	ts_req_id = find_first_zero_bit(ts_req_id_bitmap, NETC_NUM_TS_REQ_ID);
+	if (ts_req_id == NETC_NUM_TS_REQ_ID) {
+		dev_dbg_ratelimited(np->switch_priv->dev,
+				    "Port %d has no available ts_req_id\n",
+				    np->dp->index);
+		return -ENOSPC;
+	}
+
+	return ts_req_id;
+}
+
+static int netc_get_phc_time(struct netc_switch *priv, u64 *ns)
+{
+	if (unlikely(!priv->tmr_dev))
+		return -ENODEV;
+
+	return netc_timer_get_current_time(priv->tmr_dev, ns);
+}
+
+static void netc_port_set_onestep_control(struct netc_port *np,
+					  bool csum_update, int offset)
+{
+	u32 val;
+
+	val = PM_SINGLE_STEP_EN | FIELD_PREP(PM_SINGLE_STEP_OFFSET, offset);
+	if (csum_update)
+		val |= PM_SINGLE_STEP_CH;
+	netc_mac_port_wr(np, NETC_PM_SINGLE_STEP(0), val);
+}
+
+static void netc_port_program_onestep(struct netc_port *np,
+				      struct sk_buff *skb)
+{
+	u16 correction_offset = NETC_SKB_CB(skb)->correction_offset;
+	u16 tstamp_offset = NETC_SKB_CB(skb)->timestamp_offset;
+	u64 tstamp = NETC_SKB_CB(skb)->tstamp;
+	u8 *hdr = skb_mac_header(skb);
+	bool csum_update = false;
+	__be32 new_sec_l, new_ns;
+	__be16 new_sec_h;
+	u64 sec;
+	u32 ns;
+
+	/* Update originTimestamp field of Sync packet
+	 * - 48 bits seconds field
+	 * - 32 bits nanoseconds field
+	 */
+	sec = div_u64_rem(tstamp, NSEC_PER_SEC, &ns);
+	new_sec_h = htons((sec >> 32) & 0xffff);
+	new_sec_l = htonl(sec & 0xffffffff);
+	new_ns = htonl(ns);
+
+	if (NETC_SKB_CB(skb)->is_udp) {
+		__be32 old_sec_l, old_ns;
+		struct udphdr *uh;
+		__be16 old_sec_h;
+
+		if (skb->ip_summed == CHECKSUM_PARTIAL) {
+			csum_update = true;
+			goto update_timestamp;
+		}
+
+		if (unlikely(!skb_transport_header_was_set(skb)))
+			uh = (struct udphdr *)(hdr + tstamp_offset -
+					       sizeof(struct ptp_header) -
+					       sizeof(struct udphdr));
+		else
+			uh = udp_hdr(skb);
+
+		/* For IPv4, a UDP checksum of zero on the wire means "no
+		 * checksum". For IPv6, its UDP checksum is mandatory and
+		 * never zero.
+		 */
+		if (!uh->check)
+			goto update_timestamp;
+
+		old_sec_h = __get_unaligned_t(__be16, hdr + tstamp_offset);
+		old_sec_l = __get_unaligned_t(__be32, hdr + tstamp_offset + 2);
+		old_ns = __get_unaligned_t(__be32, hdr + tstamp_offset + 6);
+		inet_proto_csum_replace2(&uh->check, skb, old_sec_h,
+					 new_sec_h, false);
+		inet_proto_csum_replace4(&uh->check, skb, old_sec_l,
+					 new_sec_l, false);
+		inet_proto_csum_replace4(&uh->check, skb, old_ns,
+					 new_ns, false);
+		csum_update = true;
+	}
+
+update_timestamp:
+	__put_unaligned_t(__be16, new_sec_h, hdr + tstamp_offset);
+	__put_unaligned_t(__be32, new_sec_l, hdr + tstamp_offset + 2);
+	__put_unaligned_t(__be32, new_ns, hdr + tstamp_offset + 6);
+
+	netc_port_set_onestep_control(np, csum_update, correction_offset);
+}
+
+void netc_port_disable_onestep(struct netc_port *np)
+{
+	struct sk_buff_head free_list;
+
+	if (!dsa_port_is_user(np->dp))
+		return;
+
+	__skb_queue_head_init(&free_list);
+
+	spin_lock_bh(&np->onestep_lock);
+	skb_queue_splice_init(&np->onestep_queue, &free_list);
+	np->onestep_state = NETC_ONESTEP_PORT_INACTIVE;
+	spin_unlock_bh(&np->onestep_lock);
+
+	cancel_work_sync(&np->onestep_work);
+	__skb_queue_purge(&free_list);
+}
+
+void netc_port_enable_onestep(struct netc_port *np)
+{
+	if (!dsa_port_is_user(np->dp))
+		return;
+
+	spin_lock_bh(&np->onestep_lock);
+	np->onestep_state = NETC_ONESTEP_IDLE;
+	spin_unlock_bh(&np->onestep_lock);
+}
+
+static void netc_port_purge_onestep_queue(struct netc_port *np)
+{
+	struct sk_buff_head free_list;
+
+	__skb_queue_head_init(&free_list);
+
+	spin_lock_bh(&np->onestep_lock);
+	skb_queue_splice_init(&np->onestep_queue, &free_list);
+	spin_unlock_bh(&np->onestep_lock);
+
+	__skb_queue_purge(&free_list);
+}
+
+static void netc_port_onestep_work(struct work_struct *work)
+{
+	struct netc_port *np = container_of(work, struct netc_port,
+					    onestep_work);
+	struct netc_switch *priv = np->switch_priv;
+	struct netc_tagger_data *tagger_data;
+	struct sk_buff *clone = NULL;
+	struct sk_buff *skb = NULL;
+	int ts_req_id;
+	u64 tstamp;
+
+	spin_lock_bh(&np->onestep_lock);
+
+	if (unlikely(np->onestep_state == NETC_ONESTEP_PORT_INACTIVE))
+		goto purge_onestep_queue;
+
+skb_dequeue:
+	skb = __skb_dequeue(&np->onestep_queue);
+	if (!skb)
+		goto set_onestep_state_idle;
+
+	/* Clone is a ts_req_id token only; its payload is never read, so
+	 * sharing the buffer with the mutated original is fine.
+	 */
+	clone = skb_clone(skb, GFP_ATOMIC);
+	if (unlikely(!clone)) {
+		kfree_skb(skb);
+		goto skb_dequeue;
+	}
+
+	spin_lock_bh(&np->tstamp_lock);
+	ts_req_id = netc_get_ts_req_id(np);
+	if (unlikely(ts_req_id < 0)) {
+		spin_unlock_bh(&np->tstamp_lock);
+
+		/* Re-queuing the frame and immediately rescheduling the work
+		 * would busy-loop on system_percpu_wq and burn CPU until an
+		 * ID is freed, so drop this frame and move on to the next one
+		 * in the queue instead.
+		 */
+		np->onestep_state = NETC_ONESTEP_SCHEDULED;
+		schedule_work(&np->onestep_work);
+
+		goto onestep_unlock;
+	}
+
+	/* PHC is unavailable, drop the whole queue */
+	if (unlikely(netc_get_phc_time(priv, &tstamp))) {
+		spin_unlock_bh(&np->tstamp_lock);
+		goto set_onestep_state_idle;
+	}
+
+	NETC_SKB_CB(skb)->tstamp = tstamp;
+	NETC_SKB_CB(skb)->ts_req_id = ts_req_id;
+	NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_ONESTEP;
+	NETC_SKB_CB(clone)->ts_req_id = ts_req_id;
+	NETC_SKB_CB(clone)->ptp_tx_time = jiffies_64;
+	NETC_SKB_CB(clone)->ptp_flag = NETC_PTP_FLAG_ONESTEP;
+	np->onestep_tx_time = NETC_SKB_CB(clone)->ptp_tx_time;
+	np->onestep_ts_req_id = ts_req_id;
+
+	__skb_queue_tail(&np->tstamp_queue, clone);
+	if (!delayed_work_pending(&np->tstamp_timeout_work))
+		schedule_delayed_work(&np->tstamp_timeout_work,
+				      NETC_TSTAMP_TIMEOUT);
+
+	spin_unlock_bh(&np->tstamp_lock);
+
+	np->onestep_state = NETC_ONESTEP_IN_FLIGHT;
+	spin_unlock_bh(&np->onestep_lock);
+
+	netc_port_program_onestep(np, skb);
+	tagger_data = priv->ds->tagger_data;
+	tagger_data->onestep_sync_xmit(skb, np->dp->user);
+
+	return;
+
+set_onestep_state_idle:
+	np->onestep_state = NETC_ONESTEP_IDLE;
+purge_onestep_queue:
+	__skb_queue_purge(&np->onestep_queue);
+onestep_unlock:
+	spin_unlock_bh(&np->onestep_lock);
+	kfree_skb(skb);
+	kfree_skb(clone);
+}
+
+static bool netc_onestep_timeout(struct netc_port *np)
+{
+	u64 expire_time;
+
+	/* Use monotonic jiffies_64, as the PHC may be stepped backwards.
+	 * Add one tick since the ns-to-jiffies conversion rounds down, so
+	 * the software window is never shorter than the hardware window.
+	 */
+	expire_time = np->onestep_tx_time + 1 +
+		      nsecs_to_jiffies64(NETC_ONESTEP_VALID_WINDOW);
+	if (np->onestep_state == NETC_ONESTEP_IN_FLIGHT &&
+	    time_after64(jiffies_64, expire_time))
+		return true;
+
+	return false;
+}
+
+void netc_port_onestep_sync_enqueue(struct dsa_switch *ds, int port,
+				    struct sk_buff *skb)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+
+	spin_lock_bh(&np->onestep_lock);
+	if (unlikely(np->onestep_state == NETC_ONESTEP_PORT_INACTIVE)) {
+		kfree_skb(skb);
+		goto onestep_unlock;
+	}
+
+	if (unlikely(skb_queue_len(&np->onestep_queue) >= NETC_ONESTEP_QTH)) {
+		dev_dbg_ratelimited(np->switch_priv->dev,
+				    "The onestep_queue of port %d is full\n",
+				    port);
+		kfree_skb(skb);
+		goto onestep_unlock;
+	}
+
+	__skb_queue_tail(&np->onestep_queue, skb);
+	if (likely(np->onestep_state == NETC_ONESTEP_IDLE) ||
+	    netc_onestep_timeout(np)) {
+		np->onestep_state = NETC_ONESTEP_SCHEDULED;
+		schedule_work(&np->onestep_work);
+	}
+
+onestep_unlock:
+	spin_unlock_bh(&np->onestep_lock);
+}
+
+int netc_port_ptp_init(struct netc_port *np)
+{
+	/* Initialize to invalid entry IDs */
+	for (int i = 0; i < NETC_PTP_MAX; i++)
+		np->ptp_ipft_eid[i] = NTMP_NULL_ENTRY_ID;
+
+	spin_lock_init(&np->onestep_lock);
+	__skb_queue_head_init(&np->onestep_queue);
+	INIT_WORK(&np->onestep_work, netc_port_onestep_work);
+
+	spin_lock_init(&np->tstamp_lock);
+	__skb_queue_head_init(&np->tstamp_queue);
+	INIT_DELAYED_WORK(&np->tstamp_timeout_work,
+			  netc_port_tstamp_timeout_work);
+
+	return 0;
+}
+
+void netc_port_purge_tstamp_queue(struct netc_port *np)
+{
+	struct sk_buff_head free_list;
+
+	__skb_queue_head_init(&free_list);
+
+	spin_lock_bh(&np->tstamp_lock);
+	skb_queue_splice_init(&np->tstamp_queue, &free_list);
+	spin_unlock_bh(&np->tstamp_lock);
+
+	__skb_queue_purge(&free_list);
+}
+
+static int netc_get_phc_index(struct netc_switch *priv)
+{
+	if (!priv->tmr_dev)
+		return -1;
+
+	return ptp_clock_index_by_dev(&priv->tmr_dev->dev);
+}
+
+int netc_get_ts_info(struct dsa_switch *ds, int port,
+		     struct kernel_ethtool_ts_info *info)
+{
+	struct netc_switch *priv = ds->priv;
+
+	info->phc_index = netc_get_phc_index(priv);
+	if (info->phc_index < 0)
+		return 0;
+
+	info->so_timestamping |= SOF_TIMESTAMPING_TX_HARDWARE |
+				 SOF_TIMESTAMPING_RX_HARDWARE |
+				 SOF_TIMESTAMPING_RAW_HARDWARE;
+
+	info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON) |
+			 BIT(HWTSTAMP_TX_ONESTEP_SYNC);
+
+	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT);
+
+	return 0;
+}
+
+static int netc_port_del_ptp_filter(struct netc_port *np)
+{
+	struct netc_switch *priv = np->switch_priv;
+	int ret = 0;
+	int err;
+
+	for (int i = 0; i < NETC_PTP_MAX; i++) {
+		if (np->ptp_ipft_eid[i] == NTMP_NULL_ENTRY_ID)
+			continue;
+
+		/* No -ETIMEDOUT here: with the command BD ring enabled, the
+		 * hardware never times out on a command. Any remaining error
+		 * means the entry is still present in the table.
+		 */
+		err = ntmp_ipft_delete_entry(&priv->ntmp,
+					     np->ptp_ipft_eid[i]);
+		if (likely(!err)) {
+			np->ptp_ipft_eid[i] = NTMP_NULL_ENTRY_ID;
+			continue;
+		}
+
+		ret = err;
+		dev_err(priv->dev,
+			"Delete PTP entry 0x%x (type %d) on port %d failed\n",
+			np->ptp_ipft_eid[i], i, np->dp->index);
+	}
+
+	return ret;
+}
+
+static int netc_build_ptp_ipft_keye(struct ipft_keye_data *keye, int port,
+				    enum netc_ptp_type type)
+{
+	u16 src_port, frm_attr_flags;
+
+	keye->precedence = cpu_to_le16(NETC_IPFT_PTP_PRECEDENCE);
+	src_port = FIELD_PREP(IPFT_SRC_PORT, port);
+	src_port |= IPFT_SRC_PORT_MASK;
+	keye->src_port = cpu_to_le16(src_port);
+
+	switch (type) {
+	case NETC_PTP_L2:
+		keye->ethertype = htons(ETH_P_1588);
+		keye->ethertype_mask = htons(0xffff);
+		break;
+	case NETC_PTP_L4_IPV4_EVENT:
+	case NETC_PTP_L4_IPV4_GENERAL:
+	case NETC_PTP_L4_IPV6_EVENT:
+	case NETC_PTP_L4_IPV6_GENERAL:
+		frm_attr_flags = IPFT_FAF_IP_HDR | FIELD_PREP(IPFT_FAF_L4_CODE,
+				 IPFT_FAF_UDP_HDR);
+		if (type == NETC_PTP_L4_IPV6_EVENT ||
+		    type == NETC_PTP_L4_IPV6_GENERAL)
+			frm_attr_flags |= IPFT_FAF_IP_VER6;
+
+		keye->frm_attr_flags = cpu_to_le16(frm_attr_flags);
+
+		/* Set IP version bit in flags_mask to match IPv4 or IPv6
+		 * packets
+		 */
+		frm_attr_flags |= IPFT_FAF_IP_VER6;
+		keye->frm_attr_flags_mask = cpu_to_le16(frm_attr_flags);
+		keye->ip_protocol = IPPROTO_UDP;
+		keye->ip_protocol_mask = 0xff;
+
+		if (type == NETC_PTP_L4_IPV4_EVENT ||
+		    type == NETC_PTP_L4_IPV6_EVENT)
+			keye->l4_dst_port = htons(PTP_EV_PORT);
+		else
+			keye->l4_dst_port = htons(PTP_GEN_PORT);
+
+		keye->l4_dst_port_mask = htons(0xffff);
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	return 0;
+}
+
+static int netc_port_add_ipft_ptp_entry(struct netc_port *np,
+					enum netc_ptp_type type)
+{
+	struct netc_switch *priv = np->switch_priv;
+	struct ipft_entry_data *entry;
+	struct ipft_keye_data *keye;
+	u32 cfg;
+	int err;
+
+	entry = kzalloc_obj(*entry);
+	if (!entry)
+		return -ENOMEM;
+
+	keye = &entry->keye;
+	err = netc_build_ptp_ipft_keye(keye, np->dp->index, type);
+	if (err)
+		goto free_entry;
+
+	cfg = FIELD_PREP(IPFT_FLTFA, IPFT_FLTFA_REDIRECT);
+	cfg |= FIELD_PREP(IPFT_HR, NETC_HR_PTP_TRAP);
+	cfg |= IPFT_TIMECAPE | IPFT_RRT;
+	entry->cfge.cfg = cpu_to_le32(cfg);
+
+	err = ntmp_ipft_add_entry(&priv->ntmp, entry);
+	if (err)
+		goto free_entry;
+
+	np->ptp_ipft_eid[type] = entry->entry_id;
+
+free_entry:
+	kfree(entry);
+
+	return err;
+}
+
+static int netc_port_add_l2_ptp_filter(struct netc_port *np)
+{
+	return netc_port_add_ipft_ptp_entry(np, NETC_PTP_L2);
+}
+
+static int netc_port_add_l4_ptp_filter(struct netc_port *np)
+{
+	int err;
+
+	err = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV4_EVENT);
+	if (err)
+		return err;
+
+	err = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV4_GENERAL);
+	if (err)
+		goto del_ptp_filter;
+
+	err = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV6_EVENT);
+	if (err)
+		goto del_ptp_filter;
+
+	err = netc_port_add_ipft_ptp_entry(np, NETC_PTP_L4_IPV6_GENERAL);
+	if (err)
+		goto del_ptp_filter;
+
+	return 0;
+
+del_ptp_filter:
+	netc_port_del_ptp_filter(np);
+
+	return err;
+}
+
+static int netc_port_add_l2_l4_ptp_filter(struct netc_port *np)
+{
+	int err;
+
+	err = netc_port_add_l2_ptp_filter(np);
+	if (err)
+		return err;
+
+	err = netc_port_add_l4_ptp_filter(np);
+	if (err)
+		goto del_ptp_filter;
+
+	return 0;
+
+del_ptp_filter:
+	netc_port_del_ptp_filter(np);
+
+	return err;
+}
+
+static int netc_port_set_ptp_filter(struct netc_port *np, int rx_filter)
+{
+	int err;
+
+	err = netc_port_del_ptp_filter(np);
+	if (err)
+		return err;
+
+	np->ptp_rx_filter = HWTSTAMP_FILTER_NONE;
+
+	switch (rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+		err = netc_port_add_l2_ptp_filter(np);
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+		err = netc_port_add_l4_ptp_filter(np);
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+		err = netc_port_add_l2_l4_ptp_filter(np);
+		break;
+	default:
+		err = -ERANGE;
+	}
+
+	if (err)
+		return err;
+
+	np->ptp_rx_filter = rx_filter;
+
+	return 0;
+}
+
+int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
+			   struct kernel_hwtstamp_config *config,
+			   struct netlink_ext_ack *extack)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+	struct netc_switch *priv = ds->priv;
+	int rx_filter, err;
+
+	if ((config->tx_type != HWTSTAMP_TX_OFF ||
+	     config->rx_filter != HWTSTAMP_FILTER_NONE) &&
+	    netc_get_phc_index(priv) < 0)
+		return -EOPNOTSUPP;
+
+	switch (config->tx_type) {
+	case HWTSTAMP_TX_ONESTEP_SYNC:
+	case HWTSTAMP_TX_ON:
+	case HWTSTAMP_TX_OFF:
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	switch (config->rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		rx_filter = HWTSTAMP_FILTER_NONE;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+		rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+		rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+		rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	err = netc_port_set_ptp_filter(np, rx_filter);
+	if (err) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to set PTP filter");
+		return err;
+	}
+
+	WRITE_ONCE(np->ptp_tx_type, config->tx_type);
+	if (config->tx_type != HWTSTAMP_TX_ONESTEP_SYNC)
+		netc_port_purge_onestep_queue(np);
+
+	config->rx_filter = rx_filter;
+
+	return 0;
+}
+
+int netc_port_hwtstamp_get(struct dsa_switch *ds, int port,
+			   struct kernel_hwtstamp_config *config)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+
+	config->tx_type = READ_ONCE(np->ptp_tx_type);
+	config->rx_filter = np->ptp_rx_filter;
+
+	return 0;
+}
+
+static void netc_port_prepare_onestep_sync(struct netc_port *np,
+					   struct sk_buff *skb,
+					   u32 ptp_class, bool *twostep)
+{
+	struct netc_switch *priv = np->switch_priv;
+	u16 correction_offset, tstamp_offset;
+	struct ptp_header *ptp_hdr;
+	u8 msg_type, twostep_flag;
+	bool is_udp = false;
+	u32 pkt_type;
+	u8 *pkt_hdr;
+
+	if (unlikely(skb_linearize_cow(skb)))
+		goto set_ptp_flag_drop;
+
+	ptp_hdr = ptp_parse_header(skb, ptp_class);
+	if (unlikely(!ptp_hdr))
+		goto set_ptp_flag_drop;
+
+	msg_type = ptp_get_msgtype(ptp_hdr, ptp_class);
+	twostep_flag = ptp_hdr->flag_field[0] & 0x2;
+	if (msg_type != PTP_MSGTYPE_SYNC || twostep_flag != 0) {
+		*twostep = true;
+		return;
+	}
+
+	pkt_hdr = skb_mac_header(skb);
+	correction_offset = (u8 *)&ptp_hdr->correction - pkt_hdr;
+	tstamp_offset = (u8 *)ptp_hdr + sizeof(*ptp_hdr) - pkt_hdr;
+
+	/* Ensure that the entire originTimestamp field is present in the
+	 * linear buffer of the skb and the correction_offset must be within
+	 * the hardware capability.
+	 */
+	if (unlikely(tstamp_offset + 10 > skb_headlen(skb) ||
+		     correction_offset > NETC_MAX_STEP_OFFSET))
+		goto set_ptp_flag_drop;
+
+	pkt_type = ptp_class & PTP_CLASS_PMASK;
+	if (pkt_type == PTP_CLASS_IPV4 || pkt_type == PTP_CLASS_IPV6)
+		is_udp = true;
+
+	NETC_SKB_CB(skb)->correction_offset = correction_offset;
+	NETC_SKB_CB(skb)->timestamp_offset = tstamp_offset;
+	NETC_SKB_CB(skb)->is_udp = is_udp;
+	NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_ONESTEP;
+
+	return;
+
+set_ptp_flag_drop:
+	/* Drop instead of falling back to two-step: if it is a Sync,
+	 * one-step offload will not be executed, the timestamp in the
+	 * frame is inaccurate, which may affect PTP synchronization.
+	 */
+	NETC_SKB_CB(skb)->ptp_flag = NETC_PTP_FLAG_DROP;
+	dev_dbg_ratelimited(priv->dev,
+			    "Port %d: PTP frame dropped in error\n",
+			    np->dp->index);
+}
+
+static void netc_port_prepare_twostep(struct netc_port *np,
+				      struct sk_buff *nskb)
+{
+	struct sk_buff *clone = skb_clone_sk(nskb);
+	int ts_req_id;
+
+	if (unlikely(!clone))
+		return;
+
+	spin_lock_bh(&np->tstamp_lock);
+
+	ts_req_id = netc_get_ts_req_id(np);
+	if (ts_req_id < 0) {
+		spin_unlock_bh(&np->tstamp_lock);
+		kfree_skb(clone);
+		return;
+	}
+
+	NETC_SKB_CB(nskb)->ptp_flag = NETC_PTP_FLAG_TWOSTEP;
+	NETC_SKB_CB(nskb)->ts_req_id = ts_req_id;
+	NETC_SKB_CB(clone)->ts_req_id = ts_req_id;
+	NETC_SKB_CB(clone)->ptp_tx_time = jiffies_64;
+	NETC_SKB_CB(clone)->ptp_flag = NETC_PTP_FLAG_TWOSTEP;
+	skb_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS;
+	__skb_queue_tail(&np->tstamp_queue, clone);
+	if (!delayed_work_pending(&np->tstamp_timeout_work))
+		schedule_delayed_work(&np->tstamp_timeout_work,
+				      NETC_TSTAMP_TIMEOUT);
+
+	spin_unlock_bh(&np->tstamp_lock);
+}
+
+void netc_port_txtstamp_handler(struct dsa_switch *ds, int port,
+				u8 ts_req_id, u64 ts)
+{
+	struct sk_buff *skb, *skb_tmp, *skb_match = NULL;
+	struct netc_port *np = NETC_PORT(ds, port);
+	struct skb_shared_hwtstamps hwtstamps;
+
+	spin_lock_bh(&np->tstamp_lock);
+	skb_queue_walk_safe(&np->tstamp_queue, skb, skb_tmp) {
+		if (NETC_SKB_CB(skb)->ts_req_id != ts_req_id)
+			continue;
+
+		__skb_unlink(skb, &np->tstamp_queue);
+		skb_match = skb;
+		break;
+	}
+	spin_unlock_bh(&np->tstamp_lock);
+
+	if (!skb_match) {
+		dev_dbg_ratelimited(np->switch_priv->dev,
+				    "Port %d ts_req_id %u which seems lost\n",
+				    port, ts_req_id);
+
+		return;
+	}
+
+	if (NETC_SKB_CB(skb_match)->ptp_flag == NETC_PTP_FLAG_ONESTEP) {
+		spin_lock_bh(&np->onestep_lock);
+		if (likely(np->onestep_state == NETC_ONESTEP_IN_FLIGHT &&
+			   np->onestep_ts_req_id == ts_req_id) ||
+		    np->onestep_state == NETC_ONESTEP_IDLE) {
+			np->onestep_state = NETC_ONESTEP_SCHEDULED;
+			schedule_work(&np->onestep_work);
+		}
+		spin_unlock_bh(&np->onestep_lock);
+		consume_skb(skb_match);
+
+		return;
+	}
+
+	hwtstamps.hwtstamp = ns_to_ktime(ts);
+	skb_complete_tx_timestamp(skb_match, &hwtstamps);
+}
+
+bool netc_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,
+			unsigned int type)
+{
+	struct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb);
+
+	if (!NETC_SKB_CB(skb)->rx_tstamp_valid)
+		return false;
+
+	hwtstamps->hwtstamp = ns_to_ktime(NETC_SKB_CB(skb)->tstamp);
+
+	return false;
+}
+
+void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
+{
+	struct netc_port *np = NETC_PORT(ds, port);
+	bool twostep = false;
+	u32 ptp_class;
+	int tx_type;
+
+	NETC_SKB_CB(skb)->ptp_flag = 0;
+	ptp_class = ptp_classify_raw(skb);
+	if (ptp_class == PTP_CLASS_NONE)
+		return;
+
+	/* The rx_filters in netc_get_ts_info() has already declared that
+	 * it only supports PTP v2, so TX only supports v2 as well.
+	 */
+	if (unlikely(ptp_class & PTP_CLASS_V1))
+		return;
+
+	tx_type = READ_ONCE(np->ptp_tx_type);
+	if (tx_type == HWTSTAMP_TX_ONESTEP_SYNC)
+		netc_port_prepare_onestep_sync(np, skb, ptp_class, &twostep);
+
+	if (tx_type == HWTSTAMP_TX_ON || twostep)
+		netc_port_prepare_twostep(np, skb);
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 305f2a92e2f9a..624cf323c9a62 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -53,10 +53,16 @@
 #define NETC_FDBT_AGEING_DELAY		(3 * HZ)
 #define NETC_FDBT_AGEING_THRESH		100
 
+/* PTP frames have a higher priority, so a higher priority is defined to
+ * prioritize matching (The higher the value, the higher the priority).
+ */
+#define NETC_IPFT_PTP_PRECEDENCE	0xf000
+
 struct netc_switch;
 
 struct netc_switch_info {
 	u32 num_ports;
+	u32 tmr_devfn;
 	void (*phylink_get_caps)(int port, struct phylink_config *config);
 };
 
@@ -66,9 +72,26 @@ struct netc_port_caps {
 	u32 pseudo_link:1;
 };
 
+enum netc_ptp_type {
+	NETC_PTP_L2,
+	NETC_PTP_L4_IPV4_EVENT,
+	NETC_PTP_L4_IPV4_GENERAL,
+	NETC_PTP_L4_IPV6_EVENT,
+	NETC_PTP_L4_IPV6_GENERAL,
+	NETC_PTP_MAX,
+};
+
 enum netc_host_reason {
 	/* Software defined host reasons */
 	NETC_HR_HOST_FLOOD = 8,
+	NETC_HR_PTP_TRAP   = 9,
+};
+
+enum netc_onestep_state {
+	NETC_ONESTEP_IDLE	= 0,
+	NETC_ONESTEP_SCHEDULED,
+	NETC_ONESTEP_IN_FLIGHT,
+	NETC_ONESTEP_PORT_INACTIVE,
 };
 
 struct netc_port {
@@ -84,7 +107,27 @@ struct netc_port {
 	u16 uc:1;
 	u16 mc:1;
 	u16 pvid;
-	struct ipft_entry_data *host_flood;
+	u32 ipft_hf_eid; /* Must be initialized to NTMP_NULL_ENTRY_ID */
+
+	/* Serialize access to onestep_queue, onestep_state and
+	 * onestep_tx_time
+	 */
+	spinlock_t onestep_lock;
+	u8 onestep_state;
+	u8 onestep_ts_req_id;
+	u64 onestep_tx_time;
+	/* skb queue for one-step Sync frames */
+	struct sk_buff_head onestep_queue;
+	struct work_struct onestep_work;
+
+	/* Serialize access to tstamp_queue */
+	spinlock_t tstamp_lock;
+	/* skb queue for TX timestamp frames */
+	struct sk_buff_head tstamp_queue;
+	struct delayed_work tstamp_timeout_work;
+	int ptp_tx_type;
+	int ptp_rx_filter;
+	u32 ptp_ipft_eid[NETC_PTP_MAX];
 };
 
 struct netc_switch_regs {
@@ -139,6 +182,7 @@ struct netc_switch {
 	u32 num_bp;
 
 	struct bpt_cfge_data *bpt_list;
+	struct pci_dev *tmr_dev; /* The PTP Timer PCI device */
 };
 
 #define NETC_PRIV(ds)			((struct netc_switch *)((ds)->priv))
@@ -187,6 +231,7 @@ static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
 }
 
 int netc_switch_platform_probe(struct netc_switch *priv);
+void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val);
 
 /* ethtool APIs */
 void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
@@ -203,4 +248,24 @@ void netc_port_get_strings(struct dsa_switch *ds, int port,
 			   u32 sset, u8 *data);
 void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
 
+/* PTP APIs */
+int netc_port_ptp_init(struct netc_port *np);
+int netc_get_ts_info(struct dsa_switch *ds, int port,
+		     struct kernel_ethtool_ts_info *info);
+void netc_port_purge_tstamp_queue(struct netc_port *np);
+int netc_port_hwtstamp_set(struct dsa_switch *ds, int port,
+			   struct kernel_hwtstamp_config *config,
+			   struct netlink_ext_ack *extack);
+int netc_port_hwtstamp_get(struct dsa_switch *ds, int port,
+			   struct kernel_hwtstamp_config *config);
+void netc_port_txtstamp_handler(struct dsa_switch *ds, int port,
+				u8 ts_req_id, u64 ts);
+bool netc_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,
+			unsigned int type);
+void netc_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);
+void netc_port_disable_onestep(struct netc_port *np);
+void netc_port_enable_onestep(struct netc_port *np);
+void netc_port_onestep_sync_enqueue(struct dsa_switch *ds, int port,
+				    struct sk_buff *skb);
+
 #endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 1404ae41c7bcb..37d1dd7ec2c7f 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -203,6 +203,11 @@ enum netc_stg_stage {
 #define   SSP_10M			1
 #define   SSP_1G			2
 
+#define NETC_PM_SINGLE_STEP(a)		(0x10c0 + (a) * 0x400)
+#define  PM_SINGLE_STEP_CH		BIT(6)
+#define  PM_SINGLE_STEP_OFFSET		GENMASK(15, 7)
+#define  PM_SINGLE_STEP_EN		BIT(31)
+
 /* Port MAC 0/1 Receive Ethernet Octets Counter */
 #define NETC_PM_REOCT(a)		(0x1100 + (a) * 0x400)
 
diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c
index 19ca99d80e95b..1211aa9a37d3d 100644
--- a/drivers/ptp/ptp_netc.c
+++ b/drivers/ptp/ptp_netc.c
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
 /*
  * NXP NETC V4 Timer driver
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
  */
 
 #include <linux/bitfield.h>
@@ -123,10 +123,25 @@ struct netc_timer {
 	u8 fs_alarm_num;
 	u8 fs_alarm_bitmap;
 	struct netc_pp pp[NETC_TMR_FIPER_NUM]; /* periodic pulse */
+	struct list_head node;
 };
 
+static LIST_HEAD(netc_timer_list);
+static DEFINE_SPINLOCK(netc_timer_list_lock);
+
 #define netc_timer_rd(p, o)		netc_read((p)->base + (o))
 #define netc_timer_wr(p, o, v)		netc_write((p)->base + (o), v)
+
+/* The 64-bit timer registers consist of a low (L) and high (H) register pair.
+ * Hardware requires a strict access order: for writes, TMR_xxx_L must be
+ * written first, which latches the value into a shadow register; the write
+ * to TMR_xxx_H then atomically transfers both shadow registers into the live
+ * counter. For reads, TMR_xxx_L must be read first to capture a coherent
+ * snapshot. iowrite64_lo_hi() and ioread64_lo_hi() enforce this L-before-H
+ * ordering.
+ */
+#define netc_timer_rd64(p, o)		ioread64_lo_hi((p)->base + (o))
+#define netc_timer_wr64(p, o, v)	iowrite64_lo_hi(v, (p)->base + (o))
 #define ptp_to_netc_timer(ptp)		container_of((ptp), struct netc_timer, caps)
 
 static const char *const timer_clk_src[] = {
@@ -136,66 +151,28 @@ static const char *const timer_clk_src[] = {
 
 static void netc_timer_cnt_write(struct netc_timer *priv, u64 ns)
 {
-	u32 tmr_cnt_h = upper_32_bits(ns);
-	u32 tmr_cnt_l = lower_32_bits(ns);
-
-	/* Writes to the TMR_CNT_L register copies the written value
-	 * into the shadow TMR_CNT_L register. Writes to the TMR_CNT_H
-	 * register copies the values written into the shadow TMR_CNT_H
-	 * register. Contents of the shadow registers are copied into
-	 * the TMR_CNT_L and TMR_CNT_H registers following a write into
-	 * the TMR_CNT_H register. So the user must writes to TMR_CNT_L
-	 * register first. Other H/L registers should have the same
-	 * behavior.
-	 */
-	netc_timer_wr(priv, NETC_TMR_CNT_L, tmr_cnt_l);
-	netc_timer_wr(priv, NETC_TMR_CNT_H, tmr_cnt_h);
+	netc_timer_wr64(priv, NETC_TMR_CNT_L, ns);
 }
 
 static u64 netc_timer_offset_read(struct netc_timer *priv)
 {
-	u32 tmr_off_l, tmr_off_h;
-	u64 offset;
-
-	tmr_off_l = netc_timer_rd(priv, NETC_TMR_OFF_L);
-	tmr_off_h = netc_timer_rd(priv, NETC_TMR_OFF_H);
-	offset = (((u64)tmr_off_h) << 32) | tmr_off_l;
-
-	return offset;
+	return netc_timer_rd64(priv, NETC_TMR_OFF_L);
 }
 
 static void netc_timer_offset_write(struct netc_timer *priv, u64 offset)
 {
-	u32 tmr_off_h = upper_32_bits(offset);
-	u32 tmr_off_l = lower_32_bits(offset);
-
-	netc_timer_wr(priv, NETC_TMR_OFF_L, tmr_off_l);
-	netc_timer_wr(priv, NETC_TMR_OFF_H, tmr_off_h);
+	netc_timer_wr64(priv, NETC_TMR_OFF_L, offset);
 }
 
 static u64 netc_timer_cur_time_read(struct netc_timer *priv)
 {
-	u32 time_h, time_l;
-	u64 ns;
-
-	/* The user should read NETC_TMR_CUR_TIME_L first to
-	 * get correct current time.
-	 */
-	time_l = netc_timer_rd(priv, NETC_TMR_CUR_TIME_L);
-	time_h = netc_timer_rd(priv, NETC_TMR_CUR_TIME_H);
-	ns = (u64)time_h << 32 | time_l;
-
-	return ns;
+	return netc_timer_rd64(priv, NETC_TMR_CUR_TIME_L);
 }
 
 static void netc_timer_alarm_write(struct netc_timer *priv,
 				   u64 alarm, int index)
 {
-	u32 alarm_h = upper_32_bits(alarm);
-	u32 alarm_l = lower_32_bits(alarm);
-
-	netc_timer_wr(priv, NETC_TMR_ALARM_L(index), alarm_l);
-	netc_timer_wr(priv, NETC_TMR_ALARM_H(index), alarm_h);
+	netc_timer_wr64(priv, NETC_TMR_ALARM_L(index), alarm);
 }
 
 static u32 netc_timer_get_integral_period(struct netc_timer *priv)
@@ -500,22 +477,19 @@ static void netc_timer_handle_etts_event(struct netc_timer *priv, int index,
 					 bool update_event)
 {
 	struct ptp_clock_event event;
-	u32 etts_l = 0, etts_h = 0;
+	u64 etts = 0;
 
-	while (netc_timer_rd(priv, NETC_TMR_STAT) & TMR_STAT_ETS_VLD(index)) {
-		etts_l = netc_timer_rd(priv, NETC_TMR_ETTS_L(index));
-		etts_h = netc_timer_rd(priv, NETC_TMR_ETTS_H(index));
-	}
+	while (netc_timer_rd(priv, NETC_TMR_STAT) & TMR_STAT_ETS_VLD(index))
+		etts = netc_timer_rd64(priv, NETC_TMR_ETTS_L(index));
 
 	/* Invalid time stamp */
-	if (!etts_l && !etts_h)
+	if (!etts)
 		return;
 
 	if (update_event) {
 		event.type = PTP_CLOCK_EXTTS;
 		event.index = index;
-		event.timestamp = (u64)etts_h << 32;
-		event.timestamp |= etts_l;
+		event.timestamp = etts;
 		ptp_clock_event(priv->clock, &event);
 	}
 }
@@ -807,7 +781,6 @@ static int netc_timer_pci_probe(struct pci_dev *pdev)
 	if (!priv)
 		return -ENOMEM;
 
-	pcie_flr(pdev);
 	err = pci_enable_device_mem(pdev);
 	if (err)
 		return dev_err_probe(dev, err, "Failed to enable device\n");
@@ -1016,6 +989,10 @@ static int netc_timer_probe(struct pci_dev *pdev,
 
 	enable_irq(priv->irq);
 
+	spin_lock_bh(&netc_timer_list_lock);
+	list_add(&priv->node, &netc_timer_list);
+	spin_unlock_bh(&netc_timer_list_lock);
+
 	return 0;
 
 free_msix_irq:
@@ -1030,6 +1007,10 @@ static void netc_timer_remove(struct pci_dev *pdev)
 {
 	struct netc_timer *priv = pci_get_drvdata(pdev);
 
+	spin_lock_bh(&netc_timer_list_lock);
+	list_del(&priv->node);
+	spin_unlock_bh(&netc_timer_list_lock);
+
 	disable_irq(priv->irq);
 	ptp_clock_unregister(priv->clock);
 	netc_timer_wr(priv, NETC_TMR_TEMASK, 0);
@@ -1052,5 +1033,54 @@ static struct pci_driver netc_timer_driver = {
 };
 module_pci_driver(netc_timer_driver);
 
+/**
+ * netc_timer_get_current_time - read the current PTP time from the NETC Timer
+ * @pdev: PCI device of the NETC Timer
+ * @ns: Output, the current PTP clock time in nanoseconds
+ *
+ * Read TMR_CUR_TIME from the NETC Timer bound to @pdev. The lookup and read
+ * run under netc_timer_list_lock, so the Timer cannot be unbound and its priv
+ * freed during the read.
+ *
+ * Context: Process or softirq context. Must not be called from hardirq.
+ *
+ * Return: 0 on success, -ENODEV if the Timer is not present (not yet probed
+ *         or already removed).
+ */
+int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns)
+{
+	struct netc_timer *priv = NULL;
+	struct netc_timer *tmp;
+	unsigned long flags;
+	int err = 0;
+
+	/* Serialize against driver unbind, so holding it here ensures that
+	 * priv remains valid for the entire duration of the register read.
+	 */
+	spin_lock_bh(&netc_timer_list_lock);
+
+	list_for_each_entry(tmp, &netc_timer_list, node) {
+		if (tmp->pdev == pdev) {
+			priv = tmp;
+			break;
+		}
+	}
+
+	if (!priv) {
+		err = -ENODEV;
+		goto netc_timer_list_unlock;
+	}
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*ns = netc_timer_cur_time_read(priv);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+netc_timer_list_unlock:
+	spin_unlock_bh(&netc_timer_list_lock);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(netc_timer_get_current_time);
+
 MODULE_DESCRIPTION("NXP NETC Timer PTP Driver");
 MODULE_LICENSE("Dual BSD/GPL");
diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h
index fe964722e5b03..baf0f93bd9946 100644
--- a/include/linux/dsa/tag_netc.h
+++ b/include/linux/dsa/tag_netc.h
@@ -10,5 +10,43 @@
 #include <net/dsa.h>
 
 #define NETC_TAG_MAX_LEN			14
+#define NETC_PTP_FLAG_ONESTEP			BIT(0)
+#define NETC_PTP_FLAG_TWOSTEP			BIT(1)
+#define NETC_PTP_FLAG_DROP			BIT(2)
+
+struct netc_skb_cb {
+	u64 ptp_tx_time;
+	u64 tstamp;
+	bool rx_tstamp_valid;
+	u8 ptp_flag;
+	u8 ts_req_id;
+	bool is_udp;
+	u16 correction_offset;
+	u16 timestamp_offset;
+};
+
+#define NETC_SKB_CB(skb)	((struct netc_skb_cb *)((skb)->cb))
+
+/**
+ * struct netc_tagger_data - NETC tagger/switch-driver shared operations
+ * @txtstamp_handler: Called by the tagger when a two-step transmit timestamp
+ *	response is received, to deliver the timestamp to the switch driver.
+ * @onestep_sync_enqueue: Called from the tagger xmit path for a one-step Sync
+ *	frame. The switch driver takes ownership of the skb and queues it for
+ *	deferred transmission from process context, where the shared
+ *	PM_SINGLE_STEP register can be programmed and the PTP timer read. The
+ *	tagger must not touch the skb after this call and returns NULL to
+ *	dsa_user_xmit().
+ * @onestep_sync_xmit: Called by the switch driver to transmit a deferred
+ *	one-step Sync frame directly to the conduit, bypassing dsa_user_xmit().
+ */
+struct netc_tagger_data {
+	void (*txtstamp_handler)(struct dsa_switch *ds, int port,
+				 u8 ts_req_id, u64 ts);
+	void (*onestep_sync_enqueue)(struct dsa_switch *ds, int port,
+				     struct sk_buff *skb);
+	netdev_tx_t (*onestep_sync_xmit)(struct sk_buff *skb,
+					 struct net_device *ndev);
+};
 
 #endif
diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h
index 5b8ff528d3699..d4a26c17f99a4 100644
--- a/include/linux/fsl/netc_global.h
+++ b/include/linux/fsl/netc_global.h
@@ -6,6 +6,7 @@
 
 #include <linux/io.h>
 #include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/pci.h>
 
 static inline u32 netc_read(void __iomem *reg)
 {
@@ -22,4 +23,13 @@ static inline u64 netc_read64(void __iomem *reg)
 	return ioread64(reg);
 }
 
+#if IS_REACHABLE(CONFIG_PTP_NETC_V4_TIMER)
+int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns);
+#else
+static inline int netc_timer_get_current_time(struct pci_dev *pdev, u64 *ns)
+{
+	return -ENODEV;
+}
+#endif
+
 #endif
diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c
index df72a61796ad5..c1c0ffee75c13 100644
--- a/net/dsa/tag_netc.c
+++ b/net/dsa/tag_netc.c
@@ -16,6 +16,10 @@
 #define NETC_TAG_TO_PORT		1
 /* SubType0: No request to perform timestamping */
 #define NETC_TAG_TP_SUBTYPE0		0
+/* SubType2: Request to perform two-step timestamping */
+#define NETC_TAG_TP_SUBTYPE2		2
+/* SubType3: Request to perform both one-step and two-step timestamping */
+#define NETC_TAG_TP_SUBTYPE3		3
 
 /* To_Host NXP switch tag */
 #define NETC_TAG_TO_HOST		2
@@ -29,6 +33,8 @@
 /* NETC switch tag lengths */
 #define NETC_TAG_FORWARD_LEN		6
 #define NETC_TAG_TP_SUBTYPE0_LEN	6
+#define NETC_TAG_TP_SUBTYPE2_LEN	6
+#define NETC_TAG_TP_SUBTYPE3_LEN	10
 #define NETC_TAG_TH_SUBTYPE0_LEN	6
 #define NETC_TAG_TH_SUBTYPE1_LEN	14
 #define NETC_TAG_TH_SUBTYPE2_LEN	14
@@ -40,6 +46,8 @@
 #define NETC_TAG_IPV			GENMASK(4, 2)
 #define NETC_TAG_SWITCH			GENMASK(2, 0)
 #define NETC_TAG_PORT			GENMASK(7, 3)
+#define NETC_TAG_TS_REQ_ID		GENMASK(3, 0)
+#define NETC_TAG_TIMESTAMP		GENMASK(29, 0)
 
 struct netc_tag_cmn {
 	__be16 tpid;
@@ -48,6 +56,29 @@ struct netc_tag_cmn {
 	u8 switch_port;
 } __packed;
 
+struct netc_tag_tp_subtype2 {
+	struct netc_tag_cmn cmn;
+	u8 ts_req_id;
+} __packed;
+
+struct netc_tag_tp_subtype3 {
+	struct netc_tag_cmn cmn;
+	u8 ts_req_id;
+	__be32 timestamp;
+} __packed;
+
+struct netc_tag_th_subtype1 {
+	struct netc_tag_cmn cmn;
+	u8 host_reason;
+	__be64 timestamp;
+} __packed;
+
+struct netc_tag_th_subtype2 {
+	struct netc_tag_cmn cmn;
+	u8 hr_tsreq_id;
+	__be64 timestamp;
+} __packed;
+
 static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type,
 				 u8 subtype, u8 sw_id, u8 port, u8 ipv)
 {
@@ -97,15 +128,109 @@ static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,
 				NETC_TAG_TP_SUBTYPE0_LEN);
 }
 
-/* Currently only support To_Port tag, subtype 0 */
+static void netc_fill_tp_tag_subtype2(struct sk_buff *skb,
+				      struct net_device *ndev)
+{
+	u8 ts_req_id = NETC_SKB_CB(skb)->ts_req_id;
+	struct netc_tag_tp_subtype2 *tag;
+
+	tag = netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE2,
+				      NETC_TAG_TP_SUBTYPE2_LEN);
+	tag->ts_req_id = FIELD_PREP(NETC_TAG_TS_REQ_ID, ts_req_id);
+}
+
+static void netc_fill_tp_tag_subtype3(struct sk_buff *skb,
+				      struct net_device *ndev)
+{
+	u32 ts = FIELD_PREP(NETC_TAG_TIMESTAMP, NETC_SKB_CB(skb)->tstamp);
+	u8 ts_req_id = NETC_SKB_CB(skb)->ts_req_id;
+	struct netc_tag_tp_subtype3 *tag;
+
+	tag = netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE3,
+				      NETC_TAG_TP_SUBTYPE3_LEN);
+	tag->ts_req_id = FIELD_PREP(NETC_TAG_TS_REQ_ID, ts_req_id);
+	tag->timestamp = htonl(ts);
+}
+
+static void netc_onestep_sync_enqueue(struct sk_buff *skb,
+				      struct net_device *ndev)
+{
+	struct dsa_port *dp = dsa_user_to_port(ndev);
+	struct netc_tagger_data *tagger_data;
+
+	tagger_data = dp->ds->tagger_data;
+	if (unlikely(!tagger_data->onestep_sync_enqueue)) {
+		kfree_skb(skb);
+		return;
+	}
+
+	/* Hand the one-step Sync to the switch driver, which takes ownership
+	 * and queues it for deferred transmission from its work. The tagger
+	 * must not touch the skb after this point.
+	 */
+	tagger_data->onestep_sync_enqueue(dp->ds, dp->index, skb);
+}
+
+static netdev_tx_t netc_onestep_sync_xmit(struct sk_buff *skb,
+					  struct net_device *dev)
+{
+	netc_fill_tp_tag_subtype3(skb, dev);
+
+	return dsa_enqueue_skb(skb, dev);
+}
+
 static struct sk_buff *netc_xmit(struct sk_buff *skb,
 				 struct net_device *ndev)
 {
-	netc_fill_tp_tag_subtype0(skb, ndev);
+	u8 ptp_flag = NETC_SKB_CB(skb)->ptp_flag;
+
+	/* Fast path: the overwhelming majority of frames are not PTP frames */
+	if (likely(!ptp_flag)) {
+		netc_fill_tp_tag_subtype0(skb, ndev);
+	} else if (ptp_flag == NETC_PTP_FLAG_TWOSTEP) {
+		netc_fill_tp_tag_subtype2(skb, ndev);
+	} else if (ptp_flag == NETC_PTP_FLAG_ONESTEP) {
+		/* The switch driver takes ownership of the one-step Sync and
+		 * queues it for deferred TX; the deferred work tags it subtype
+		 * 3 and transmits it directly to the conduit. Return NULL so
+		 * dsa_user_xmit() stops processing this skb.
+		 */
+		netc_onestep_sync_enqueue(skb, ndev);
+		skb = NULL;
+	} else {
+		/* NETC_PTP_FLAG_DROP */
+		kfree_skb(skb);
+		skb = NULL;
+	}
 
 	return skb;
 }
 
+static void netc_rx_tstamp_process(struct netc_tag_th_subtype1 *tag,
+				   struct sk_buff *skb)
+{
+	u64 ts = get_unaligned_be64(&tag->timestamp);
+
+	NETC_SKB_CB(skb)->rx_tstamp_valid = true;
+	NETC_SKB_CB(skb)->tstamp = ts;
+}
+
+static void netc_twostep_tstamp_process(struct netc_tag_th_subtype2 *tag,
+					struct sk_buff *skb)
+{
+	u8 ts_req_id = FIELD_GET(NETC_TAG_TS_REQ_ID, tag->hr_tsreq_id);
+	struct dsa_port *dp = dsa_user_to_port(skb->dev);
+	u64 ts = get_unaligned_be64(&tag->timestamp);
+	struct netc_tagger_data *tagger_data;
+	struct dsa_switch *ds = dp->ds;
+
+	tagger_data = ds->tagger_data;
+	if (unlikely(!tagger_data->txtstamp_handler))
+		return;
+
+	tagger_data->txtstamp_handler(ds, dp->index, ts_req_id, ts);
+}
+
 static int netc_get_rx_tag_len(int type, int subtype)
 {
 	/* Only NETC_TAG_TO_HOST and NETC_TAG_FORWARD are expected in RX,
@@ -129,11 +254,22 @@ static struct sk_buff *netc_rcv(struct sk_buff *skb,
 	struct netc_tag_cmn *tag_cmn;
 	int tag_len, sw_id, port;
 	int type, subtype;
+	void *tag;
 
-	if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN)))
+	/* eth_type_trans() pulled ETH_HLEN bytes, so skb->data sits 2 bytes
+	 * past the start of the switch tag (past the TPID) and skb->len is
+	 * ETH_HLEN bytes shorter than the original frame length. The longest
+	 * switch tag is NETC_TAG_MAX_LEN (14) bytes, but since 2 of those
+	 * bytes are already behind skb->data, only NETC_TAG_MAX_LEN - 2 bytes
+	 * need to be in the linear buffer. For the To_Host subtype 2 response
+	 * frame, whose total length is only 26 bytes with no payload after the
+	 * tag, this check is the only guard against a too-short frame.
+	 */
+	if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN - 2)))
 		goto err_free_skb;
 
-	tag_cmn = dsa_etype_header_pos_rx(skb);
+	tag = dsa_etype_header_pos_rx(skb);
+	tag_cmn = tag;
 	if (ntohs(tag_cmn->tpid) != ETH_P_NXP_NETC) {
 		dev_warn_ratelimited(&ndev->dev, "Unknown TPID 0x%04x\n",
 				     ntohs(tag_cmn->tpid));
@@ -156,17 +292,50 @@ static struct sk_buff *netc_rcv(struct sk_buff *skb,
 	if (!skb->dev)
 		goto err_free_skb;
 
+	/* skb->cb may be used to store hardware RX timestamp, so clear
+	 * rx_tstamp_valid before processing to avoid data pollution from
+	 * the previous layer.
+	 */
+	NETC_SKB_CB(skb)->rx_tstamp_valid = false;
+
 	type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type);
 	subtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn->type);
 	if (type == NETC_TAG_FORWARD) {
 		dsa_default_offload_fwd_mark(skb);
 	} else if (type == NETC_TAG_TO_HOST) {
-		/* Currently only subtype0 supported */
-		if (subtype != NETC_TAG_TH_SUBTYPE0)
+		switch (subtype) {
+		case NETC_TAG_TH_SUBTYPE0:
+			break;
+		case NETC_TAG_TH_SUBTYPE1:
+			/* To_Host Subtype 1 tag is 14 bytes, ensure it and the
+			 * EtherType behind it are fully present in the linear
+			 * area before netc_rcv() calls dsa_strip_etype_header()
+			 * to strip the tag.
+			 */
+			if (unlikely(!pskb_may_pull(skb,
+						    NETC_TAG_TH_SUBTYPE1_LEN)))
+				goto err_free_skb;
+
+			tag = dsa_etype_header_pos_rx(skb);
+			netc_rx_tstamp_process(tag, skb);
+			break;
+		case NETC_TAG_TH_SUBTYPE2:
+			/* This skb is a hardware-generated response to a
+			 * two-step transmit timestamp request. The tag
+			 * driver must free the skb after processing.
+			 */
+			netc_twostep_tstamp_process(tag, skb);
+			consume_skb(skb);
+			return NULL;
+		default:
+			dev_warn_ratelimited(&ndev->dev,
+					     "Unsupported To_Host subtype: %d\n",
+					     subtype);
 			goto err_free_skb;
+		}
 	} else {
 		dev_warn_ratelimited(&ndev->dev,
-				     "Unexpected  tag type %d\n", type);
+				     "Unexpected tag type %d\n", type);
 		goto err_free_skb;
 	}
 
@@ -190,16 +359,49 @@ static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto,
 	int type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type);
 	int tag_len = netc_get_rx_tag_len(type, subtype);
 
-	/* The RX minimum frame length of the NETC switch port is 64 bytes,
-	 * and the frame is received by the ENETC driver. From the hardware
-	 * perspective, the receive buffer of RX BD is at least 128 bytes,
-	 * so the switch tag header is guaranteed to be in the linear region
-	 * of the skb.
+	/* The CPU port of the switch is connected to the ENETC, so the frame
+	 * is received by the ENETC driver. From the hardware perspective, the
+	 * receive buffer of RX BD is at least 128 bytes, so the switch tag
+	 * header is guaranteed to be in the linear region of the skb.
+	 *
+	 * When the subtype of the frame is NETC_TAG_TH_SUBTYPE2, it indicates
+	 * the frame is a hardware generated timestamp response, which is only
+	 * 26 bytes (DMAC + SMAC + tag), so the frame has no payload after the
+	 * tag. Therefore, there is no need to parse the protocol and offset.
+	 * For other types of the frames, they are all received from the switch
+	 * ports, and the RX minimum frame length of the port is 64 bytes,
+	 * frames shorter than 64 bytes will be discarded by the hardware and
+	 * will not be received by the software.
 	 */
+	if (type == NETC_TAG_TO_HOST && subtype == NETC_TAG_TH_SUBTYPE2)
+		return;
+
 	*offset = tag_len;
 	*proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
 }
 
+static int netc_connect(struct dsa_switch *ds)
+{
+	struct netc_tagger_data *tagger_data;
+
+	tagger_data = kzalloc_obj(*tagger_data);
+	if (!tagger_data)
+		return -ENOMEM;
+
+	tagger_data->onestep_sync_xmit = netc_onestep_sync_xmit;
+	ds->tagger_data = tagger_data;
+
+	return 0;
+}
+
+static void netc_disconnect(struct dsa_switch *ds)
+{
+	struct netc_tagger_data *tagger_data = ds->tagger_data;
+
+	kfree(tagger_data);
+	ds->tagger_data = NULL;
+}
+
 static const struct dsa_device_ops netc_netdev_ops = {
 	.name			= NETC_NAME,
 	.proto			= DSA_TAG_PROTO_NETC,
@@ -207,6 +409,8 @@ static const struct dsa_device_ops netc_netdev_ops = {
 	.rcv			= netc_rcv,
 	.needed_headroom	= NETC_TAG_MAX_LEN,
 	.flow_dissect		= netc_flow_dissect,
+	.connect		= netc_connect,
+	.disconnect		= netc_disconnect,
 };
 
 MODULE_DESCRIPTION("DSA tag driver for NXP NETC switch family");

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

Args:
null

Results:
null

Tokens:
							input: 29364
							output: 22
							thoughts: 573
						

Thoughts:
**My Analysis of the Patch Series for Syzkaller Fuzzing Suitability**

Okay, let's break this down. My initial assessment is whether this patch series is even *worth* fuzzing with syzkaller. First, I need to remind myself of the core triage philosophy. The most crucial factor is reachability. Syzkaller is only going to uncover bugs in code that is actually *exercisable* in standard virtualized environments. No point in flogging a dead horse. That means GCE or QEMU using software-emulated devices - USB gadgets, netdev, tun/tap, that sort of thing. If the modified code is structurally unreachable, it’s a hard "no" for fuzzing. We're not going to be able to catch bugs this way if the code *never* runs. Vendor-specific hardware requiring physical ASICs or PCI-e cards are out.

Now, looking at the files modified: `drivers/net/dsa/netc/Kconfig`, `Makefile`, `netc_main.c`, `netc_platform.c`, `netc_ptp.c`, `netc_switch.h`, `netc_switch_hw.h`, `ptp_netc.c`, `include/linux/dsa/tag_netc.h`, `include/linux/fsl/netc_global.h`, and `net/dsa/tag_netc.c`. Hold on a minute! That last file, `net/dsa/tag_netc.c`, that's a DSA tagging driver! And the `drivers/net/dsa/netc/` directory – that's the NXP NETC switch driver. The NETC switch is a PCI device used on the i.MX94 platform.

The question then becomes: Can DSA taggers and switches even *be* used without specific hardware? Can they be instantiated through user-space interactions or through virtual devices? Do we have a virtual DSA switch? This is a critical question. Can syzkaller even *reach* this code? Can we attach a DSA tag protocol to a virtual interface like a tap/tun or even a dummy interface? Does syzkaller even have coverage for DSA tagging drivers?

I need to dig deeper. I'm going to check how DSA tagging drivers function within the kernel, specifically focusing on how `tag_netc.c` works. I need to figure out if it's reachable in a fuzzable environment. I'll need to figure out where `tag_netc.c` is actually *configured*. A quick grep for `NET_DSA_TAG_NETC` seems like the obvious starting point. My focus is on determining whether this DSA tagger code is, in fact, structurally reachable within the confines of a fuzzable environment.



4/2 2026/09/20 10:36 tool grepper
0m Args:
{
  "Expression": "NET_DSA_TAG_NETC"
}

Results:
{
  "Output": "drivers/net/dsa/netc/Kconfig=2=config NET_DSA_NETC_SWITCH\n--\ndrivers/net/dsa/netc/Kconfig-8-\tdepends on PTP_NETC_V4_TIMER || PTP_NETC_V4_TIMER=n\ndrivers/net/dsa/netc/Kconfig:9:\tselect NET_DSA_TAG_NETC\ndrivers/net/dsa/netc/Kconfig-10-\tselect FSL_ENETC_MDIO\n--\ninclude/linux/dsa/tag_netc.h-5-\ninclude/linux/dsa/tag_netc.h:6:#ifndef __NET_DSA_TAG_NETC_H\ninclude/linux/dsa/tag_netc.h:7:#define __NET_DSA_TAG_NETC_H\ninclude/linux/dsa/tag_netc.h-8-\n--\nnet/dsa/Kconfig=134=config NET_DSA_TAG_KSZ\n--\nnet/dsa/Kconfig-139-\nnet/dsa/Kconfig:140:config NET_DSA_TAG_NETC\nnet/dsa/Kconfig-141-\ttristate \"Tag driver for NXP NETC switches\"\n--\nnet/dsa/Makefile=34=obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o\nnet/dsa/Makefile:35:obj-$(CONFIG_NET_DSA_TAG_NETC) += tag_netc.o\nnet/dsa/Makefile-36-obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 30982
							output: 28
							thoughts: 86
						

Thoughts:
**Investigating DSA Tagger Implementation**

Okay, so I'm trying to figure out something about these DSA taggers. Can they be used or configured independently, without needing the physical switch hardware itself? Does DSA *require* a switch tree and a switch driver to function, or is there a way to simulate or test DSA functionalities, perhaps through looping back or using Netlink?

The core question here is the dependency on physical hardware. I need to understand if the tagger functionality is tightly coupled to the switch, or if there's a more flexible, virtualized approach. Let's delve into the `net/dsa/` directory in the kernel source. I'm hoping to find clues about a virtual switch driver or some way taggers might be invoked using Netlink or a similar inter-process communication conduit.

Finally, I need to understand how DSA handles the fundamental processes of receiving and transmitting packets to get a better sense of the overall architecture. That should illuminate whether there's an abstraction layer I can exploit or simulate.



6/2 2026/09/20 10:36 tool grepper
0m Args:
{
  "Expression": "dsa_tree",
  "PathPrefix": "net/dsa"
}

Results:
{
  "Output": "net/dsa/conduit.c=350=static ssize_t tagging_store(struct device *d, struct device_attribute *attr,\n--\nnet/dsa/conduit.c-380-\nnet/dsa/conduit.c:381:\terr = dsa_tree_change_tag_proto(cpu_dp-\u003eds-\u003edst, new_tag_ops,\nnet/dsa/conduit.c-382-\t\t\t\t\told_tag_ops);\n--\nnet/dsa/dsa.c=35=static DEFINE_MUTEX(dsa2_mutex);\nnet/dsa/dsa.c:36:LIST_HEAD(dsa_tree_list);\nnet/dsa/dsa.c-37-\n--\nnet/dsa/dsa.c=93=void dsa_lag_unmap(struct dsa_switch_tree *dst, struct dsa_lag *lag)\n--\nnet/dsa/dsa.c-105-\nnet/dsa/dsa.c:106:struct dsa_lag *dsa_tree_lag_find(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-107-\t\t\t\t  const struct net_device *lag_dev)\n--\nnet/dsa/dsa.c-117-\nnet/dsa/dsa.c:118:struct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-119-\t\t\t\t\tconst struct net_device *br)\n--\nnet/dsa/dsa.c=130=static int dsa_bridge_num_find(const struct net_device *bridge_dev)\n--\nnet/dsa/dsa.c-133-\nnet/dsa/dsa.c:134:\tlist_for_each_entry(dst, \u0026dsa_tree_list, list) {\nnet/dsa/dsa.c-135-\t\tstruct dsa_bridge *bridge;\nnet/dsa/dsa.c-136-\nnet/dsa/dsa.c:137:\t\tbridge = dsa_tree_bridge_find(dst, bridge_dev);\nnet/dsa/dsa.c-138-\t\tif (bridge)\n--\nnet/dsa/dsa.c=181=struct dsa_switch *dsa_switch_find(int tree_index, int sw_index)\n--\nnet/dsa/dsa.c-185-\nnet/dsa/dsa.c:186:\tlist_for_each_entry(dst, \u0026dsa_tree_list, list) {\nnet/dsa/dsa.c-187-\t\tif (dst-\u003eindex != tree_index)\n--\nnet/dsa/dsa.c=200=EXPORT_SYMBOL_GPL(dsa_switch_find);\nnet/dsa/dsa.c-201-\nnet/dsa/dsa.c:202:static struct dsa_switch_tree *dsa_tree_find(int index)\nnet/dsa/dsa.c-203-{\n--\nnet/dsa/dsa.c-205-\nnet/dsa/dsa.c:206:\tlist_for_each_entry(dst, \u0026dsa_tree_list, list)\nnet/dsa/dsa.c-207-\t\tif (dst-\u003eindex == index)\n--\nnet/dsa/dsa.c-212-\nnet/dsa/dsa.c:213:static struct dsa_switch_tree *dsa_tree_alloc(int index)\nnet/dsa/dsa.c-214-{\n--\nnet/dsa/dsa.c-227-\tINIT_LIST_HEAD(\u0026dst-\u003elist);\nnet/dsa/dsa.c:228:\tlist_add_tail(\u0026dst-\u003elist, \u0026dsa_tree_list);\nnet/dsa/dsa.c-229-\n--\nnet/dsa/dsa.c-234-\nnet/dsa/dsa.c:235:static void dsa_tree_free(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-236-{\n--\nnet/dsa/dsa.c-242-\nnet/dsa/dsa.c:243:static struct dsa_switch_tree *dsa_tree_get(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-244-{\n--\nnet/dsa/dsa.c-250-\nnet/dsa/dsa.c:251:static struct dsa_switch_tree *dsa_tree_touch(int index)\nnet/dsa/dsa.c-252-{\n--\nnet/dsa/dsa.c-254-\nnet/dsa/dsa.c:255:\tdst = dsa_tree_find(index);\nnet/dsa/dsa.c-256-\tif (dst)\nnet/dsa/dsa.c:257:\t\treturn dsa_tree_get(dst);\nnet/dsa/dsa.c-258-\telse\nnet/dsa/dsa.c:259:\t\treturn dsa_tree_alloc(index);\nnet/dsa/dsa.c-260-}\nnet/dsa/dsa.c-261-\nnet/dsa/dsa.c:262:static void dsa_tree_release(struct kref *ref)\nnet/dsa/dsa.c-263-{\n--\nnet/dsa/dsa.c-267-\nnet/dsa/dsa.c:268:\tdsa_tree_free(dst);\nnet/dsa/dsa.c-269-}\nnet/dsa/dsa.c-270-\nnet/dsa/dsa.c:271:static void dsa_tree_put(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-272-{\nnet/dsa/dsa.c-273-\tif (dst)\nnet/dsa/dsa.c:274:\t\tkref_put(\u0026dst-\u003erefcount, dsa_tree_release);\nnet/dsa/dsa.c-275-}\nnet/dsa/dsa.c-276-\nnet/dsa/dsa.c:277:static struct dsa_port *dsa_tree_find_port_by_node(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-278-\t\t\t\t\t\t   struct device_node *dn)\n--\nnet/dsa/dsa.c=315=static bool dsa_port_setup_routing_table(struct dsa_port *dp)\n--\nnet/dsa/dsa.c-325-\tof_for_each_phandle(\u0026it, err, dn, \"link\", NULL, 0) {\nnet/dsa/dsa.c:326:\t\tlink_dp = dsa_tree_find_port_by_node(dst, it.node);\nnet/dsa/dsa.c-327-\t\tif (!link_dp) {\n--\nnet/dsa/dsa.c-341-\nnet/dsa/dsa.c:342:static bool dsa_tree_setup_routing_table(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-343-{\n--\nnet/dsa/dsa.c-357-\nnet/dsa/dsa.c:358:static struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-359-{\n--\nnet/dsa/dsa.c-368-\nnet/dsa/dsa.c:369:struct net_device *dsa_tree_find_first_conduit(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-370-{\n--\nnet/dsa/dsa.c-372-\nnet/dsa/dsa.c:373:\tcpu_dp = dsa_tree_find_first_cpu(dst);\nnet/dsa/dsa.c-374-\treturn cpu_dp-\u003econduit;\n--\nnet/dsa/dsa.c-379- */\nnet/dsa/dsa.c:380:static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-381-{\n--\nnet/dsa/dsa.c-383-\nnet/dsa/dsa.c:384:\tcpu_dp = dsa_tree_find_first_cpu(dst);\nnet/dsa/dsa.c-385-\tif (!cpu_dp) {\n--\nnet/dsa/dsa.c=402=dsa_switch_preferred_default_local_cpu_port(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-423- */\nnet/dsa/dsa.c:424:static int dsa_tree_setup_cpu_ports(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-425-{\n--\nnet/dsa/dsa.c-446-\nnet/dsa/dsa.c:447:\treturn dsa_tree_setup_default_cpu(dst);\nnet/dsa/dsa.c-448-}\nnet/dsa/dsa.c-449-\nnet/dsa/dsa.c:450:static void dsa_tree_teardown_cpu_ports(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-451-{\n--\nnet/dsa/dsa.c=686=static void dsa_switch_teardown(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-714- */\nnet/dsa/dsa.c:715:static void dsa_tree_teardown_ports(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-716-{\n--\nnet/dsa/dsa.c-729-\nnet/dsa/dsa.c:730:static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-731-{\n--\nnet/dsa/dsa.c-738-/* Bring shared ports up first, then non-shared ports */\nnet/dsa/dsa.c:739:static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-740-{\n--\nnet/dsa/dsa.c-765-teardown:\nnet/dsa/dsa.c:766:\tdsa_tree_teardown_ports(dst);\nnet/dsa/dsa.c-767-\n--\nnet/dsa/dsa.c-770-\nnet/dsa/dsa.c:771:static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-772-{\n--\nnet/dsa/dsa.c-778-\t\tif (err) {\nnet/dsa/dsa.c:779:\t\t\tdsa_tree_teardown_switches(dst);\nnet/dsa/dsa.c-780-\t\t\tbreak;\n--\nnet/dsa/dsa.c-786-\nnet/dsa/dsa.c:787:static int dsa_tree_setup_conduit(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-788-{\n--\nnet/dsa/dsa.c-793-\nnet/dsa/dsa.c:794:\tdsa_tree_for_each_cpu_port(cpu_dp, dst) {\nnet/dsa/dsa.c-795-\t\tstruct net_device *conduit = cpu_dp-\u003econduit;\n--\nnet/dsa/dsa.c-803-\t\t/* Replay conduit state event */\nnet/dsa/dsa.c:804:\t\tdsa_tree_conduit_admin_state_change(dst, conduit, admin_up);\nnet/dsa/dsa.c:805:\t\tdsa_tree_conduit_oper_state_change(dst, conduit,\nnet/dsa/dsa.c-806-\t\t\t\t\t\t   netif_oper_up(conduit));\n--\nnet/dsa/dsa.c-813-\nnet/dsa/dsa.c:814:static void dsa_tree_teardown_conduit(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-815-{\n--\nnet/dsa/dsa.c-819-\nnet/dsa/dsa.c:820:\tdsa_tree_for_each_cpu_port(cpu_dp, dst) {\nnet/dsa/dsa.c-821-\t\tstruct net_device *conduit = cpu_dp-\u003econduit;\n--\nnet/dsa/dsa.c-826-\t\t */\nnet/dsa/dsa.c:827:\t\tdsa_tree_conduit_admin_state_change(dst, conduit, false);\nnet/dsa/dsa.c-828-\n--\nnet/dsa/dsa.c-834-\nnet/dsa/dsa.c:835:static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-836-{\n--\nnet/dsa/dsa.c-855-\nnet/dsa/dsa.c:856:static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-857-{\n--\nnet/dsa/dsa.c-860-\nnet/dsa/dsa.c:861:static void dsa_tree_teardown_routing_table(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-862-{\n--\nnet/dsa/dsa.c-870-\nnet/dsa/dsa.c:871:static int dsa_tree_setup(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-872-{\n--\nnet/dsa/dsa.c-881-\nnet/dsa/dsa.c:882:\tcomplete = dsa_tree_setup_routing_table(dst);\nnet/dsa/dsa.c-883-\tif (!complete)\n--\nnet/dsa/dsa.c-885-\nnet/dsa/dsa.c:886:\terr = dsa_tree_setup_cpu_ports(dst);\nnet/dsa/dsa.c-887-\tif (err)\n--\nnet/dsa/dsa.c-889-\nnet/dsa/dsa.c:890:\terr = dsa_tree_setup_switches(dst);\nnet/dsa/dsa.c-891-\tif (err)\n--\nnet/dsa/dsa.c-893-\nnet/dsa/dsa.c:894:\terr = dsa_tree_setup_ports(dst);\nnet/dsa/dsa.c-895-\tif (err)\n--\nnet/dsa/dsa.c-897-\nnet/dsa/dsa.c:898:\terr = dsa_tree_setup_conduit(dst);\nnet/dsa/dsa.c-899-\tif (err)\n--\nnet/dsa/dsa.c-901-\nnet/dsa/dsa.c:902:\terr = dsa_tree_setup_lags(dst);\nnet/dsa/dsa.c-903-\tif (err)\n--\nnet/dsa/dsa.c-912-teardown_conduit:\nnet/dsa/dsa.c:913:\tdsa_tree_teardown_conduit(dst);\nnet/dsa/dsa.c-914-teardown_ports:\nnet/dsa/dsa.c:915:\tdsa_tree_teardown_ports(dst);\nnet/dsa/dsa.c-916-teardown_switches:\nnet/dsa/dsa.c:917:\tdsa_tree_teardown_switches(dst);\nnet/dsa/dsa.c-918-teardown_cpu_ports:\nnet/dsa/dsa.c:919:\tdsa_tree_teardown_cpu_ports(dst);\nnet/dsa/dsa.c-920-teardown_rtable:\nnet/dsa/dsa.c:921:\tdsa_tree_teardown_routing_table(dst);\nnet/dsa/dsa.c-922-\n--\nnet/dsa/dsa.c-925-\nnet/dsa/dsa.c:926:static void dsa_tree_teardown(struct dsa_switch_tree *dst)\nnet/dsa/dsa.c-927-{\n--\nnet/dsa/dsa.c-930-\nnet/dsa/dsa.c:931:\tdsa_tree_teardown_lags(dst);\nnet/dsa/dsa.c-932-\nnet/dsa/dsa.c:933:\tdsa_tree_teardown_conduit(dst);\nnet/dsa/dsa.c-934-\nnet/dsa/dsa.c:935:\tdsa_tree_teardown_ports(dst);\nnet/dsa/dsa.c-936-\nnet/dsa/dsa.c:937:\tdsa_tree_teardown_switches(dst);\nnet/dsa/dsa.c-938-\nnet/dsa/dsa.c:939:\tdsa_tree_teardown_cpu_ports(dst);\nnet/dsa/dsa.c-940-\nnet/dsa/dsa.c:941:\tdsa_tree_teardown_routing_table(dst);\nnet/dsa/dsa.c-942-\n--\nnet/dsa/dsa.c-947-\nnet/dsa/dsa.c:948:static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-949-\t\t\t\t   const struct dsa_device_ops *tag_ops)\n--\nnet/dsa/dsa.c-960-\tinfo.tag_ops = tag_ops;\nnet/dsa/dsa.c:961:\terr = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, \u0026info);\nnet/dsa/dsa.c-962-\tif (err \u0026\u0026 err != -EOPNOTSUPP)\n--\nnet/dsa/dsa.c-966-\tinfo.tag_ops = old_tag_ops;\nnet/dsa/dsa.c:967:\tdsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, \u0026info);\nnet/dsa/dsa.c-968-\n--\nnet/dsa/dsa.c-972-\tinfo.tag_ops = tag_ops;\nnet/dsa/dsa.c:973:\tdsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, \u0026info);\nnet/dsa/dsa.c-974-\tdst-\u003etag_ops = old_tag_ops;\n--\nnet/dsa/dsa.c-982- */\nnet/dsa/dsa.c:983:int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-984-\t\t\t      const struct dsa_device_ops *tag_ops,\n--\nnet/dsa/dsa.c-998-\t */\nnet/dsa/dsa.c:999:\tdsa_tree_for_each_user_port(dp, dst) {\nnet/dsa/dsa.c-1000-\t\tif (dsa_port_to_conduit(dp)-\u003eflags \u0026 IFF_UP)\n--\nnet/dsa/dsa.c-1008-\tinfo.tag_ops = tag_ops;\nnet/dsa/dsa.c:1009:\terr = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, \u0026info);\nnet/dsa/dsa.c-1010-\tif (err)\n--\nnet/dsa/dsa.c-1012-\nnet/dsa/dsa.c:1013:\terr = dsa_tree_bind_tag_proto(dst, tag_ops);\nnet/dsa/dsa.c-1014-\tif (err)\n--\nnet/dsa/dsa.c-1022-\tinfo.tag_ops = old_tag_ops;\nnet/dsa/dsa.c:1023:\tdsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, \u0026info);\nnet/dsa/dsa.c-1024-out_unlock:\n--\nnet/dsa/dsa.c-1028-\nnet/dsa/dsa.c:1029:static void dsa_tree_conduit_state_change(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-1030-\t\t\t\t\t  struct net_device *conduit)\n--\nnet/dsa/dsa.c-1037-\nnet/dsa/dsa.c:1038:\tdsa_tree_notify(dst, DSA_NOTIFIER_CONDUIT_STATE_CHANGE, \u0026info);\nnet/dsa/dsa.c-1039-}\nnet/dsa/dsa.c-1040-\nnet/dsa/dsa.c:1041:void dsa_tree_conduit_admin_state_change(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-1042-\t\t\t\t\t struct net_device *conduit,\n--\nnet/dsa/dsa.c-1060-\tif (notify)\nnet/dsa/dsa.c:1061:\t\tdsa_tree_conduit_state_change(dst, conduit);\nnet/dsa/dsa.c-1062-}\nnet/dsa/dsa.c-1063-\nnet/dsa/dsa.c:1064:void dsa_tree_conduit_oper_state_change(struct dsa_switch_tree *dst,\nnet/dsa/dsa.c-1065-\t\t\t\t\tstruct net_device *conduit,\n--\nnet/dsa/dsa.c-1083-\tif (notify)\nnet/dsa/dsa.c:1084:\t\tdsa_tree_conduit_state_change(dst, conduit);\nnet/dsa/dsa.c-1085-}\n--\nnet/dsa/dsa.c=1325=static int dsa_switch_parse_member_of(struct dsa_switch *ds,\n--\nnet/dsa/dsa.c-1337-\nnet/dsa/dsa.c:1338:\tds-\u003edst = dsa_tree_touch(m[0]);\nnet/dsa/dsa.c-1339-\tif (!ds-\u003edst)\n--\nnet/dsa/dsa.c=1465=static int dsa_switch_parse(struct dsa_switch *ds, struct dsa_chip_data *cd)\n--\nnet/dsa/dsa.c-1474-\tds-\u003eindex = 0;\nnet/dsa/dsa.c:1475:\tds-\u003edst = dsa_tree_touch(0);\nnet/dsa/dsa.c-1476-\tif (!ds-\u003edst)\n--\nnet/dsa/dsa.c=1534=static int dsa_switch_probe(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1565-\tdst = ds-\u003edst;\nnet/dsa/dsa.c:1566:\tdsa_tree_get(dst);\nnet/dsa/dsa.c:1567:\terr = dsa_tree_setup(dst);\nnet/dsa/dsa.c-1568-\tif (err) {\nnet/dsa/dsa.c-1569-\t\tdsa_switch_release_ports(ds);\nnet/dsa/dsa.c:1570:\t\tdsa_tree_put(dst);\nnet/dsa/dsa.c-1571-\t}\n--\nnet/dsa/dsa.c=1576=int dsa_register_switch(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1581-\terr = dsa_switch_probe(ds);\nnet/dsa/dsa.c:1582:\tdsa_tree_put(ds-\u003edst);\nnet/dsa/dsa.c-1583-\tmutex_unlock(\u0026dsa2_mutex);\n--\nnet/dsa/dsa.c=1589=static void dsa_switch_remove(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1592-\nnet/dsa/dsa.c:1593:\tdsa_tree_teardown(dst);\nnet/dsa/dsa.c-1594-\tdsa_switch_release_ports(ds);\nnet/dsa/dsa.c:1595:\tdsa_tree_put(dst);\nnet/dsa/dsa.c-1596-}\n--\nnet/dsa/dsa.h=14=struct work_struct;\nnet/dsa/dsa.h-15-\nnet/dsa/dsa.h:16:extern struct list_head dsa_tree_list;\nnet/dsa/dsa.h-17-\n--\nnet/dsa/dsa.h=21=void dsa_lag_unmap(struct dsa_switch_tree *dst, struct dsa_lag *lag);\nnet/dsa/dsa.h:22:struct dsa_lag *dsa_tree_lag_find(struct dsa_switch_tree *dst,\nnet/dsa/dsa.h-23-\t\t\t\t  const struct net_device *lag_dev);\nnet/dsa/dsa.h:24:struct net_device *dsa_tree_find_first_conduit(struct dsa_switch_tree *dst);\nnet/dsa/dsa.h:25:int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,\nnet/dsa/dsa.h-26-\t\t\t      const struct dsa_device_ops *tag_ops,\nnet/dsa/dsa.h-27-\t\t\t      const struct dsa_device_ops *old_tag_ops);\nnet/dsa/dsa.h:28:void dsa_tree_conduit_admin_state_change(struct dsa_switch_tree *dst,\nnet/dsa/dsa.h-29-\t\t\t\t\t struct net_device *conduit,\nnet/dsa/dsa.h-30-\t\t\t\t\t bool up);\nnet/dsa/dsa.h:31:void dsa_tree_conduit_oper_state_change(struct dsa_switch_tree *dst,\nnet/dsa/dsa.h-32-\t\t\t\t\tstruct net_device *conduit,\n--\nnet/dsa/dsa.h=35=void dsa_bridge_num_put(const struct net_device *bridge_dev,\nnet/dsa/dsa.h-36-\t\t\tunsigned int bridge_num);\nnet/dsa/dsa.h:37:struct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst,\nnet/dsa/dsa.h-38-\t\t\t\t\tconst struct net_device *br);\n--\nnet/dsa/port.c=33=static int dsa_port_notify(const struct dsa_port *dp, unsigned long e, void *v)\nnet/dsa/port.c-34-{\nnet/dsa/port.c:35:\treturn dsa_tree_notify(dp-\u003eds-\u003edst, e, v);\nnet/dsa/port.c-36-}\n--\nnet/dsa/port.c=420=static int dsa_port_bridge_create(struct dsa_port *dp,\n--\nnet/dsa/port.c-426-\nnet/dsa/port.c:427:\tbridge = dsa_tree_bridge_find(ds-\u003edst, br);\nnet/dsa/port.c-428-\tif (bridge) {\n--\nnet/dsa/port.c=607=static int dsa_port_lag_create(struct dsa_port *dp,\n--\nnet/dsa/port.c-612-\nnet/dsa/port.c:613:\tlag = dsa_tree_lag_find(ds-\u003edst, lag_dev);\nnet/dsa/port.c-614-\tif (lag) {\n--\nnet/dsa/switch.c=873=static int dsa_switch_change_tag_proto(struct dsa_switch *ds,\n--\nnet/dsa/switch.c-900-\nnet/dsa/switch.c:901:\t\t/* rtnl_mutex is held in dsa_tree_change_tag_proto */\nnet/dsa/switch.c-902-\t\tdsa_user_change_mtu(user, user-\u003emtu);\n--\nnet/dsa/switch.c=974=static int dsa_switch_event(struct notifier_block *nb,\n--\nnet/dsa/switch.c-1074-/**\nnet/dsa/switch.c:1075: * dsa_tree_notify - Execute code for all switches in a DSA switch tree.\nnet/dsa/switch.c-1076- * @dst: collection of struct dsa_switch devices to notify.\n--\nnet/dsa/switch.c-1083- */\nnet/dsa/switch.c:1084:int dsa_tree_notify(struct dsa_switch_tree *dst, unsigned long e, void *v)\nnet/dsa/switch.c-1085-{\n--\nnet/dsa/switch.c=1106=int dsa_broadcast(unsigned long e, void *v)\n--\nnet/dsa/switch.c-1110-\nnet/dsa/switch.c:1111:\tlist_for_each_entry(dst, \u0026dsa_tree_list, list) {\nnet/dsa/switch.c:1112:\t\terr = dsa_tree_notify(dst, e, v);\nnet/dsa/switch.c-1113-\t\tif (err)\n--\nnet/dsa/switch.h=114=struct dsa_vlan *dsa_vlan_find(struct list_head *vlan_list,\n--\nnet/dsa/switch.h-116-\nnet/dsa/switch.h:117:int dsa_tree_notify(struct dsa_switch_tree *dst, unsigned long e, void *v);\nnet/dsa/switch.h-118-int dsa_broadcast(unsigned long e, void *v);\n--\nnet/dsa/tag_8021q.c=475=dsa_tag_8021q_find_port_by_vbid(struct net_device *conduit, int vbid)\n--\nnet/dsa/tag_8021q.c-483-\nnet/dsa/tag_8021q.c:484:\tdsa_tree_for_each_user_port(dp, dst) {\nnet/dsa/tag_8021q.c-485-\t\tif (!dp-\u003ebridge)\n--\nnet/dsa/user.c=2035=static void dsa_bridge_mtu_normalization(struct dsa_port *dp)\n--\nnet/dsa/user.c-2053-\t */\nnet/dsa/user.c:2054:\tlist_for_each_entry(dst, \u0026dsa_tree_list, list) {\nnet/dsa/user.c-2055-\t\tlist_for_each_entry(other_dp, \u0026dst-\u003eports, list) {\n--\nnet/dsa/user.c=2102=int dsa_user_change_mtu(struct net_device *dev, int new_mtu)\n--\nnet/dsa/user.c-2119-\nnet/dsa/user.c:2120:\tdsa_tree_for_each_user_port(other_dp, ds-\u003edst) {\nnet/dsa/user.c-2121-\t\tint user_mtu;\n--\nnet/dsa/user.c=3317=dsa_bridge_prechangelower_sanity_check(struct net_device *new_lower,\n--\nnet/dsa/user.c-3346-\nnet/dsa/user.c:3347:static void dsa_tree_migrate_ports_from_lag_conduit(struct dsa_switch_tree *dst,\nnet/dsa/user.c-3348-\t\t\t\t\t\t    struct net_device *lag_dev)\nnet/dsa/user.c-3349-{\nnet/dsa/user.c:3350:\tstruct net_device *new_conduit = dsa_tree_find_first_conduit(dst);\nnet/dsa/user.c-3351-\tstruct dsa_port *dp;\n--\nnet/dsa/user.c-3353-\nnet/dsa/user.c:3354:\tdsa_tree_for_each_user_port(dp, dst) {\nnet/dsa/user.c-3355-\t\tif (dsa_port_to_conduit(dp) != lag_dev)\n--\nnet/dsa/user.c=3367=static int dsa_conduit_lag_join(struct net_device *conduit,\n--\nnet/dsa/user.c-3380-\nnet/dsa/user.c:3381:\tdsa_tree_for_each_user_port(dp, dst) {\nnet/dsa/user.c-3382-\t\tif (dsa_port_to_conduit(dp) != conduit)\n--\nnet/dsa/user.c-3392-restore:\nnet/dsa/user.c:3393:\tdsa_tree_for_each_user_port_continue_reverse(dp, dst) {\nnet/dsa/user.c-3394-\t\tif (dsa_port_to_conduit(dp) != lag_dev)\n--\nnet/dsa/user.c=3410=static void dsa_conduit_lag_leave(struct net_device *conduit,\n--\nnet/dsa/user.c-3429-\t\t */\nnet/dsa/user.c:3430:\t\tdsa_tree_for_each_user_port(dp, dst)\nnet/dsa/user.c-3431-\t\t\tif (dsa_port_to_conduit(dp) == lag_dev)\n--\nnet/dsa/user.c-3442-\t\t */\nnet/dsa/user.c:3443:\t\tdsa_tree_migrate_ports_from_lag_conduit(dst, lag_dev);\nnet/dsa/user.c-3444-\t}\n--\nnet/dsa/user.c=3477=static int dsa_user_netdevice_event(struct notifier_block *nb,\n--\nnet/dsa/user.c-3562-\t\t\t/* Track when the conduit port is UP */\nnet/dsa/user.c:3563:\t\t\tdsa_tree_conduit_oper_state_change(dst, dev,\nnet/dsa/user.c-3564-\t\t\t\t\t\t\t   netif_oper_up(dev));\n--\nnet/dsa/user.c-3573-\t\t\t */\nnet/dsa/user.c:3574:\t\t\tdsa_tree_conduit_admin_state_change(dst, dev,\nnet/dsa/user.c-3575-\t\t\t\t\t\t\t    !qdisc_tx_is_noop(dev));\n--\nnet/dsa/user.c-3592-\nnet/dsa/user.c:3593:\t\tdsa_tree_conduit_admin_state_change(dst, dev, false);\nnet/dsa/user.c-3594-\n--\nnet/dsa/user.c=3692=static bool dsa_foreign_dev_check(const struct net_device *dev,\n--\nnet/dsa/user.c-3698-\tif (netif_is_bridge_master(foreign_dev))\nnet/dsa/user.c:3699:\t\treturn !dsa_tree_offloads_bridge_dev(dst, foreign_dev);\nnet/dsa/user.c-3700-\nnet/dsa/user.c-3701-\tif (netif_is_bridge_port(foreign_dev))\nnet/dsa/user.c:3702:\t\treturn !dsa_tree_offloads_bridge_port(dst, foreign_dev);\nnet/dsa/user.c-3703-\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 41122
							output: 22
							thoughts: 69
						

Thoughts:
**My DSA Musings**

Okay, let's break this down. Where does DSA fit into the grand scheme of things? First, a switch driver has to actually *register* itself with DSA, which we do with the `dsa_register_switch` function, of course.

Now, I'm curious. What existing drivers are currently leveraging `DSA_TAG_PROTO_NETC`? Time to go fishing in the code base! I'll do a quick search and see what pops up. Gotta stay sharp and informed. Let's start with a search for `DSA_TAG_PROTO_NETC`.



8/2 2026/09/20 10:36 tool grepper
0m Args:
{
  "Expression": "DSA_TAG_PROTO_NETC"
}

Results:
{
  "Output": "drivers/net/dsa/netc/netc_main.c=62=netc_get_tag_protocol(struct dsa_switch *ds, int port,\n--\ndrivers/net/dsa/netc/netc_main.c-64-{\ndrivers/net/dsa/netc/netc_main.c:65:\treturn DSA_TAG_PROTO_NETC;\ndrivers/net/dsa/netc/netc_main.c-66-}\n--\ndrivers/net/dsa/netc/netc_main.c=68=static int netc_connect_tag_protocol(struct dsa_switch *ds,\n--\ndrivers/net/dsa/netc/netc_main.c-72-\ndrivers/net/dsa/netc/netc_main.c:73:\tif (proto != DSA_TAG_PROTO_NETC)\ndrivers/net/dsa/netc/netc_main.c-74-\t\treturn -EPROTONOSUPPORT;\n--\ninclude/net/dsa.h=26=struct tc_action;\n--\ninclude/net/dsa.h-60-#define DSA_TAG_PROTO_MXL862_VALUE\t\t32\ninclude/net/dsa.h:61:#define DSA_TAG_PROTO_NETC_VALUE\t\t33\ninclude/net/dsa.h-62-#define DSA_TAG_PROTO_KSZ8463_VALUE\t\t34\n--\ninclude/net/dsa.h=66=enum dsa_tag_protocol {\n--\ninclude/net/dsa.h-99-\tDSA_TAG_PROTO_MXL862\t\t= DSA_TAG_PROTO_MXL862_VALUE,\ninclude/net/dsa.h:100:\tDSA_TAG_PROTO_NETC\t\t= DSA_TAG_PROTO_NETC_VALUE,\ninclude/net/dsa.h-101-\tDSA_TAG_PROTO_KSZ8463\t\t= DSA_TAG_PROTO_KSZ8463_VALUE,\n--\nnet/dsa/tag_netc.c=405=static const struct dsa_device_ops netc_netdev_ops = {\nnet/dsa/tag_netc.c-406-\t.name\t\t\t= NETC_NAME,\nnet/dsa/tag_netc.c:407:\t.proto\t\t\t= DSA_TAG_PROTO_NETC,\nnet/dsa/tag_netc.c-408-\t.xmit\t\t\t= netc_xmit,\n--\nnet/dsa/tag_netc.c=417=MODULE_LICENSE(\"GPL\");\nnet/dsa/tag_netc.c-418-\nnet/dsa/tag_netc.c:419:MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_NETC, NETC_NAME);\nnet/dsa/tag_netc.c-420-module_dsa_tag_driver(netc_netdev_ops);\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 42102
							output: 37
							thoughts: 117
						

Thoughts:
**Analyzing the NETC Tagging Protocol**

Okay, so I'm looking at `tag_netc.c`, specifically how it handles the tagging for NXP NETC switches, which use the `DSA_TAG_PROTO_NETC` protocol. Hmmm, a quick thought: could any DSA conduit theoretically switch its tagging protocol to `DSA_TAG_PROTO_NETC`? That seems like something I need to investigate.

I need to figure out where the tagger protocol can be modified. I'll need to check the `dsa_tree_change_tag_proto` function, the sysfs interface through `tagging_store`, or potentially even ethtool, or any other method that could influence the tagger. My next step is to head over to `net/dsa/conduit.c` and take a look around line 350. That should give me some solid leads.



10/2 2026/09/20 10:36 tool read-file
0m Args:
{
  "File": "net/dsa/conduit.c",
  "FirstLine": 345,
  "LineCount": 50
}

Results:
{
  "Contents": " 345:\t\n 346:\t\treturn sysfs_emit(buf, \"%s\\n\",\n 347:\t\t\t       dsa_tag_protocol_to_str(cpu_dp-\u003etag_ops));\n 348:\t}\n 349:\t\n 350:\tstatic ssize_t tagging_store(struct device *d, struct device_attribute *attr,\n 351:\t\t\t\t     const char *buf, size_t count)\n 352:\t{\n 353:\t\tconst struct dsa_device_ops *new_tag_ops, *old_tag_ops;\n 354:\t\tconst char *end = strchrnul(buf, '\\n'), *name;\n 355:\t\tstruct net_device *dev = to_net_dev(d);\n 356:\t\tstruct dsa_port *cpu_dp = dev-\u003edsa_ptr;\n 357:\t\tsize_t len = end - buf;\n 358:\t\tint err;\n 359:\t\n 360:\t\t/* Empty string passed */\n 361:\t\tif (!len)\n 362:\t\t\treturn -ENOPROTOOPT;\n 363:\t\n 364:\t\tname = kstrndup(buf, len, GFP_KERNEL);\n 365:\t\tif (!name)\n 366:\t\t\treturn -ENOMEM;\n 367:\t\n 368:\t\told_tag_ops = cpu_dp-\u003etag_ops;\n 369:\t\tnew_tag_ops = dsa_tag_driver_get_by_name(name);\n 370:\t\tkfree(name);\n 371:\t\t/* Bad tagger name? */\n 372:\t\tif (IS_ERR(new_tag_ops))\n 373:\t\t\treturn PTR_ERR(new_tag_ops);\n 374:\t\n 375:\t\tif (new_tag_ops == old_tag_ops)\n 376:\t\t\t/* Drop the temporarily held duplicate reference, since\n 377:\t\t\t * the DSA switch tree uses this tagger.\n 378:\t\t\t */\n 379:\t\t\tgoto out;\n 380:\t\n 381:\t\terr = dsa_tree_change_tag_proto(cpu_dp-\u003eds-\u003edst, new_tag_ops,\n 382:\t\t\t\t\t\told_tag_ops);\n 383:\t\tif (err) {\n 384:\t\t\t/* On failure the old tagger is restored, so we don't need the\n 385:\t\t\t * driver for the new one.\n 386:\t\t\t */\n 387:\t\t\tdsa_tag_driver_put(new_tag_ops);\n 388:\t\t\treturn err;\n 389:\t\t}\n 390:\t\n 391:\t\t/* On success we no longer need the module for the old tagging protocol\n 392:\t\t */\n 393:\tout:\n 394:\t\tdsa_tag_driver_put(old_tag_ops);\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 43226
							output: 20
							thoughts: 103
						

Thoughts:
**Investigating DSA Switch Presence in Virtual Environments**

Okay, hold on a second! Let me retrace my steps. The code snippet I'm looking at, specifically the `tagging_store` function, is grabbing `cpu_dp` from the `dev->dsa_ptr`. Right, `dev` represents the conduit device – but this is for an *already probed* DSA switch tree. My immediate question is, do standard QEMU or GCE environments even *have* DSA switches that would be probed in the first place? It's crucial I ascertain if this scenario is even remotely applicable to these virtual setups.

Therefore, the next logical step is to determine if any virtual DSA switches are known to the kernel, or if QEMU provides emulated DSA switches. I need to comprehensively search the kernel source, focusing on `drivers/` to locate any instances of `dsa_register_switch`. That function is the key to identifying the registration of DSA switches, either real or virtual, which will give me the answer I seek.



12/2 2026/09/20 10:36 tool grepper
0m Args:
{
  "Expression": "dsa_register_switch"
}

Results:
{
  "Output": "Documentation/networking/dsa/dsa.rst=529=Switch registration from the perspective of a driver means passing a valid\nDocumentation/networking/dsa/dsa.rst:530:``struct dsa_switch`` pointer to ``dsa_register_switch()``, usually from the\nDocumentation/networking/dsa/dsa.rst-531-switch driver's probing function. The following members must be valid in the\n--\nDocumentation/networking/dsa/dsa.rst=572=In case of a tree with multiple switches, probing takes place asymmetrically.\nDocumentation/networking/dsa/dsa.rst:573:The first N-1 callers of ``dsa_register_switch()`` only add their ports to the\nDocumentation/networking/dsa/dsa.rst-574-port list of the tree (``dst-\u003eports``), each port having a backpointer to its\nDocumentation/networking/dsa/dsa.rst=575=associated switch (``dp-\u003eds``). Then, these switches exit their\nDocumentation/networking/dsa/dsa.rst:576:``dsa_register_switch()`` call early, because ``dsa_tree_setup_routing_table()``\nDocumentation/networking/dsa/dsa.rst-577-has determined that the tree is not yet complete (not all ports referenced by\nDocumentation/networking/dsa/dsa.rst=578=DSA links are present in the tree's port list). The tree becomes complete when\nDocumentation/networking/dsa/dsa.rst:579:the last switch calls ``dsa_register_switch()``, and this triggers the effective\nDocumentation/networking/dsa/dsa.rst-580-continuation of initialization (including the call to ``ds-\u003eops-\u003esetup()``) for\n--\ndrivers/net/dsa/b53/b53_common.c=3308=int b53_switch_register(struct b53_device *dev)\n--\ndrivers/net/dsa/b53/b53_common.c-3326-\ndrivers/net/dsa/b53/b53_common.c:3327:\treturn dsa_register_switch(dev-\u003eds);\ndrivers/net/dsa/b53/b53_common.c-3328-}\n--\ndrivers/net/dsa/dsa_loop.c=57=struct dsa_loop_pdata {\ndrivers/net/dsa/dsa_loop.c:58:\t/* Must be first, such that dsa_register_switch() can access this\ndrivers/net/dsa/dsa_loop.c-59-\t * without gory pointer manipulations\n--\ndrivers/net/dsa/dsa_loop.c=355=static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/dsa_loop.c-388-\ndrivers/net/dsa/dsa_loop.c:389:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/dsa_loop.c-390-\tif (!ret)\n--\ndrivers/net/dsa/hirschmann/hellcreek.c=1933=static int hellcreek_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/hirschmann/hellcreek.c-2026-\ndrivers/net/dsa/hirschmann/hellcreek.c:2027:\tret = dsa_register_switch(hellcreek-\u003eds);\ndrivers/net/dsa/hirschmann/hellcreek.c-2028-\tif (ret) {\n--\ndrivers/net/dsa/lan9303-core.c=1404=static int lan9303_register_switch(struct lan9303 *chip)\n--\ndrivers/net/dsa/lan9303-core.c-1416-\ndrivers/net/dsa/lan9303-core.c:1417:\treturn dsa_register_switch(chip-\u003eds);\ndrivers/net/dsa/lan9303-core.c-1418-}\n--\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c=1679=int gswip_probe_common(struct gswip_priv *priv, u32 version)\n--\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-1704-\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c:1705:\terr = dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/lantiq/lantiq_gswip_common.c-1706-\tif (err)\n--\ndrivers/net/dsa/microchip/ksz_common.c=3966=int ksz_switch_register(struct ksz_device *dev)\n--\ndrivers/net/dsa/microchip/ksz_common.c-4123-\ndrivers/net/dsa/microchip/ksz_common.c:4124:\tret = dsa_register_switch(dev-\u003eds);\ndrivers/net/dsa/microchip/ksz_common.c-4125-\tif (ret)\n--\ndrivers/net/dsa/motorcomm/chip.c=4724=static int yt921x_mdio_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/motorcomm/chip.c-4768-\ndrivers/net/dsa/motorcomm/chip.c:4769:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/motorcomm/chip.c-4770-}\n--\ndrivers/net/dsa/mt7530-mdio.c=159=mt7530_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mt7530-mdio.c-223-\ndrivers/net/dsa/mt7530-mdio.c:224:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/mt7530-mdio.c-225-}\n--\ndrivers/net/dsa/mt7530-mmio.c=30=mt7988_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/mt7530-mmio.c-63-\ndrivers/net/dsa/mt7530-mmio.c:64:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/mt7530-mmio.c-65-}\n--\ndrivers/net/dsa/mt7628.c=565=static int mt7628_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/mt7628.c-606-\ndrivers/net/dsa/mt7628.c:607:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mt7628.c-608-}\n--\ndrivers/net/dsa/mv88e6060.c=304=static int mv88e6060_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mv88e6060.c-335-\ndrivers/net/dsa/mv88e6060.c:336:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mv88e6060.c-337-}\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=3988=static int mv88e6xxx_setup(struct dsa_switch *ds)\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-4003-\t * depends on the physical switch topology. We need to let DSA figure\ndrivers/net/dsa/mv88e6xxx/chip.c:4004:\t * that out and therefore we cannot set this at dsa_register_switch()\ndrivers/net/dsa/mv88e6xxx/chip.c-4005-\t * time.\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=7259=static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-7284-\ndrivers/net/dsa/mv88e6xxx/chip.c:7285:\treturn dsa_register_switch(ds);\ndrivers/net/dsa/mv88e6xxx/chip.c-7286-}\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c=2102=static int mxl862xx_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2141-\ndrivers/net/dsa/mxl862xx/mxl862xx.c:2142:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/mxl862xx/mxl862xx.c-2143-\tif (err) {\n--\ndrivers/net/dsa/netc/netc_main.c=2508=static int netc_switch_probe(struct pci_dev *pdev,\n--\ndrivers/net/dsa/netc/netc_main.c-2551-\ndrivers/net/dsa/netc/netc_main.c:2552:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/netc/netc_main.c-2553-\tif (err)\n--\ndrivers/net/dsa/ocelot/felix.c=2390=int felix_register_switch(struct device *dev, resource_size_t switch_base,\n--\ndrivers/net/dsa/ocelot/felix.c-2428-\ndrivers/net/dsa/ocelot/felix.c:2429:\terr = dsa_register_switch(ds);\ndrivers/net/dsa/ocelot/felix.c-2430-\tif (err)\n--\ndrivers/net/dsa/qca/ar9331.c=1031=static int ar9331_sw_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/qca/ar9331.c-1078-\ndrivers/net/dsa/qca/ar9331.c:1079:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/qca/ar9331.c-1080-\tif (ret)\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c=2059=qca8k_sw_probe(struct mdio_device *mdiodev)\n--\ndrivers/net/dsa/qca/qca8k-8xxx.c-2120-\ndrivers/net/dsa/qca/qca8k-8xxx.c:2121:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/qca/qca8k-8xxx.c-2122-}\n--\ndrivers/net/dsa/realtek/rtl83xx.c=248=int rtl83xx_register_switch(struct realtek_priv *priv)\n--\ndrivers/net/dsa/realtek/rtl83xx.c-264-\ndrivers/net/dsa/realtek/rtl83xx.c:265:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/realtek/rtl83xx.c-266-\tif (ret) {\n--\ndrivers/net/dsa/rzn1_a5psw.c=1207=static int a5psw_probe(struct platform_device *pdev)\n--\ndrivers/net/dsa/rzn1_a5psw.c-1262-\ndrivers/net/dsa/rzn1_a5psw.c:1263:\tret = dsa_register_switch(ds);\ndrivers/net/dsa/rzn1_a5psw.c-1264-\tif (ret) {\n--\ndrivers/net/dsa/sja1105/sja1105_main.c=3237=static int sja1105_probe(struct spi_device *spi)\n--\ndrivers/net/dsa/sja1105/sja1105_main.c-3340-\ndrivers/net/dsa/sja1105/sja1105_main.c:3341:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/sja1105/sja1105_main.c-3342-}\n--\ndrivers/net/dsa/vitesse-vsc73xx-core.c=2333=int vsc73xx_probe(struct vsc73xx *vsc)\n--\ndrivers/net/dsa/vitesse-vsc73xx-core.c-2384-\tvsc-\u003eds-\u003ephylink_mac_ops = \u0026vsc73xx_phylink_mac_ops;\ndrivers/net/dsa/vitesse-vsc73xx-core.c:2385:\tret = dsa_register_switch(vsc-\u003eds);\ndrivers/net/dsa/vitesse-vsc73xx-core.c-2386-\tif (ret) {\n--\ndrivers/net/dsa/xrs700x/xrs700x.c=818=int xrs700x_switch_register(struct xrs700x *priv)\n--\ndrivers/net/dsa/xrs700x/xrs700x.c-841-\ndrivers/net/dsa/xrs700x/xrs700x.c:842:\treturn dsa_register_switch(priv-\u003eds);\ndrivers/net/dsa/xrs700x/xrs700x.c-843-}\n--\ninclude/linux/platform_data/b53.h=25=struct b53_platform_data {\ninclude/linux/platform_data/b53.h:26:\t/* Must be first such that dsa_register_switch() can access it */\ninclude/linux/platform_data/b53.h-27-\tstruct dsa_chip_data cd;\n--\ninclude/linux/platform_data/microchip-ksz.h=51=struct ksz_platform_data {\ninclude/linux/platform_data/microchip-ksz.h:52:\t/* Must be first such that dsa_register_switch() can access it */\ninclude/linux/platform_data/microchip-ksz.h-53-\tstruct dsa_chip_data cd;\n--\ninclude/linux/platform_data/mv88e6xxx.h=7=struct dsa_mv88e6xxx_pdata {\ninclude/linux/platform_data/mv88e6xxx.h:8:\t/* Must be first, such that dsa_register_switch() can access this\ninclude/linux/platform_data/mv88e6xxx.h-9-\t * without gory pointer manipulations\n--\ninclude/net/dsa.h=1391=void dsa_unregister_switch(struct dsa_switch *ds);\ninclude/net/dsa.h:1392:int dsa_register_switch(struct dsa_switch *ds);\ninclude/net/dsa.h-1393-void dsa_switch_shutdown(struct dsa_switch *ds);\n--\nnet/dsa/dsa.c=1534=static int dsa_switch_probe(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1575-\nnet/dsa/dsa.c:1576:int dsa_register_switch(struct dsa_switch *ds)\nnet/dsa/dsa.c-1577-{\n--\nnet/dsa/dsa.c-1586-}\nnet/dsa/dsa.c:1587:EXPORT_SYMBOL_GPL(dsa_register_switch);\nnet/dsa/dsa.c-1588-\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 47796
							output: 39
							thoughts: 46
						

Thoughts:
**Examining `dsa_loop.c`**

Alright, let's dive in. First, I need to remind myself – what exactly *is* `dsa_loop.c`? Where does this fit in the grand scheme of things? Ah, right. It's likely a DSA-related file. Okay, a quick double-check to confirm. Let's make sure I'm looking at the correct path: `drivers/net/dsa/dsa_loop.c`. Confirmed. Now I can start analyzing.



14/2 2026/09/20 10:36 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 1,
  "LineCount": 70
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0-or-later\n   2:\t/*\n   3:\t * Distributed Switch Architecture loopback driver\n   4:\t *\n   5:\t * Copyright (C) 2016, Florian Fainelli \u003cf.fainelli@gmail.com\u003e\n   6:\t */\n   7:\t\n   8:\t#include \u003clinux/platform_device.h\u003e\n   9:\t#include \u003clinux/netdevice.h\u003e\n  10:\t#include \u003clinux/phy.h\u003e\n  11:\t#include \u003clinux/phy_fixed.h\u003e\n  12:\t#include \u003clinux/export.h\u003e\n  13:\t#include \u003clinux/ethtool.h\u003e\n  14:\t#include \u003clinux/workqueue.h\u003e\n  15:\t#include \u003clinux/module.h\u003e\n  16:\t#include \u003clinux/if_bridge.h\u003e\n  17:\t#include \u003clinux/if_vlan.h\u003e\n  18:\t#include \u003clinux/types.h\u003e\n  19:\t#include \u003cnet/dsa.h\u003e\n  20:\t\n  21:\t#define DSA_LOOP_NUM_PORTS\t6\n  22:\t#define DSA_LOOP_CPU_PORT\t(DSA_LOOP_NUM_PORTS - 1)\n  23:\t#define NUM_FIXED_PHYS\t\t(DSA_LOOP_NUM_PORTS - 2)\n  24:\t\n  25:\tstruct dsa_loop_vlan {\n  26:\t\tu16 members;\n  27:\t\tu16 untagged;\n  28:\t};\n  29:\t\n  30:\tstruct dsa_loop_mib_entry {\n  31:\t\tchar name[ETH_GSTRING_LEN];\n  32:\t\tunsigned long val;\n  33:\t};\n  34:\t\n  35:\tenum dsa_loop_mib_counters {\n  36:\t\tDSA_LOOP_PHY_READ_OK,\n  37:\t\tDSA_LOOP_PHY_READ_ERR,\n  38:\t\tDSA_LOOP_PHY_WRITE_OK,\n  39:\t\tDSA_LOOP_PHY_WRITE_ERR,\n  40:\t\t__DSA_LOOP_CNT_MAX,\n  41:\t};\n  42:\t\n  43:\tstruct dsa_loop_port {\n  44:\t\tstruct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];\n  45:\t\tu16 pvid;\n  46:\t\tint mtu;\n  47:\t};\n  48:\t\n  49:\tstruct dsa_loop_priv {\n  50:\t\tstruct mii_bus\t*bus;\n  51:\t\tunsigned int\tport_base;\n  52:\t\tstruct dsa_loop_vlan vlans[VLAN_N_VID];\n  53:\t\tstruct net_device *netdev;\n  54:\t\tstruct dsa_loop_port ports[DSA_MAX_PORTS];\n  55:\t};\n  56:\t\n  57:\tstruct dsa_loop_pdata {\n  58:\t\t/* Must be first, such that dsa_register_switch() can access this\n  59:\t\t * without gory pointer manipulations\n  60:\t\t */\n  61:\t\tstruct dsa_chip_data cd;\n  62:\t\tconst char *name;\n  63:\t\tunsigned int enabled_ports;\n  64:\t\tconst char *netdev;\n  65:\t};\n  66:\t\n  67:\tstatic struct dsa_loop_mib_entry dsa_loop_mibs[] = {\n  68:\t\t[DSA_LOOP_PHY_READ_OK]\t= { \"phy_read_ok\", },\n  69:\t\t[DSA_LOOP_PHY_READ_ERR]\t= { \"phy_read_err\", },\n  70:\t\t[DSA_LOOP_PHY_WRITE_OK] = { \"phy_write_ok\", },\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 49006
							output: 38
							thoughts: 29
						

Thoughts:
Okay, let's break this down.

**Investigating the `dsa_loop` Tagging Protocol**

Alright, so I need to figure out the specific tag protocol that the `dsa_loop` component utilizes. My immediate instinct is to dive into the source code, specifically `dsa_loop.c`. That's where I expect to find the implementation details related to tag handling. I'm going to start there and see what information I can pull out. Time to roll up my sleeves and get into the code.



16/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "get_tag_protocol",
  "PathPrefix": "drivers/net/dsa/dsa_loop.c"
}

Results:
{
  "Output": "drivers/net/dsa/dsa_loop.c=334=static const struct dsa_switch_ops dsa_loop_driver = {\ndrivers/net/dsa/dsa_loop.c:335:\t.get_tag_protocol\t= dsa_loop_get_protocol,\ndrivers/net/dsa/dsa_loop.c-336-\t.setup\t\t\t= dsa_loop_setup,\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 49302
							output: 41
							thoughts: 14
						

Thoughts:
Okay, let's break this down. Here's how I'd approach summarizing the prompt, as if it were my own thought process:

**Diving into `dsa_loop_get_protocol`**

Right, so the system is asking me to think about something called `dsa_loop_get_protocol`. My expert instincts immediately kick in. This suggests we're dealing with Distributed Shared Array (DSA) loop functionality, and specifically, we're trying to retrieve or determine the communication protocol being used. Given that "loop" is in the name, my mind jumps to data transfer within a loop structure or iteration. I'll need to consider this in that context, i.e., what sort of protocol do we want within that loop. I'm imagining a piece of code, likely in a parallel processing context, where this function is a key element. I need to understand what's calling this and the context so that I can provide useful information. I need to think about the different protocols that are available, such as TCP, UDP, or even more specialized, high-performance inter-process communication mechanisms. I'll need to consider how this protocol is determined, is it dynamically negotiated or statically configured? The answer to that question will have a significant impact on performance or versatility.



18/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "dsa_loop_get_protocol",
  "PathPrefix": "drivers/net/dsa/dsa_loop.c"
}

Results:
{
  "Output": "drivers/net/dsa/dsa_loop.c=97=static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)\n--\ndrivers/net/dsa/dsa_loop.c-124-\ndrivers/net/dsa/dsa_loop.c:125:static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/dsa_loop.c-126-\t\t\t\t\t\t   int port,\n--\ndrivers/net/dsa/dsa_loop.c=334=static const struct dsa_switch_ops dsa_loop_driver = {\ndrivers/net/dsa/dsa_loop.c:335:\t.get_tag_protocol\t= dsa_loop_get_protocol,\ndrivers/net/dsa/dsa_loop.c-336-\t.setup\t\t\t= dsa_loop_setup,\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 49875
							output: 41
							thoughts: 29
						

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

**Analyzing `dsa_loop.c` (Lines 120-140)**

Okay, let's dive into `drivers/net/dsa/dsa_loop.c` and zero in on lines 120 through 140. I want to quickly refresh my understanding of how this virtual DSA loopback device handles packet processing. I'm expecting to see the core of the packet transmission and reception logic, the interaction with the DSA core, and perhaps some buffer management details. Given the nature of a loopback device, I'd anticipate this code to be relatively straightforward, without too much complexity involving PHY interactions or actual hardware access. My focus is on the data flow and how it maps to the DSA framework. I'll be looking specifically for any obvious performance bottlenecks or potential areas for optimization within this virtual environment, even if they aren't critical in this context. I'll be paying close attention to any memory copying operations, interrupt handling (though I don't anticipate much), and any synchronization primitives being used. The goal is to quickly grasp the high-level architecture of this loopback and how it integrates into the broader DSA ecosystem.



20/2 2026/09/20 10:37 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 120,
  "LineCount": 25
}

Results:
{
  "Contents": " 120:\tout:\n 121:\t\tdsa_devlink_resources_unregister(ds);\n 122:\t\treturn err;\n 123:\t}\n 124:\t\n 125:\tstatic enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,\n 126:\t\t\t\t\t\t\t   int port,\n 127:\t\t\t\t\t\t\t   enum dsa_tag_protocol mp)\n 128:\t{\n 129:\t\tdev_dbg(ds-\u003edev, \"%s: port: %d\\n\", __func__, port);\n 130:\t\n 131:\t\treturn DSA_TAG_PROTO_NONE;\n 132:\t}\n 133:\t\n 134:\tstatic int dsa_loop_setup(struct dsa_switch *ds)\n 135:\t{\n 136:\t\tstruct dsa_loop_priv *ps = ds-\u003epriv;\n 137:\t\tunsigned int i;\n 138:\t\n 139:\t\tfor (i = 0; i \u003c ds-\u003enum_ports; i++)\n 140:\t\t\tmemcpy(ps-\u003eports[i].mib, dsa_loop_mibs,\n 141:\t\t\t       sizeof(dsa_loop_mibs));\n 142:\t\n 143:\t\tdev_dbg(ds-\u003edev, \"%s\\n\", __func__);\n 144:\t\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 50585
							output: 37
							thoughts: 50
						

Thoughts:
Here's my summary, as the thought process unfolds:

**Digging into Potential Tagging Shenanigans**

Okay, hold on a minute! I'm pretty sure I remember something about the DSA loop and its potential to mess with the tag protocol via the sysfs "tagging" interface. That rings a bell, but I need to confirm that. Hmm, let's see...I should probably check the source code for the DSA tree functionality. Specifically, I need to see what's going on with how the tag protocol is altered. Let's get right into the code for `dsa_tree_change_tag_proto`. That function in `net/dsa/dsa.c` should tell me everything I need to know about the function of `tagging` using sysfs.



22/2 2026/09/20 10:37 tool read-file
0m Args:
{
  "File": "net/dsa/dsa.c",
  "FirstLine": 980,
  "LineCount": 55
}

Results:
{
  "Contents": " 980:\t * is that all DSA switches within a tree share the same tagger, otherwise\n 981:\t * they would have formed disjoint trees (different \"dsa,member\" values).\n 982:\t */\n 983:\tint dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,\n 984:\t\t\t\t      const struct dsa_device_ops *tag_ops,\n 985:\t\t\t\t      const struct dsa_device_ops *old_tag_ops)\n 986:\t{\n 987:\t\tstruct dsa_notifier_tag_proto_info info;\n 988:\t\tstruct dsa_port *dp;\n 989:\t\tint err = -EBUSY;\n 990:\t\n 991:\t\tif (!rtnl_trylock())\n 992:\t\t\treturn restart_syscall();\n 993:\t\n 994:\t\t/* At the moment we don't allow changing the tag protocol under\n 995:\t\t * traffic. The rtnl_mutex also happens to serialize concurrent\n 996:\t\t * attempts to change the tagging protocol. If we ever lift the IFF_UP\n 997:\t\t * restriction, there needs to be another mutex which serializes this.\n 998:\t\t */\n 999:\t\tdsa_tree_for_each_user_port(dp, dst) {\n1000:\t\t\tif (dsa_port_to_conduit(dp)-\u003eflags \u0026 IFF_UP)\n1001:\t\t\t\tgoto out_unlock;\n1002:\t\n1003:\t\t\tif (dp-\u003euser-\u003eflags \u0026 IFF_UP)\n1004:\t\t\t\tgoto out_unlock;\n1005:\t\t}\n1006:\t\n1007:\t\t/* Notify the tag protocol change */\n1008:\t\tinfo.tag_ops = tag_ops;\n1009:\t\terr = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, \u0026info);\n1010:\t\tif (err)\n1011:\t\t\tgoto out_unwind_tagger;\n1012:\t\n1013:\t\terr = dsa_tree_bind_tag_proto(dst, tag_ops);\n1014:\t\tif (err)\n1015:\t\t\tgoto out_unwind_tagger;\n1016:\t\n1017:\t\trtnl_unlock();\n1018:\t\n1019:\t\treturn 0;\n1020:\t\n1021:\tout_unwind_tagger:\n1022:\t\tinfo.tag_ops = old_tag_ops;\n1023:\t\tdsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, \u0026info);\n1024:\tout_unlock:\n1025:\t\trtnl_unlock();\n1026:\t\treturn err;\n1027:\t}\n1028:\t\n1029:\tstatic void dsa_tree_conduit_state_change(struct dsa_switch_tree *dst,\n1030:\t\t\t\t\t\t  struct net_device *conduit)\n1031:\t{\n1032:\t\tstruct dsa_notifier_conduit_state_info info;\n1033:\t\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\n1034:\t\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 51703
							output: 36
							thoughts: 92
						

Thoughts:
**Investigating the Tag Protocol Notification**

Okay, hold on a second! I need to dig into this. My eye was drawn to this line: `info.tag_ops = tag_ops; err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);`. Specifically, that `dsa_tree_notify` call, using `DSA_NOTIFIER_TAG_PROTO`. What exactly is that notification doing, and what's the significance of `DSA_NOTIFIER_TAG_PROTO`? I need a clear understanding of what triggers that notification, and its impact.

Right, time to go spelunking in the code. I'll head over to `net/dsa/switch.c`, and zero in on the `dsa_switch_change_tag_proto` function. That seems like a prime candidate for where this tag protocol change notification would be implemented, or at least called from. I need to understand the control flow here.



24/2 2026/09/20 10:37 tool read-file
0m Args:
{
  "File": "net/dsa/switch.c",
  "FirstLine": 870,
  "LineCount": 40
}

Results:
{
  "Contents": " 870:\t\treturn 0;\n 871:\t}\n 872:\t\n 873:\tstatic int dsa_switch_change_tag_proto(struct dsa_switch *ds,\n 874:\t\t\t\t\t       struct dsa_notifier_tag_proto_info *info)\n 875:\t{\n 876:\t\tconst struct dsa_device_ops *tag_ops = info-\u003etag_ops;\n 877:\t\tstruct dsa_port *dp, *cpu_dp;\n 878:\t\tint err;\n 879:\t\n 880:\t\tif (!ds-\u003eops-\u003echange_tag_protocol)\n 881:\t\t\treturn -EOPNOTSUPP;\n 882:\t\n 883:\t\tASSERT_RTNL();\n 884:\t\n 885:\t\terr = ds-\u003eops-\u003echange_tag_protocol(ds, tag_ops-\u003eproto);\n 886:\t\tif (err)\n 887:\t\t\treturn err;\n 888:\t\n 889:\t\tdsa_switch_for_each_cpu_port(cpu_dp, ds)\n 890:\t\t\tdsa_port_set_tag_protocol(cpu_dp, tag_ops);\n 891:\t\n 892:\t\t/* Now that changing the tag protocol can no longer fail, let's update\n 893:\t\t * the remaining bits which are \"duplicated for faster access\", and the\n 894:\t\t * bits that depend on the tagger, such as the MTU.\n 895:\t\t */\n 896:\t\tdsa_switch_for_each_user_port(dp, ds) {\n 897:\t\t\tstruct net_device *user = dp-\u003euser;\n 898:\t\n 899:\t\t\tdsa_user_setup_tagger(user);\n 900:\t\n 901:\t\t\t/* rtnl_mutex is held in dsa_tree_change_tag_proto */\n 902:\t\t\tdsa_user_change_mtu(user, user-\u003emtu);\n 903:\t\t}\n 904:\t\n 905:\t\treturn 0;\n 906:\t}\n 907:\t\n 908:\t/* We use the same cross-chip notifiers to inform both the tagger side, as well\n 909:\t * as the switch side, of connection and disconnection events.\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 52663
							output: 19
							thoughts: 65
						

Thoughts:
**Investigating the Missing Piece in Tag Protocol Handling**

Okay, so I'm looking at this snippet of code: `if (!ds->ops->change_tag_protocol) return -EOPNOTSUPP;`. Right away, that `-EOPNOTSUPP` return tells me something is amiss. It's a clear signal that the underlying data structure's operations, in this case `ds->ops->change_tag_protocol`, isn't implemented. Specifically, it seems like the `dsa_loop` driver *doesn't* provide a mechanism to change the tag protocol. This is interesting.

Now, my expert intuition is tingling. This missing piece needs to be understood. If `dsa_loop` *isn't* doing it, then *who is*? I need to track down exactly which drivers, or subsystems, are responsible for implementing this vital `.change_tag_protocol` function. It's time to dive into the kernel source and perform a code search to see where this function is actually defined. Let's see what the kernel has to tell me about this, shall we?



26/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "change_tag_protocol"
}

Results:
{
  "Output": "Documentation/networking/dsa/dsa.rst=606=Switch configuration\n--\nDocumentation/networking/dsa/dsa.rst-615-\nDocumentation/networking/dsa/dsa.rst:616:- ``change_tag_protocol``: when the default tagging protocol has compatibility\nDocumentation/networking/dsa/dsa.rst-617-  problems with the conduit or other issues, the driver may support changing it\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=6654=static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds,\n--\ndrivers/net/dsa/mv88e6xxx/chip.c-6662-\ndrivers/net/dsa/mv88e6xxx/chip.c:6663:static int mv88e6xxx_change_tag_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/mv88e6xxx/chip.c-6664-\t\t\t\t\t enum dsa_tag_protocol proto)\n--\ndrivers/net/dsa/mv88e6xxx/chip.c=7195=static const struct dsa_switch_ops mv88e6xxx_switch_ops = {\ndrivers/net/dsa/mv88e6xxx/chip.c-7196-\t.get_tag_protocol\t= mv88e6xxx_get_tag_protocol,\ndrivers/net/dsa/mv88e6xxx/chip.c:7197:\t.change_tag_protocol\t= mv88e6xxx_change_tag_protocol,\ndrivers/net/dsa/mv88e6xxx/chip.c-7198-\t.setup\t\t\t= mv88e6xxx_setup,\n--\ndrivers/net/dsa/ocelot/felix.c=732=felix_tag_proto_setup_shared(struct dsa_switch *ds,\n--\ndrivers/net/dsa/ocelot/felix.c-753- */\ndrivers/net/dsa/ocelot/felix.c:754:static int felix_change_tag_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/ocelot/felix.c-755-\t\t\t\t     enum dsa_tag_protocol proto)\n--\ndrivers/net/dsa/ocelot/felix.c=1696=static int felix_setup(struct dsa_switch *ds)\n--\ndrivers/net/dsa/ocelot/felix.c-1751-\t */\ndrivers/net/dsa/ocelot/felix.c:1752:\tfelix_change_tag_protocol(ds, felix-\u003etag_proto);\ndrivers/net/dsa/ocelot/felix.c-1753-\n--\ndrivers/net/dsa/ocelot/felix.c=2306=static const struct dsa_switch_ops felix_switch_ops = {\ndrivers/net/dsa/ocelot/felix.c-2307-\t.get_tag_protocol\t\t= felix_get_tag_protocol,\ndrivers/net/dsa/ocelot/felix.c:2308:\t.change_tag_protocol\t\t= felix_change_tag_protocol,\ndrivers/net/dsa/ocelot/felix.c-2309-\t.connect_tag_protocol\t\t= felix_connect_tag_protocol,\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=2891=static int rtl8365mb_cpu_config(struct realtek_priv *priv)\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c-2919-\ndrivers/net/dsa/realtek/rtl8365mb_main.c:2920:static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/realtek/rtl8365mb_main.c-2921-\t\t\t\t\t enum dsa_tag_protocol proto)\n--\ndrivers/net/dsa/realtek/rtl8365mb_main.c=3285=static const struct dsa_switch_ops rtl8365mb_switch_ops = {\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3286-\t.get_tag_protocol = rtl8365mb_get_tag_protocol,\ndrivers/net/dsa/realtek/rtl8365mb_main.c:3287:\t.change_tag_protocol = rtl8365mb_change_tag_protocol,\ndrivers/net/dsa/realtek/rtl8365mb_main.c-3288-\t.setup = rtl8365mb_setup,\n--\ninclude/net/dsa.h=866=struct dsa_switch_ops {\n--\ninclude/net/dsa.h-870-\t * mandatory. Switches which can operate using multiple tagging\ninclude/net/dsa.h:871:\t * protocols should implement @change_tag_protocol and report in\ninclude/net/dsa.h-872-\t * @get_tag_protocol the tagger in current use.\n--\ninclude/net/dsa.h-876-\t\t\t\t\t\t  enum dsa_tag_protocol mprot);\ninclude/net/dsa.h:877:\tint\t(*change_tag_protocol)(struct dsa_switch *ds,\ninclude/net/dsa.h-878-\t\t\t\t       enum dsa_tag_protocol proto);\n--\nnet/dsa/dsa.c=570=static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-579-\trtnl_lock();\nnet/dsa/dsa.c:580:\terr = ds-\u003eops-\u003echange_tag_protocol(ds, tag_ops-\u003eproto);\nnet/dsa/dsa.c-581-\trtnl_unlock();\n--\nnet/dsa/dsa.c=1155=static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *conduit,\n--\nnet/dsa/dsa.c-1176-\tif (user_protocol) {\nnet/dsa/dsa.c:1177:\t\tif (!ds-\u003eops-\u003echange_tag_protocol) {\nnet/dsa/dsa.c-1178-\t\t\tdev_err(ds-\u003edev, \"Tag protocol cannot be modified\\n\");\n--\nnet/dsa/dsa.c-1230-\t * dsa_switch_setup_tag_protocol once .setup has run and the\nnet/dsa/dsa.c:1231:\t * driver is ready to accept calls to .change_tag_protocol. If\nnet/dsa/dsa.c-1232-\t * the driver does not support the custom protocol at that\n--\nnet/dsa/switch.c=873=static int dsa_switch_change_tag_proto(struct dsa_switch *ds,\n--\nnet/dsa/switch.c-879-\nnet/dsa/switch.c:880:\tif (!ds-\u003eops-\u003echange_tag_protocol)\nnet/dsa/switch.c-881-\t\treturn -EOPNOTSUPP;\n--\nnet/dsa/switch.c-884-\nnet/dsa/switch.c:885:\terr = ds-\u003eops-\u003echange_tag_protocol(ds, tag_ops-\u003eproto);\nnet/dsa/switch.c-886-\tif (err)\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 54933
							output: 45
							thoughts: 280
						

Thoughts:
**My Analysis of DSA Tag Protocol Implementation**

Okay, so I'm digging into this `DSA_TAG_PROTO_NETC` tag protocol, and here's what's going through my head. From my initial assessment, it seems that only `mv88e6xxx`, `felix`, and `rtl8365mb` even *implement* `change_tag_protocol`, but none of them seem to support `DSA_TAG_PROTO_NETC`. That's a red flag. I need to figure out if *any* switch driver is actually configured to accept `DSA_TAG_PROTO_NETC`.

My next thought is to isolate those which use `DSA_TAG_PROTO_NETC`, so I checked and it looks like the only place is `drivers/net/dsa/netc/`. Okay, so let's delve into that directory.

Now I'm looking at `drivers/net/dsa/netc/netc_main.c`. I can see `netc_get_tag_protocol` does, indeed, return `DSA_TAG_PROTO_NETC`. Alright, that's what I was expecting. I also found `netc_connect_tag_protocol`, and I found:

```c
static int netc_connect_tag_protocol(struct dsa_switch *ds,
				     enum dsa_tag_protocol proto)
{
	struct netc_tagger_data *tagger_data;

	if (proto != DSA_TAG_PROTO_NETC)
		return -EPROTONOSUPPORT;
...
```

Right, that confirms that `netc` is actively checking for, and enforcing, that it's using the NETC protocol. My next step is to understand what kind of hardware device this `netc_switch` actually *is*. I'm going to revisit `drivers/net/dsa/netc/netc_main.c` to look for a hardware reference.



28/2 2026/09/20 10:37 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/netc/netc_main.c",
  "FirstLine": 2505,
  "LineCount": 70
}

Results:
{
  "Contents": "2505:\t\t.port_txtstamp\t\t\t= netc_port_txtstamp,\n2506:\t};\n2507:\t\n2508:\tstatic int netc_switch_probe(struct pci_dev *pdev,\n2509:\t\t\t\t     const struct pci_device_id *id)\n2510:\t{\n2511:\t\tstruct device_node *node = dev_of_node(\u0026pdev-\u003edev);\n2512:\t\tstruct device *dev = \u0026pdev-\u003edev;\n2513:\t\tstruct netc_switch *priv;\n2514:\t\tstruct dsa_switch *ds;\n2515:\t\tint err;\n2516:\t\n2517:\t\tif (!node)\n2518:\t\t\treturn dev_err_probe(dev, -ENODEV,\n2519:\t\t\t\t\t     \"No DT bindings, skipping\\n\");\n2520:\t\n2521:\t\terr = netc_switch_check_emdio_is_ready(dev);\n2522:\t\tif (err)\n2523:\t\t\treturn err;\n2524:\t\n2525:\t\terr = netc_switch_pci_init(pdev);\n2526:\t\tif (err)\n2527:\t\t\treturn err;\n2528:\t\n2529:\t\tpriv = pci_get_drvdata(pdev);\n2530:\t\tnetc_switch_get_ip_revision(priv);\n2531:\t\n2532:\t\terr = netc_switch_platform_probe(priv);\n2533:\t\tif (err)\n2534:\t\t\treturn err;\n2535:\t\n2536:\t\tds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);\n2537:\t\tif (!ds)\n2538:\t\t\treturn -ENOMEM;\n2539:\t\n2540:\t\tds-\u003edev = dev;\n2541:\t\tds-\u003enum_ports = priv-\u003einfo-\u003enum_ports;\n2542:\t\tds-\u003enum_tx_queues = NETC_TC_NUM;\n2543:\t\tds-\u003eops = \u0026netc_switch_ops;\n2544:\t\tds-\u003ephylink_mac_ops = \u0026netc_phylink_mac_ops;\n2545:\t\tds-\u003efdb_isolation = true;\n2546:\t\tds-\u003emax_num_bridges = priv-\u003einfo-\u003enum_ports - 1;\n2547:\t\tds-\u003eageing_time_min = 1000;\n2548:\t\tds-\u003eageing_time_max = U32_MAX;\n2549:\t\tds-\u003epriv = priv;\n2550:\t\tpriv-\u003eds = ds;\n2551:\t\n2552:\t\terr = dsa_register_switch(ds);\n2553:\t\tif (err)\n2554:\t\t\treturn dev_err_probe(dev, err,\n2555:\t\t\t\t\t     \"Failed to register DSA switch\\n\");\n2556:\t\n2557:\t\treturn 0;\n2558:\t}\n2559:\t\n2560:\tstatic void netc_switch_remove(struct pci_dev *pdev)\n2561:\t{\n2562:\t\tstruct netc_switch *priv = pci_get_drvdata(pdev);\n2563:\t\n2564:\t\tif (!priv)\n2565:\t\t\treturn;\n2566:\t\n2567:\t\tdsa_unregister_switch(priv-\u003eds);\n2568:\t}\n2569:\t\n2570:\tstatic void netc_switch_shutdown(struct pci_dev *pdev)\n2571:\t{\n2572:\t\tstruct netc_switch *priv = pci_get_drvdata(pdev);\n2573:\t\n2574:\t\tif (!priv)\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 56715
							output: 28
							thoughts: 1076
						

Thoughts:
**Analysis of netc_switch_probe and DSA/NETC Dependencies for Fuzzing Eligibility**

Okay, let's break this down. My initial observation is focused on `netc_switch_probe` and its dependencies. I see it's looking for a Device Tree node, and also relies on PCI hardware, as indicated by the `pdev` structure. The Kconfig entry makes this very explicit, with its dependencies on `ARM64 || COMPILE_TEST`, `NET_DSA && PCI`, and a few other Freescale-specific components including the `PTP` configuration. This immediately flags a potential issue: our target architecture is `amd64`, not `ARM64`. Furthermore, the code expects the NXP NETC switch hardware, specifically an NXP i.MX94 SoC, which is an ARM64 automotive SoC that I understand has an integrated PCIe endpoint / switch architecture. Also, there is a `ptp_netc.c`, which describes a "NXP NETC V4 Timer driver", and implies another PCI function of the same hardware, likely on the same internal PCI bus.

Now, let's double-check the negative criteria outlined in the instructions. It states that changes are *not* worthy of fuzzing if they fall into categories like unreachable hardware or architecture-specific code paths. And that really catches my attention, especially the section on "Structurally unreachable hardware." Specifically, "Vendor-specific PCIe switches... requiring physical ASIC/PCIe cards not emulated in standard QEMU".

So, the question becomes: Can the code in `net/dsa/tag_netc.c` be reached on amd64 *without* the hardware? The Device Tree check in `netc_switch_probe` suggests this is *unlikely* without a suitable Device Tree blob describing the NXP hardware. I'm going to carefully investigate how `tag_netc.c` is actually used.

I'll check what registers `DSA_TAG_PROTO_NETC`: I see `module_dsa_tag_driver(netc_netdev_ops);` registers `netc_netdev_ops` with the DSA subsystem.  The key here is that it registers tagger operations. I'm especially interested in when DSA invokes those operations: `.rcv = netc_rcv`, `.xmit = netc_xmit`, `.flow_dissect = netc_flow_dissect`, `.connect = netc_connect`, and `.disconnect = netc_disconnect`.

Now, I need to understand how `netc_rcv`, `netc_xmit`, or any of those functions, are ever called. I have to trace how the DSA subsystem invokes these tag operations. My next step is to examine where `tag_ops->rcv` and `tag_ops->xmit` are actually *called* within the DSA code.



30/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "-\u003ercv\\(",
  "PathPrefix": "net/dsa"
}

Results:
{
  "Output": "net/dsa/tag.c=54=static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,\n--\nnet/dsa/tag.c-86-\t} else {\nnet/dsa/tag.c:87:\t\tnskb = cpu_dp-\u003ercv(skb, dev);\nnet/dsa/tag.c-88-\t}\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 58550
							output: 31
							thoughts: 125
						

Thoughts:
**Analyzing the DSA Packet Reception Path**

Okay, let's break this down. First, I see `cpu_dp->rcv(skb, dev)`. My immediate question is: where is `dsa_switch_rcv` getting invoked? A quick grep reveals that `dsa_switch_rcv` is registered in `net/dsa/tag.c` within a `struct packet_type` called `dsa_pack_type`. The `.func` pointer is set to `dsa_switch_rcv`, which means it's called when packets are received on a network device that's registered as a DSA conduit. 

Now, the crucial question. How does a network device get *defined* as a DSA conduit – i.e., how is `cpu_dp->conduit` established? I need to trace the DSA initialization path to understand this. Time to dig into how conduits are established within the DSA framework.



32/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "conduit-\u003edsa_ptr",
  "PathPrefix": "net/dsa"
}

Results:
{
  "Output": "net/dsa/dsa.c=1029=static void dsa_tree_conduit_state_change(struct dsa_switch_tree *dst,\n--\nnet/dsa/dsa.c-1032-\tstruct dsa_notifier_conduit_state_info info;\nnet/dsa/dsa.c:1033:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/dsa.c-1034-\n--\nnet/dsa/dsa.c=1041=void dsa_tree_conduit_admin_state_change(struct dsa_switch_tree *dst,\n--\nnet/dsa/dsa.c-1044-{\nnet/dsa/dsa.c:1045:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/dsa.c-1046-\tbool notify = false;\n--\nnet/dsa/dsa.c=1064=void dsa_tree_conduit_oper_state_change(struct dsa_switch_tree *dst,\n--\nnet/dsa/dsa.c-1067-{\nnet/dsa/dsa.c:1068:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/dsa.c-1069-\tbool notify = false;\n--\nnet/dsa/dsa.c=1611=void dsa_switch_shutdown(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1653-\tdsa_switch_for_each_cpu_port(dp, ds) {\nnet/dsa/dsa.c:1654:\t\tdp-\u003econduit-\u003edsa_ptr = NULL;\nnet/dsa/dsa.c-1655-\t\tnetdev_put(dp-\u003econduit, \u0026dp-\u003econduit_tracker);\n--\nnet/dsa/port.c=1397=static int dsa_port_assign_conduit(struct dsa_port *dp,\n--\nnet/dsa/port.c-1412-\nnet/dsa/port.c:1413:\tdp-\u003ecpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/port.c-1414-\tdp-\u003ecpu_port_in_lag = netif_is_lag_master(conduit);\n--\nnet/dsa/tag.h=191=dsa_find_designated_bridge_port_by_vid(struct net_device *conduit, u16 vid)\nnet/dsa/tag.h-192-{\nnet/dsa/tag.h:193:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/tag.h-194-\tstruct dsa_switch_tree *dst = cpu_dp-\u003edst;\n--\nnet/dsa/tag_8021q.c=475=dsa_tag_8021q_find_port_by_vbid(struct net_device *conduit, int vbid)\nnet/dsa/tag_8021q.c-476-{\nnet/dsa/tag_8021q.c:477:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/tag_8021q.c-478-\tstruct dsa_switch_tree *dst = cpu_dp-\u003edst;\n--\nnet/dsa/tag_sja1105.c=529=static struct sk_buff *sja1110_rcv_meta(struct sk_buff *skb, u16 rx_header)\n--\nnet/dsa/tag_sja1105.c-539-\nnet/dsa/tag_sja1105.c:540:\tcpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/tag_sja1105.c-541-\tds = dsa_switch_find(cpu_dp-\u003edst-\u003eindex, switch_id);\n--\nnet/dsa/user.c=3367=static int dsa_conduit_lag_join(struct net_device *conduit,\n--\nnet/dsa/user.c-3371-{\nnet/dsa/user.c:3372:\tstruct dsa_port *cpu_dp = conduit-\u003edsa_ptr;\nnet/dsa/user.c-3373-\tstruct dsa_switch_tree *dst = cpu_dp-\u003edst;\n--\nnet/dsa/user.c-3404-\nnet/dsa/user.c:3405:\tdsa_conduit_lag_teardown(lag_dev, conduit-\u003edsa_ptr);\nnet/dsa/user.c-3406-\n--\nnet/dsa/user.c=3410=static void dsa_conduit_lag_leave(struct net_device *conduit,\n--\nnet/dsa/user.c-3448-\t */\nnet/dsa/user.c:3449:\tdsa_conduit_lag_teardown(lag_dev, conduit-\u003edsa_ptr);\nnet/dsa/user.c-3450-}\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 60259
							output: 29
							thoughts: 38
						

Thoughts:
Here's my thought process, as if I were analyzing that:

**Tracing the Data Structure Pointer**

Okay, so I need to figure out where `conduit->dsa_ptr` is being initialized. My first instinct is to dive into the core implementation, likely in `dsa_conduit.c` or a similarly named file, if I'm familiar with the codebase. I want to pinpoint the exact location where this pointer is assigned a value. Understanding this initialization is crucial for comprehending the lifecycle and usage of the `dsa_ptr` within the `conduit` structure. It's a fundamental step for debugging or understanding the system's architecture.



34/2 2026/09/20 10:37 tool grepper
0m Args:
{
  "Expression": "dsa_ptr =",
  "PathPrefix": "net/dsa"
}

Results:
{
  "Output": "net/dsa/conduit.c=419=int dsa_conduit_setup(struct net_device *dev, struct dsa_port *cpu_dp)\n--\nnet/dsa/conduit.c-452-\nnet/dsa/conduit.c:453:\tdev-\u003edsa_ptr = cpu_dp;\nnet/dsa/conduit.c-454-\n--\nnet/dsa/conduit.c=474=void dsa_conduit_teardown(struct net_device *dev)\n--\nnet/dsa/conduit.c-480-\nnet/dsa/conduit.c:481:\tdev-\u003edsa_ptr = NULL;\nnet/dsa/conduit.c-482-\n--\nnet/dsa/dsa.c=1611=void dsa_switch_shutdown(struct dsa_switch *ds)\n--\nnet/dsa/dsa.c-1653-\tdsa_switch_for_each_cpu_port(dp, ds) {\nnet/dsa/dsa.c:1654:\t\tdp-\u003econduit-\u003edsa_ptr = NULL;\nnet/dsa/dsa.c-1655-\t\tnetdev_put(dp-\u003econduit, \u0026dp-\u003econduit_tracker);\n--\nnet/dsa/user.c=3410=static void dsa_conduit_lag_leave(struct net_device *conduit,\n--\nnet/dsa/user.c-3436-\t\t */\nnet/dsa/user.c:3437:\t\tlag_dev-\u003edsa_ptr = new_cpu_dp;\nnet/dsa/user.c-3438-\t\twmb();\n"
}

35/2 2026/09/20 10:37 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 60913
							output: 41
							thoughts: 152
						

Thoughts:
**My Line of Inquiry: Tracing the `dsa_ptr` Assignment**

Okay, so `dev->dsa_ptr = cpu_dp;`—that assignment is *only* happening within `dsa_conduit_setup`, right? And `dsa_conduit_setup` is explicitly triggered during the registration or setup phase of a DSA switch. That immediately means that the whole setup is being kicked off by a driver probing and initiating the switch registration.

My mind immediately zooms to drivers. Which ones are responsible for probing and initiating? Obviously a driver needs to exist. Does `dsa_loop` probe? I need to clarify if `dsa_loop` is involved in this process. Let's delve into `dsa_loop.c` and search the probe function.

Ah, it seems that `dsa_loop_drv_probe` is labeled as an `mdio_device` driver. That's good intel.

Now I need to understand *how* `dsa_loop` is actually probing. Specifically, I'm looking for the mechanism that triggers it. Let's go to line 350 in `dsa_loop.c` and see what's happening.



36/2 2026/09/20 10:37 tool read-file
0m Args:
{
  "File": "drivers/net/dsa/dsa_loop.c",
  "FirstLine": 345,
  "LineCount": 70
}

Results:
{
  "Contents": " 345:\t\t.port_bridge_leave\t= dsa_loop_port_bridge_leave,\n 346:\t\t.port_stp_state_set\t= dsa_loop_port_stp_state_set,\n 347:\t\t.port_vlan_filtering\t= dsa_loop_port_vlan_filtering,\n 348:\t\t.port_vlan_add\t\t= dsa_loop_port_vlan_add,\n 349:\t\t.port_vlan_del\t\t= dsa_loop_port_vlan_del,\n 350:\t\t.port_change_mtu\t= dsa_loop_port_change_mtu,\n 351:\t\t.port_max_mtu\t\t= dsa_loop_port_max_mtu,\n 352:\t\t.phylink_get_caps\t= dsa_loop_phylink_get_caps,\n 353:\t};\n 354:\t\n 355:\tstatic int dsa_loop_drv_probe(struct mdio_device *mdiodev)\n 356:\t{\n 357:\t\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\n 358:\t\tstruct dsa_loop_priv *ps;\n 359:\t\tstruct dsa_switch *ds;\n 360:\t\tint ret;\n 361:\t\n 362:\t\tif (!pdata)\n 363:\t\t\treturn -ENODEV;\n 364:\t\n 365:\t\tds = devm_kzalloc(\u0026mdiodev-\u003edev, sizeof(*ds), GFP_KERNEL);\n 366:\t\tif (!ds)\n 367:\t\t\treturn -ENOMEM;\n 368:\t\n 369:\t\tds-\u003edev = \u0026mdiodev-\u003edev;\n 370:\t\tds-\u003enum_ports = DSA_LOOP_NUM_PORTS;\n 371:\t\n 372:\t\tps = devm_kzalloc(\u0026mdiodev-\u003edev, sizeof(*ps), GFP_KERNEL);\n 373:\t\tif (!ps)\n 374:\t\t\treturn -ENOMEM;\n 375:\t\n 376:\t\tps-\u003enetdev = dev_get_by_name(\u0026init_net, pdata-\u003enetdev);\n 377:\t\tif (!ps-\u003enetdev)\n 378:\t\t\treturn -EPROBE_DEFER;\n 379:\t\n 380:\t\tpdata-\u003ecd.netdev[DSA_LOOP_CPU_PORT] = \u0026ps-\u003enetdev-\u003edev;\n 381:\t\n 382:\t\tds-\u003edev = \u0026mdiodev-\u003edev;\n 383:\t\tds-\u003eops = \u0026dsa_loop_driver;\n 384:\t\tds-\u003epriv = ps;\n 385:\t\tps-\u003ebus = mdiodev-\u003ebus;\n 386:\t\n 387:\t\tdev_set_drvdata(\u0026mdiodev-\u003edev, ds);\n 388:\t\n 389:\t\tret = dsa_register_switch(ds);\n 390:\t\tif (!ret)\n 391:\t\t\tdev_info(\u0026mdiodev-\u003edev, \"%s: 0x%0x\\n\",\n 392:\t\t\t\t pdata-\u003ename, pdata-\u003eenabled_ports);\n 393:\t\n 394:\t\treturn ret;\n 395:\t}\n 396:\t\n 397:\tstatic void dsa_loop_drv_remove(struct mdio_device *mdiodev)\n 398:\t{\n 399:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n 400:\t\tstruct dsa_loop_priv *ps;\n 401:\t\n 402:\t\tif (!ds)\n 403:\t\t\treturn;\n 404:\t\n 405:\t\tps = ds-\u003epriv;\n 406:\t\n 407:\t\tdsa_unregister_switch(ds);\n 408:\t\tdev_put(ps-\u003enetdev);\n 409:\t}\n 410:\t\n 411:\tstatic void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\n 412:\t{\n 413:\t\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\n 414:\t\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 62504
							output: 30
							thoughts: 35
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Investigating the Origins of `dsa_loop`**

Alright, so I need to understand how the `dsa_loop` device is instantiated. Where does this crucial piece of the DSA puzzle originate? My initial thought is to dive right into the source code, specifically looking for the implementation. My gut feeling tells me the likely candidate is going to be within the DSA driver structure.  I'm going to head directly to `drivers/net/dsa/dsa_loop_bdinfo.c`. That should contain all the necessary details. Let's see what's in there.



38/2 2026/09/20 10:38 tool grepper
0m Args:
{
  "Expression": "dsa_loop",
  "PathPrefix": "drivers/net/dsa"
}

Results:
{
  "Output": "drivers/net/dsa/Makefile=3=bcm-sf2-objs\t\t\t:= bcm_sf2.o bcm_sf2_cfp.o\ndrivers/net/dsa/Makefile:4:obj-$(CONFIG_NET_DSA_LOOP)\t+= dsa_loop.o\ndrivers/net/dsa/Makefile-5-obj-$(CONFIG_NET_DSA_MT7530)\t+= mt7530.o\n--\ndrivers/net/dsa/dsa_loop.c-24-\ndrivers/net/dsa/dsa_loop.c:25:struct dsa_loop_vlan {\ndrivers/net/dsa/dsa_loop.c-26-\tu16 members;\n--\ndrivers/net/dsa/dsa_loop.c-29-\ndrivers/net/dsa/dsa_loop.c:30:struct dsa_loop_mib_entry {\ndrivers/net/dsa/dsa_loop.c-31-\tchar name[ETH_GSTRING_LEN];\n--\ndrivers/net/dsa/dsa_loop.c-34-\ndrivers/net/dsa/dsa_loop.c:35:enum dsa_loop_mib_counters {\ndrivers/net/dsa/dsa_loop.c-36-\tDSA_LOOP_PHY_READ_OK,\n--\ndrivers/net/dsa/dsa_loop.c-42-\ndrivers/net/dsa/dsa_loop.c:43:struct dsa_loop_port {\ndrivers/net/dsa/dsa_loop.c:44:\tstruct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX];\ndrivers/net/dsa/dsa_loop.c-45-\tu16 pvid;\n--\ndrivers/net/dsa/dsa_loop.c-48-\ndrivers/net/dsa/dsa_loop.c:49:struct dsa_loop_priv {\ndrivers/net/dsa/dsa_loop.c-50-\tstruct mii_bus\t*bus;\ndrivers/net/dsa/dsa_loop.c-51-\tunsigned int\tport_base;\ndrivers/net/dsa/dsa_loop.c:52:\tstruct dsa_loop_vlan vlans[VLAN_N_VID];\ndrivers/net/dsa/dsa_loop.c-53-\tstruct net_device *netdev;\ndrivers/net/dsa/dsa_loop.c:54:\tstruct dsa_loop_port ports[DSA_MAX_PORTS];\ndrivers/net/dsa/dsa_loop.c-55-};\ndrivers/net/dsa/dsa_loop.c-56-\ndrivers/net/dsa/dsa_loop.c:57:struct dsa_loop_pdata {\ndrivers/net/dsa/dsa_loop.c-58-\t/* Must be first, such that dsa_register_switch() can access this\n--\ndrivers/net/dsa/dsa_loop.c-66-\ndrivers/net/dsa/dsa_loop.c:67:static struct dsa_loop_mib_entry dsa_loop_mibs[] = {\ndrivers/net/dsa/dsa_loop.c-68-\t[DSA_LOOP_PHY_READ_OK]\t= { \"phy_read_ok\", },\n--\ndrivers/net/dsa/dsa_loop.c=75=static struct mdio_device *switch_mdiodev;\ndrivers/net/dsa/dsa_loop.c-76-\ndrivers/net/dsa/dsa_loop.c:77:enum dsa_loop_devlink_resource_id {\ndrivers/net/dsa/dsa_loop.c-78-\tDSA_LOOP_DEVLINK_PARAM_ID_NONE,  /* DEVLINK_RESOURCE_ID_PARENT_TOP */\n--\ndrivers/net/dsa/dsa_loop.c-81-\ndrivers/net/dsa/dsa_loop.c:82:static u64 dsa_loop_devlink_vtu_get(void *priv)\ndrivers/net/dsa/dsa_loop.c-83-{\ndrivers/net/dsa/dsa_loop.c:84:\tstruct dsa_loop_priv *ps = priv;\ndrivers/net/dsa/dsa_loop.c-85-\tunsigned int i, count = 0;\ndrivers/net/dsa/dsa_loop.c:86:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-87-\n--\ndrivers/net/dsa/dsa_loop.c-96-\ndrivers/net/dsa/dsa_loop.c:97:static int dsa_loop_setup_devlink_resources(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-98-{\ndrivers/net/dsa/dsa_loop.c-99-\tstruct devlink_resource_size_params size_params;\ndrivers/net/dsa/dsa_loop.c:100:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-101-\tint err;\n--\ndrivers/net/dsa/dsa_loop.c-115-\t\t\t\t\t      DSA_LOOP_DEVLINK_PARAM_ID_VTU,\ndrivers/net/dsa/dsa_loop.c:116:\t\t\t\t\t      dsa_loop_devlink_vtu_get, ps);\ndrivers/net/dsa/dsa_loop.c-117-\n--\ndrivers/net/dsa/dsa_loop.c-124-\ndrivers/net/dsa/dsa_loop.c:125:static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds,\ndrivers/net/dsa/dsa_loop.c-126-\t\t\t\t\t\t   int port,\n--\ndrivers/net/dsa/dsa_loop.c-133-\ndrivers/net/dsa/dsa_loop.c:134:static int dsa_loop_setup(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-135-{\ndrivers/net/dsa/dsa_loop.c:136:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-137-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-139-\tfor (i = 0; i \u003c ds-\u003enum_ports; i++)\ndrivers/net/dsa/dsa_loop.c:140:\t\tmemcpy(ps-\u003eports[i].mib, dsa_loop_mibs,\ndrivers/net/dsa/dsa_loop.c:141:\t\t       sizeof(dsa_loop_mibs));\ndrivers/net/dsa/dsa_loop.c-142-\n--\ndrivers/net/dsa/dsa_loop.c-144-\ndrivers/net/dsa/dsa_loop.c:145:\treturn dsa_loop_setup_devlink_resources(ds);\ndrivers/net/dsa/dsa_loop.c-146-}\ndrivers/net/dsa/dsa_loop.c-147-\ndrivers/net/dsa/dsa_loop.c:148:static void dsa_loop_teardown(struct dsa_switch *ds)\ndrivers/net/dsa/dsa_loop.c-149-{\n--\ndrivers/net/dsa/dsa_loop.c-152-\ndrivers/net/dsa/dsa_loop.c:153:static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset)\ndrivers/net/dsa/dsa_loop.c-154-{\n--\ndrivers/net/dsa/dsa_loop.c-160-\ndrivers/net/dsa/dsa_loop.c:161:static void dsa_loop_get_strings(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-162-\t\t\t\t u32 stringset, uint8_t *data)\ndrivers/net/dsa/dsa_loop.c-163-{\ndrivers/net/dsa/dsa_loop.c:164:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-165-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-173-\ndrivers/net/dsa/dsa_loop.c:174:static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-175-\t\t\t\t       uint64_t *data)\ndrivers/net/dsa/dsa_loop.c-176-{\ndrivers/net/dsa/dsa_loop.c:177:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-178-\tunsigned int i;\n--\ndrivers/net/dsa/dsa_loop.c-183-\ndrivers/net/dsa/dsa_loop.c:184:static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum)\ndrivers/net/dsa/dsa_loop.c-185-{\ndrivers/net/dsa/dsa_loop.c:186:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-187-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-198-\ndrivers/net/dsa/dsa_loop.c:199:static int dsa_loop_phy_write(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-200-\t\t\t      int regnum, u16 value)\ndrivers/net/dsa/dsa_loop.c-201-{\ndrivers/net/dsa/dsa_loop.c:202:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-203-\tstruct mii_bus *bus = ps-\u003ebus;\n--\ndrivers/net/dsa/dsa_loop.c-214-\ndrivers/net/dsa/dsa_loop.c:215:static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-216-\t\t\t\t     struct dsa_bridge bridge,\n--\ndrivers/net/dsa/dsa_loop.c-225-\ndrivers/net/dsa/dsa_loop.c:226:static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-227-\t\t\t\t       struct dsa_bridge bridge)\n--\ndrivers/net/dsa/dsa_loop.c-232-\ndrivers/net/dsa/dsa_loop.c:233:static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-234-\t\t\t\t\tu8 state)\n--\ndrivers/net/dsa/dsa_loop.c-239-\ndrivers/net/dsa/dsa_loop.c:240:static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-241-\t\t\t\t\tbool vlan_filtering,\n--\ndrivers/net/dsa/dsa_loop.c-249-\ndrivers/net/dsa/dsa_loop.c:250:static int dsa_loop_port_vlan_add(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-251-\t\t\t\t  const struct switchdev_obj_port_vlan *vlan,\n--\ndrivers/net/dsa/dsa_loop.c-255-\tbool pvid = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_PVID;\ndrivers/net/dsa/dsa_loop.c:256:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-257-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:258:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-259-\n--\ndrivers/net/dsa/dsa_loop.c-282-\ndrivers/net/dsa/dsa_loop.c:283:static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-284-\t\t\t\t  const struct switchdev_obj_port_vlan *vlan)\n--\ndrivers/net/dsa/dsa_loop.c-286-\tbool untagged = vlan-\u003eflags \u0026 BRIDGE_VLAN_INFO_UNTAGGED;\ndrivers/net/dsa/dsa_loop.c:287:\tstruct dsa_loop_priv *ps = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-288-\tu16 pvid = ps-\u003eports[port].pvid;\ndrivers/net/dsa/dsa_loop.c-289-\tstruct mii_bus *bus = ps-\u003ebus;\ndrivers/net/dsa/dsa_loop.c:290:\tstruct dsa_loop_vlan *vl;\ndrivers/net/dsa/dsa_loop.c-291-\n--\ndrivers/net/dsa/dsa_loop.c-310-\ndrivers/net/dsa/dsa_loop.c:311:static int dsa_loop_port_change_mtu(struct dsa_switch *ds, int port,\ndrivers/net/dsa/dsa_loop.c-312-\t\t\t\t    int new_mtu)\ndrivers/net/dsa/dsa_loop.c-313-{\ndrivers/net/dsa/dsa_loop.c:314:\tstruct dsa_loop_priv *priv = ds-\u003epriv;\ndrivers/net/dsa/dsa_loop.c-315-\n--\ndrivers/net/dsa/dsa_loop.c-320-\ndrivers/net/dsa/dsa_loop.c:321:static int dsa_loop_port_max_mtu(struct dsa_switch *ds, int port)\ndrivers/net/dsa/dsa_loop.c-322-{\n--\ndrivers/net/dsa/dsa_loop.c-325-\ndrivers/net/dsa/dsa_loop.c:326:static void dsa_loop_phylink_get_caps(struct dsa_switch *dsa, int port,\ndrivers/net/dsa/dsa_loop.c-327-\t\t\t\t      struct phylink_config *config)\n--\ndrivers/net/dsa/dsa_loop.c-333-\ndrivers/net/dsa/dsa_loop.c:334:static const struct dsa_switch_ops dsa_loop_driver = {\ndrivers/net/dsa/dsa_loop.c:335:\t.get_tag_protocol\t= dsa_loop_get_protocol,\ndrivers/net/dsa/dsa_loop.c:336:\t.setup\t\t\t= dsa_loop_setup,\ndrivers/net/dsa/dsa_loop.c:337:\t.teardown\t\t= dsa_loop_teardown,\ndrivers/net/dsa/dsa_loop.c:338:\t.get_strings\t\t= dsa_loop_get_strings,\ndrivers/net/dsa/dsa_loop.c:339:\t.get_ethtool_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:340:\t.get_sset_count\t\t= dsa_loop_get_sset_count,\ndrivers/net/dsa/dsa_loop.c:341:\t.get_ethtool_phy_stats\t= dsa_loop_get_ethtool_stats,\ndrivers/net/dsa/dsa_loop.c:342:\t.phy_read\t\t= dsa_loop_phy_read,\ndrivers/net/dsa/dsa_loop.c:343:\t.phy_write\t\t= dsa_loop_phy_write,\ndrivers/net/dsa/dsa_loop.c:344:\t.port_bridge_join\t= dsa_loop_port_bridge_join,\ndrivers/net/dsa/dsa_loop.c:345:\t.port_bridge_leave\t= dsa_loop_port_bridge_leave,\ndrivers/net/dsa/dsa_loop.c:346:\t.port_stp_state_set\t= dsa_loop_port_stp_state_set,\ndrivers/net/dsa/dsa_loop.c:347:\t.port_vlan_filtering\t= dsa_loop_port_vlan_filtering,\ndrivers/net/dsa/dsa_loop.c:348:\t.port_vlan_add\t\t= dsa_loop_port_vlan_add,\ndrivers/net/dsa/dsa_loop.c:349:\t.port_vlan_del\t\t= dsa_loop_port_vlan_del,\ndrivers/net/dsa/dsa_loop.c:350:\t.port_change_mtu\t= dsa_loop_port_change_mtu,\ndrivers/net/dsa/dsa_loop.c:351:\t.port_max_mtu\t\t= dsa_loop_port_max_mtu,\ndrivers/net/dsa/dsa_loop.c:352:\t.phylink_get_caps\t= dsa_loop_phylink_get_caps,\ndrivers/net/dsa/dsa_loop.c-353-};\ndrivers/net/dsa/dsa_loop.c-354-\ndrivers/net/dsa/dsa_loop.c:355:static int dsa_loop_drv_probe(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-356-{\ndrivers/net/dsa/dsa_loop.c:357:\tstruct dsa_loop_pdata *pdata = mdiodev-\u003edev.platform_data;\ndrivers/net/dsa/dsa_loop.c:358:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-359-\tstruct dsa_switch *ds;\n--\ndrivers/net/dsa/dsa_loop.c-382-\tds-\u003edev = \u0026mdiodev-\u003edev;\ndrivers/net/dsa/dsa_loop.c:383:\tds-\u003eops = \u0026dsa_loop_driver;\ndrivers/net/dsa/dsa_loop.c-384-\tds-\u003epriv = ps;\n--\ndrivers/net/dsa/dsa_loop.c-396-\ndrivers/net/dsa/dsa_loop.c:397:static void dsa_loop_drv_remove(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-398-{\ndrivers/net/dsa/dsa_loop.c-399-\tstruct dsa_switch *ds = dev_get_drvdata(\u0026mdiodev-\u003edev);\ndrivers/net/dsa/dsa_loop.c:400:\tstruct dsa_loop_priv *ps;\ndrivers/net/dsa/dsa_loop.c-401-\n--\ndrivers/net/dsa/dsa_loop.c-410-\ndrivers/net/dsa/dsa_loop.c:411:static void dsa_loop_drv_shutdown(struct mdio_device *mdiodev)\ndrivers/net/dsa/dsa_loop.c-412-{\n--\ndrivers/net/dsa/dsa_loop.c-422-\ndrivers/net/dsa/dsa_loop.c:423:static struct mdio_driver dsa_loop_drv = {\ndrivers/net/dsa/dsa_loop.c-424-\t.mdiodrv.driver\t= {\n--\ndrivers/net/dsa/dsa_loop.c-426-\t},\ndrivers/net/dsa/dsa_loop.c:427:\t.probe\t= dsa_loop_drv_probe,\ndrivers/net/dsa/dsa_loop.c:428:\t.remove\t= dsa_loop_drv_remove,\ndrivers/net/dsa/dsa_loop.c:429:\t.shutdown = dsa_loop_drv_shutdown,\ndrivers/net/dsa/dsa_loop.c-430-};\ndrivers/net/dsa/dsa_loop.c-431-\ndrivers/net/dsa/dsa_loop.c:432:static int dsa_loop_bus_match(struct device *dev,\ndrivers/net/dsa/dsa_loop.c-433-\t\t\t      const struct device_driver *drv)\ndrivers/net/dsa/dsa_loop.c-434-{\ndrivers/net/dsa/dsa_loop.c:435:\treturn drv == \u0026dsa_loop_drv.mdiodrv.driver;\ndrivers/net/dsa/dsa_loop.c-436-}\ndrivers/net/dsa/dsa_loop.c-437-\ndrivers/net/dsa/dsa_loop.c:438:static void dsa_loop_phydevs_unregister(void)\ndrivers/net/dsa/dsa_loop.c-439-{\n--\ndrivers/net/dsa/dsa_loop.c-445-\ndrivers/net/dsa/dsa_loop.c:446:static int __init dsa_loop_create_switch_mdiodev(void)\ndrivers/net/dsa/dsa_loop.c-447-{\ndrivers/net/dsa/dsa_loop.c:448:\tstatic struct dsa_loop_pdata dsa_loop_pdata = {\ndrivers/net/dsa/dsa_loop.c-449-\t\t.cd = {\n--\ndrivers/net/dsa/dsa_loop.c-470-\ndrivers/net/dsa/dsa_loop.c:471:\tswitch_mdiodev-\u003ebus_match = dsa_loop_bus_match;\ndrivers/net/dsa/dsa_loop.c:472:\tswitch_mdiodev-\u003edev.platform_data = \u0026dsa_loop_pdata;\ndrivers/net/dsa/dsa_loop.c-473-\n--\ndrivers/net/dsa/dsa_loop.c-481-\ndrivers/net/dsa/dsa_loop.c:482:static int __init dsa_loop_init(void)\ndrivers/net/dsa/dsa_loop.c-483-{\n--\ndrivers/net/dsa/dsa_loop.c-486-\ndrivers/net/dsa/dsa_loop.c:487:\tret = dsa_loop_create_switch_mdiodev();\ndrivers/net/dsa/dsa_loop.c-488-\tif (ret)\n--\ndrivers/net/dsa/dsa_loop.c-493-\ndrivers/net/dsa/dsa_loop.c:494:\tret = mdio_driver_register(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c-495-\tif (ret) {\ndrivers/net/dsa/dsa_loop.c:496:\t\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-497-\t\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-502-}\ndrivers/net/dsa/dsa_loop.c:503:module_init(dsa_loop_init);\ndrivers/net/dsa/dsa_loop.c-504-\ndrivers/net/dsa/dsa_loop.c:505:static void __exit dsa_loop_exit(void)\ndrivers/net/dsa/dsa_loop.c-506-{\ndrivers/net/dsa/dsa_loop.c:507:\tmdio_driver_unregister(\u0026dsa_loop_drv);\ndrivers/net/dsa/dsa_loop.c:508:\tdsa_loop_phydevs_unregister();\ndrivers/net/dsa/dsa_loop.c-509-\tmdio_device_remove(switch_mdiodev);\n--\ndrivers/net/dsa/dsa_loop.c-511-}\ndrivers/net/dsa/dsa_loop.c:512:module_exit(dsa_loop_exit);\ndrivers/net/dsa/dsa_loop.c-513-\n"
}

39/2 2026/09/20 10:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 69256
							output: 28
							thoughts: 1448
						

Thoughts:
**Analysis of NETC DSA Driver Reachability on amd64**

Okay, so I'm looking at this patch and immediately, the `dsa_loop` function and its use of `DSA_TAG_PROTO_NONE` jump out at me.  Crucially, it *doesn't* implement `change_tag_protocol`.  That's a bit of a red flag.  The code seems very specific.

The next thing I'm focusing on is that `DSA_TAG_PROTO_NETC` is only used in `drivers/net/dsa/netc/netc_main.c` specifically for `NET_DSA_NETC_SWITCH`. This is the core of this whole patch's context.

Now, can `NET_DSA_NETC_SWITCH` even probe?  Let's look at `netc_switch_probe`.  It's a PCI driver.  It relies on device tree bindings, and it's looking for a Freescale (now NXP) PCI device with vendor ID `0x1957` and device ID `0xee00`.  Right away, I'm thinking, "Is this hardware even present in any standard QEMU setup on amd64?"

And no, it's not. This `netc_switch` is tied to specific NXP automotive/embedded processors, like the i.MX94.  It's specialized hardware. Looking at the Kconfig, this confirms the limitations by checking for architecture dependencies, confirming that it depends on `ARM64 || COMPILE_TEST`.

Then, I see the `drivers/ptp/ptp_netc.c` file and its dependency on Freescale vendor ID `0xee02`.  This is the NXP NETC V4 Timer driver, also a PCI device with the same Freescale vendor ID, furthering the issue with accessibility.

So, according to the guidelines, this falls squarely into the "Structurally unreachable hardware" category, specifically with vendor-specific PCIe switches.

The patch modifies a whole bunch of files: Kconfig, Makefile, source files in `drivers/net/dsa/netc`, the PTP driver, and even the include files.  That's a broad touch.

Now, the critical question: Can any part of this code be *reached* on an amd64 system?  Let's drill down into `net/dsa/tag_netc.c`. What are the call paths?

*   `netc_xmit`: This gets called via `dsa_user_xmit` when transmitting frames on a DSA user net_device. To *get* that DSA user net_device, you need a successful `dsa_register_switch` call, which creates the user netdevs.
*   `netc_rcv`: This is called through `dsa_switch_rcv` when a packet arrives on the conduit netdev. The important bit is that `dev->dsa_ptr` has to be set, which only happens in `dsa_conduit_setup`, called only when registering a DSA switch.
*   `netc_flow_dissect`: Similarly, this one is called from `dsa_conduit_flow_dissect`, but only when operating on a conduit netdev, where `conduit->dsa_ptr` is set.
*   `netc_connect` and `netc_disconnect`: These are triggered via `dsa_tree_notify` when binding the tagger to a switch tree.  This only happens when a DSA switch tree is set up or tag protocol is changed.

The whole chain depends on creating a DSA switch or conduit.

So my immediate follow up question is whether it is possible for something like syzkaller create a mock DSA switch or conduit via Netlink or any virtual device. Can we "fake" it from userspace?  I need to check *very* carefully if DSA has ANY interface that allows userspace to create a switch or conduit (e.g., using `rtnl`, `nl80211`, or `devlink`). I need to search the `net/dsa/` directory for any Netlink or userspace interfaces. If there's no way to create the essential objects, this whole patch is likely dead code on amd64.



40/2 2026/09/20 10:38 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "rtnl_register",
  "PathPrefix": "net/dsa"
}

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

41/2 2026/09/20 10:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 71680
							output: 30
							thoughts: 24
						

Thoughts:
Here's a summary of my immediate thought process:

**Checking DSA Registration**

Okay, time to get down to brass tacks. I need to quickly determine if either `devlink` or `rtnl_link_ops` have been correctly registered within the `net/dsa/` directory. This is the first step in understanding the current state of the DSA framework and whether it's correctly integrated. Are the hooks in place? Are the interfaces functional? I need to verify their presence to proceed with my analysis. This will tell me if the right infrastructure is present or if I need to start digging deeper for a registration problem.



42/2 2026/09/20 10:38 tool grepper
0m Args:
{
  "Expression": "rtnl_link_ops",
  "PathPrefix": "net/dsa"
}

Results:
{
  "Output": "net/dsa/netlink.c=45=static int dsa_fill_info(struct sk_buff *skb, const struct net_device *dev)\n--\nnet/dsa/netlink.c-54-\nnet/dsa/netlink.c:55:struct rtnl_link_ops dsa_link_ops __read_mostly = {\nnet/dsa/netlink.c-56-\t.kind\t\t\t= \"dsa\",\n--\nnet/dsa/netlink.h-5-\nnet/dsa/netlink.h:6:extern struct rtnl_link_ops dsa_link_ops __read_mostly;\nnet/dsa/netlink.h-7-\n--\nnet/dsa/user.c=2758=int dsa_user_create(struct dsa_port *port)\n--\nnet/dsa/user.c-2784-\nnet/dsa/user.c:2785:\tuser_dev-\u003ertnl_link_ops = \u0026dsa_link_ops;\nnet/dsa/user.c-2786-\tuser_dev-\u003eethtool_ops = \u0026dsa_user_ethtool_ops;\n"
}

43/2 2026/09/20 10:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 72173
							output: 35
							thoughts: 15
						

Thoughts:
Here's a summary of my immediate thought process, focusing on what I'd be looking at in `net/dsa/netlink.c`:

**Initial Dive: Exploring the DSA Netlink Interface**

Okay, so I'm thinking about `net/dsa/netlink.c`. That file is where the Netlink interface for Distributed Switch Architecture (DSA) lives. I immediately start mentally mapping out what that means: it's the bridge between user space applications – tools, configuration utilities, etc. – and the DSA driver framework within the kernel. I'm expecting to see the core Netlink handlers that define how we interact with DSA-capable network devices.

I'd be specifically interested in how DSA configuration is passed into the kernel. What Netlink message types are handled? Are they using standard Netlink attributes or custom ones? I'll probably be looking for structures and enums that define the payload format of those messages. I want to see the definitions for commands like adding or removing DSA ports, setting VLAN configurations, maybe even flow control or QoS parameters if this is an advanced switch.

I anticipate seeing how Netlink sockets are created, managed, and how the kernel registers itself to listen for incoming messages. There's probably an initial setup function, call-backs, and a structure that holds the registered Netlink handlers. I need to get a clear picture of what user-space applications can *do* with the DSA infrastructure.



44/2 2026/09/20 10:38 tool read-file
0m Args:
{
  "File": "net/dsa/netlink.c",
  "FirstLine": 1,
  "LineCount": 60
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0\n   2:\t/* Copyright 2022 NXP\n   3:\t */\n   4:\t#include \u003clinux/netdevice.h\u003e\n   5:\t#include \u003cnet/rtnetlink.h\u003e\n   6:\t\n   7:\t#include \"netlink.h\"\n   8:\t#include \"user.h\"\n   9:\t\n  10:\tstatic const struct nla_policy dsa_policy[IFLA_DSA_MAX + 1] = {\n  11:\t\t[IFLA_DSA_CONDUIT]\t= { .type = NLA_U32 },\n  12:\t};\n  13:\t\n  14:\tstatic int dsa_changelink(struct net_device *dev, struct nlattr *tb[],\n  15:\t\t\t\t  struct nlattr *data[],\n  16:\t\t\t\t  struct netlink_ext_ack *extack)\n  17:\t{\n  18:\t\tint err;\n  19:\t\n  20:\t\tif (!data)\n  21:\t\t\treturn 0;\n  22:\t\n  23:\t\tif (data[IFLA_DSA_CONDUIT]) {\n  24:\t\t\tu32 ifindex = nla_get_u32(data[IFLA_DSA_CONDUIT]);\n  25:\t\t\tstruct net_device *conduit;\n  26:\t\n  27:\t\t\tconduit = __dev_get_by_index(dev_net(dev), ifindex);\n  28:\t\t\tif (!conduit)\n  29:\t\t\t\treturn -EINVAL;\n  30:\t\n  31:\t\t\terr = dsa_user_change_conduit(dev, conduit, extack);\n  32:\t\t\tif (err)\n  33:\t\t\t\treturn err;\n  34:\t\t}\n  35:\t\n  36:\t\treturn 0;\n  37:\t}\n  38:\t\n  39:\tstatic size_t dsa_get_size(const struct net_device *dev)\n  40:\t{\n  41:\t\treturn nla_total_size(sizeof(u32)) +\t/* IFLA_DSA_CONDUIT  */\n  42:\t\t       0;\n  43:\t}\n  44:\t\n  45:\tstatic int dsa_fill_info(struct sk_buff *skb, const struct net_device *dev)\n  46:\t{\n  47:\t\tstruct net_device *conduit = dsa_user_to_conduit(dev);\n  48:\t\n  49:\t\tif (nla_put_u32(skb, IFLA_DSA_CONDUIT, conduit-\u003eifindex))\n  50:\t\t\treturn -EMSGSIZE;\n  51:\t\n  52:\t\treturn 0;\n  53:\t}\n  54:\t\n  55:\tstruct rtnl_link_ops dsa_link_ops __read_mostly = {\n  56:\t\t.kind\t\t\t= \"dsa\",\n  57:\t\t.priv_size\t\t= sizeof(struct dsa_port),\n  58:\t\t.maxtype\t\t= IFLA_DSA_MAX,\n  59:\t\t.policy\t\t\t= dsa_policy,\n  60:\t\t.changelink\t\t= dsa_changelink,\n"
}

45/2 2026/09/20 10:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 73371
							output: 182
							thoughts: 826
						

Thoughts:
**My Assessment: Not Worth Fuzzing**

Okay, let's break this down. My initial observation is that `dsa_link_ops` lacks a `.newlink` method, which immediately flags a potential issue. DSA user netdevs aren't created with `ip link add type dsa`. They're *only* created internally via `dsa_user_create()` triggered by `dsa_register_switch()`. That's the entry point.

Diving deeper into `drivers/net/dsa/netc/netc_main.c`, `netc_switch_probe` requires a very specific setup: a PCIe device with the FREESCALE vendor ID (0x1957) and device ID 0xee00. On top of that, it needs Device Tree bindings and internal platform resources. Then, looking at `drivers/ptp/ptp_netc.c`, this driver *also* demands a FREESCALE PCIe device, but with ID 0xee02.

Here's the problem: neither of these specific hardware requirements are present in the standard QEMU or GCE environments I use for fuzzing on amd64. This is a critical point. My target architecture is amd64, but the code relies on `NET_DSA_NETC_SWITCH` which depends on `ARM64 || COMPILE_TEST`.

Moreover, the negative criteria in section 2 of my guidelines *explicitly* addresses this: "Structurally unreachable hardware" like vendor-specific PCIe switches and "Unreachable execution paths" for architectures other than the target. This patch series is entirely within `drivers/net/dsa/netc/*` for the NXP NETC PCIe switch driver, `drivers/ptp/ptp_netc.c` for the NXP NETC V4 Timer driver, `net/dsa/tag_netc.c` which is a DSA tagger specifically for NXP NETC switches, and relevant headers, `include/linux/dsa/tag_netc.h` and `include/linux/fsl/netc_global.h`. All these modifications are for a specialized NXP i.MX94 SoC with specific hardware dependencies. I cannot emulate these switch and timer devices or the associated Device Tree and platform resources in my standard virtual environments, which make the code inherently unreachable in my fuzzing setup. The DSA tag driver, in particular, cannot be exercised without the switch hardware.

Therefore, `WorthFuzzing` should absolutely be `false`.

Here's what I'll set for the `set-results`:

*   `WorthFuzzing`: `false`
*   `Reasoning`: The changes enhance PTP support and refactor host flood rules for the NXP NETC switch (`drivers/net/dsa/netc/`, `drivers/ptp/ptp_netc.c`, and `net/dsa/tag_netc.c`). These drivers are designed for specific NXP automotive hardware (i.MX94 SoC), requiring PCIe devices (vendor Freescale 0xee00 and 0xee02), Device Tree bindings, and platform resources. These hardware requirements are not emulated within standard virtualized fuzzing environments (QEMU/GCE) on amd64. Moreover, the DSA tag driver, `tag_netc`, requires the switch hardware. The code is thus structurally unreachable within standard fuzzing setups.
*   `EnableConfigs`: `[]`
*   `FocusSymbols`: `[]`

This conclusion aligns perfectly with the triage guidelines and negative criteria. We're good to go.



46/2 2026/09/20 10:38 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series implements PTP (IEEE 1588) support and refactors host flood filtering for the NXP NETC switch family (drivers/net/dsa/netc/, drivers/ptp/ptp_netc.c, and net/dsa/tag_netc.c). The switch and timer drivers require physical or emulated PCIe devices specific to NXP SoCs (Freescale vendor ID with Device Tree node bindings) that are not emulated in standard QEMU/GCE amd64 virtualized environments. Furthermore, the DSA tagging driver (tag_netc) cannot be bound or exercised without the underlying hardware switch. Consequently, the modified code is structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series implements PTP (IEEE 1588) support and refactors host flood filtering for the NXP NETC switch family (drivers/net/dsa/netc/, drivers/ptp/ptp_netc.c, and net/dsa/tag_netc.c). The switch and timer drivers require physical or emulated PCIe devices specific to NXP SoCs (Freescale vendor ID with Device Tree node bindings) that are not emulated in standard QEMU/GCE amd64 virtualized environments. Furthermore, the DSA tagging driver (tag_netc) cannot be bound or exercised without the underlying hardware switch. Consequently, the modified code is structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

47/1 2026/09/20 10:38 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)