| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/10 11:33 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch adds L3 routing offload support for the Microchip Sparx5 switch driver. This is a vendor-specific hardware switch driver that requires physical hardware (Sparx5 SoC) and is not emulated in standard QEMU. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/10 11:33 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit cfb847c5db0faa51a40d3da33d5ce37279302d47\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 10 11:33:04 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile\nindex d447f9e84d92a..d917734d9f12e 100644\n--- a/drivers/net/ethernet/microchip/sparx5/Makefile\n+++ b/drivers/net/ethernet/microchip/sparx5/Makefile\n@@ -11,7 +11,7 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \\\n sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \\\n sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \\\n sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \\\n- sparx5_psfp.o sparx5_mirror.o sparx5_regs.o\n+ sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_router.o\n \n sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o\n sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o\ndiff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c\nindex f3a9c71bea36a..f8d1741c264f0 100644\n--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c\n+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c\n@@ -322,6 +322,8 @@ static const struct sparx5_consts lan969x_consts = {\n \t.qres_max_prio_idx = 315,\n \t.qres_max_colour_idx = 323,\n \t.tod_pin = 4,\n+\t.vmid_cnt = 127,\n+\t.arp_tbl_cnt = 1024,\n \t.vcaps = lan969x_vcaps,\n \t.vcap_stats = \u0026lan969x_vcap_stats,\n \t.vcaps_cfg = lan969x_vcap_inst_cfg,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c\nindex 3fc2c006ba129..00fd03f87bdd8 100644\n--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c\n+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c\n@@ -1,11 +1,11 @@\n // SPDX-License-Identifier: GPL-2.0+\n /* Microchip lan969x Switch driver\n *\n- * Copyright (c) 2024 Microchip Technology Inc.\n+ * Copyright (c) 2026 Microchip Technology Inc.\n */\n \n-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.\n- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b\n+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.\n+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2\n */\n \n #include \"lan969x.h\"\n@@ -77,12 +77,16 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {\n \t[GA_ANA_CL_COMMON] = 87040,\n \t[GA_ANA_L2_COMMON] = 561928,\n \t[GA_ANA_L3_COMMON] = 370752,\n+\t[GA_ANA_L3_VMID] = 360448,\n+\t[GA_ANA_L3_ARP_PTR_REMAP] = 370944,\n+\t[GA_ANA_L3_ARP] = 294912,\n \t[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 368580,\n \t[GA_ASM_CFG] = 18304,\n \t[GA_ASM_PFC_TIMER_CFG] = 15568,\n \t[GA_ASM_LBK_WM_CFG] = 15596,\n \t[GA_ASM_LBK_MISC_CFG] = 15608,\n \t[GA_ASM_RAM_CTRL] = 15684,\n+\t[GA_EACL_COMMON] = 37408,\n \t[GA_EACL_ES2_KEY_SELECT_PROFILE] = 36864,\n \t[GA_EACL_CNT_TBL] = 30720,\n \t[GA_EACL_POL_CFG] = 38400,\n@@ -103,6 +107,7 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {\n \t[GA_QSYS_RAM_CTRL] = 2204,\n \t[GA_REW_COMMON] = 98304,\n \t[GA_REW_PORT] = 49152,\n+\t[GA_REW_VMID] = 96768,\n \t[GA_REW_VOE_PORT_LM_CNT] = 90112,\n \t[GA_REW_RAM_CTRL] = 93992,\n \t[GA_VOP_RAM_CTRL] = 16368,\n@@ -124,6 +129,8 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {\n \t[GC_ANA_L2_ISDX_LIMIT] = 256,\n \t[GC_ANA_L2_ISDX] = 1024,\n \t[GC_ANA_L3_VLAN] = 4608,\n+\t[GC_ANA_L3_VMID] = 127,\n+\t[GC_ANA_L3_ARP] = 1024,\n \t[GC_ASM_DEV_STATISTICS] = 30,\n \t[GC_EACL_ES2_KEY_SELECT_PROFILE] = 68,\n \t[GC_EACL_CNT_TBL] = 512,\n@@ -134,6 +141,7 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {\n \t[GC_PTP_PTP_PINS] = 8,\n \t[GC_PTP_PHASE_DETECTOR_CTRL] = 8,\n \t[GC_REW_PORT] = 35,\n+\t[GC_REW_VMID] = 127,\n \t[GC_REW_VOE_PORT_LM_CNT] = 240,\n };\n \n@@ -191,7 +199,12 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {\n \t[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 30,\n \t[FW_ANA_L2_DLB_CFG_DLB_IDX] = 9,\n \t[FW_ANA_L2_TSN_CFG_TSN_SFID] = 8,\n+\t[FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 30,\n+\t[FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 3,\n+\t[FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 30,\n+\t[FW_ANA_L3_VMID_CFG_VMID] = 7,\n \t[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 30,\n+\t[FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 7,\n \t[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 2,\n \t[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 7,\n \t[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 5,\n@@ -217,6 +230,7 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {\n \t[FW_QSYS_ATOP_ATOP] = 11,\n \t[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 11,\n \t[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 6,\n+\t[FW_REW_RLEG_CTRL_DECAP_IRLEG] = 7,\n \t[FW_XQS_STAT_CFG_STAT_VIEW] = 10,\n \t[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 14,\n \t[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 14,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c\nindex 7acc5bcf337a2..e623b1dbe9cb9 100644\n--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c\n+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c\n@@ -1,10 +1,10 @@\n // SPDX-License-Identifier: BSD-3-Clause\n-/* Copyright (C) 2024 Microchip Technology Inc. and its subsidiaries.\n+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.\n * Microchip VCAP API\n */\n \n-/* This file is autogenerated by cml-utils 2024-10-07 11:10:56 +0200.\n- * Commit ID: b5ddc8e244eb2481a9524f1ddc630a8b41e7c391\n+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.\n+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a\n */\n \n #include \u003clinux/types.h\u003e\n@@ -1380,6 +1380,83 @@ static const struct vcap_field es0_isdx_keyfield[] = {\n \t},\n };\n \n+static const struct vcap_field lpm_sgl_ip4_keyfield[] = {\n+\t[VCAP_KF_DST_FLAG] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP4_XIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 11,\n+\t\t.width = 32,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_dbl_ip4_keyfield[] = {\n+\t[VCAP_KF_TYPE] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP4_SIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 11,\n+\t\t.width = 32,\n+\t},\n+\t[VCAP_KF_IP4_DIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 43,\n+\t\t.width = 32,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_sgl_ip6_keyfield[] = {\n+\t[VCAP_KF_DST_FLAG] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP6_XIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 11,\n+\t\t.width = 128,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_dbl_ip6_keyfield[] = {\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP6_SIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 10,\n+\t\t.width = 128,\n+\t},\n+\t[VCAP_KF_IP6_DIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 138,\n+\t\t.width = 128,\n+\t},\n+};\n+\n static const struct vcap_field es2_mac_etype_keyfield[] = {\n \t[VCAP_KF_TYPE] = {\n \t\t.type = VCAP_FIELD_U32,\n@@ -2338,6 +2415,29 @@ static const struct vcap_set es0_keyfield_set[] = {\n \t},\n };\n \n+static const struct vcap_set lpm_keyfield_set[] = {\n+\t[VCAP_KFS_SGL_IP4] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+\t[VCAP_KFS_DBL_IP4] = {\n+\t\t.type_id = 0,\n+\t\t.sw_per_item = 2,\n+\t\t.sw_cnt = 6,\n+\t},\n+\t[VCAP_KFS_SGL_IP6] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 3,\n+\t\t.sw_cnt = 4,\n+\t},\n+\t[VCAP_KFS_DBL_IP6] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 6,\n+\t\t.sw_cnt = 2,\n+\t},\n+};\n+\n static const struct vcap_set es2_keyfield_set[] = {\n \t[VCAP_KFS_MAC_ETYPE] = {\n \t\t.type_id = 0,\n@@ -2390,6 +2490,13 @@ static const struct vcap_field *es0_keyfield_set_map[] = {\n \t[VCAP_KFS_ISDX] = es0_isdx_keyfield,\n };\n \n+static const struct vcap_field *lpm_keyfield_set_map[] = {\n+\t[VCAP_KFS_SGL_IP4] = lpm_sgl_ip4_keyfield,\n+\t[VCAP_KFS_DBL_IP4] = lpm_dbl_ip4_keyfield,\n+\t[VCAP_KFS_SGL_IP6] = lpm_sgl_ip6_keyfield,\n+\t[VCAP_KFS_DBL_IP6] = lpm_dbl_ip6_keyfield,\n+};\n+\n static const struct vcap_field *es2_keyfield_set_map[] = {\n \t[VCAP_KFS_MAC_ETYPE] = es2_mac_etype_keyfield,\n \t[VCAP_KFS_ARP] = es2_arp_keyfield,\n@@ -2418,6 +2525,13 @@ static int es0_keyfield_set_map_size[] = {\n \t[VCAP_KFS_ISDX] = ARRAY_SIZE(es0_isdx_keyfield),\n };\n \n+static int lpm_keyfield_set_map_size[] = {\n+\t[VCAP_KFS_SGL_IP4] = ARRAY_SIZE(lpm_sgl_ip4_keyfield),\n+\t[VCAP_KFS_DBL_IP4] = ARRAY_SIZE(lpm_dbl_ip4_keyfield),\n+\t[VCAP_KFS_SGL_IP6] = ARRAY_SIZE(lpm_sgl_ip6_keyfield),\n+\t[VCAP_KFS_DBL_IP6] = ARRAY_SIZE(lpm_dbl_ip6_keyfield),\n+};\n+\n static int es2_keyfield_set_map_size[] = {\n \t[VCAP_KFS_MAC_ETYPE] = ARRAY_SIZE(es2_mac_etype_keyfield),\n \t[VCAP_KFS_ARP] = ARRAY_SIZE(es2_arp_keyfield),\n@@ -2993,6 +3107,92 @@ static const struct vcap_field es0_es0_actionfield[] = {\n \t},\n };\n \n+static const struct vcap_field lpm_arp_ptr_actionfield[] = {\n+\t[VCAP_AF_TYPE] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t},\n+\t[VCAP_AF_ARP_PTR] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 2,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_AF_ARP_PTR_REMAP_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 12,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ECMP_CNT] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 13,\n+\t\t.width = 4,\n+\t},\n+\t[VCAP_AF_RGID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 17,\n+\t\t.width = 3,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_arp_entry_actionfield[] = {\n+\t[VCAP_AF_TYPE] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t},\n+\t[VCAP_AF_MAC_MSB] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 2,\n+\t\t.width = 16,\n+\t},\n+\t[VCAP_AF_MAC_LSB] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 18,\n+\t\t.width = 32,\n+\t},\n+\t[VCAP_AF_ARP_VMID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 50,\n+\t\t.width = 7,\n+\t},\n+\t[VCAP_AF_ZERO_DMAC_CPU_QU] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 57,\n+\t\t.width = 3,\n+\t},\n+\t[VCAP_AF_SIP_RPF_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 60,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_SECUR_MATCH_VMID_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 61,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_SECUR_MATCH_MAC_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 62,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ARP_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 63,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ENCAP_ID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 64,\n+\t\t.width = 8,\n+\t},\n+\t[VCAP_AF_RSDX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 72,\n+\t\t.width = 10,\n+\t},\n+};\n+\n static const struct vcap_field es2_base_type_actionfield[] = {\n \t[VCAP_AF_HIT_ME_ONCE] = {\n \t\t.type = VCAP_FIELD_BIT,\n@@ -3101,6 +3301,19 @@ static const struct vcap_set es0_actionfield_set[] = {\n \t},\n };\n \n+static const struct vcap_set lpm_actionfield_set[] = {\n+\t[VCAP_AFS_ARP_PTR] = {\n+\t\t.type_id = 0,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+\t[VCAP_AFS_ARP_ENTRY] = {\n+\t\t.type_id = 2,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+};\n+\n static const struct vcap_set es2_actionfield_set[] = {\n \t[VCAP_AFS_BASE_TYPE] = {\n \t\t.type_id = -1,\n@@ -3124,6 +3337,11 @@ static const struct vcap_field *es0_actionfield_set_map[] = {\n \t[VCAP_AFS_ES0] = es0_es0_actionfield,\n };\n \n+static const struct vcap_field *lpm_actionfield_set_map[] = {\n+\t[VCAP_AFS_ARP_PTR] = lpm_arp_ptr_actionfield,\n+\t[VCAP_AFS_ARP_ENTRY] = lpm_arp_entry_actionfield,\n+};\n+\n static const struct vcap_field *es2_actionfield_set_map[] = {\n \t[VCAP_AFS_BASE_TYPE] = es2_base_type_actionfield,\n };\n@@ -3143,6 +3361,11 @@ static int es0_actionfield_set_map_size[] = {\n \t[VCAP_AFS_ES0] = ARRAY_SIZE(es0_es0_actionfield),\n };\n \n+static int lpm_actionfield_set_map_size[] = {\n+\t[VCAP_AFS_ARP_PTR] = ARRAY_SIZE(lpm_arp_ptr_actionfield),\n+\t[VCAP_AFS_ARP_ENTRY] = ARRAY_SIZE(lpm_arp_entry_actionfield),\n+};\n+\n static int es2_actionfield_set_map_size[] = {\n \t[VCAP_AFS_BASE_TYPE] = ARRAY_SIZE(es2_base_type_actionfield),\n };\n@@ -3308,6 +3531,82 @@ static const struct vcap_typegroup es0_x1_keyfield_set_typegroups[] = {\n \t{}\n };\n \n+static const struct vcap_typegroup lpm_x6_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 4,\n+\t\t.value = 8,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 104,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 156,\n+\t\t.width = 3,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 208,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 260,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x3_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 3,\n+\t\t.value = 4,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 104,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x2_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t\t.value = 2,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x1_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t\t.value = 1,\n+\t},\n+\t{}\n+};\n+\n static const struct vcap_typegroup es2_x12_keyfield_set_typegroups[] = {\n \t{\n \t\t.offset = 0,\n@@ -3376,6 +3675,14 @@ static const struct vcap_typegroup *es0_keyfield_set_typegroups[] = {\n \t[2] = NULL,\n };\n \n+static const struct vcap_typegroup *lpm_keyfield_set_typegroups[] = {\n+\t[6] = lpm_x6_keyfield_set_typegroups,\n+\t[3] = lpm_x3_keyfield_set_typegroups,\n+\t[2] = lpm_x2_keyfield_set_typegroups,\n+\t[1] = lpm_x1_keyfield_set_typegroups,\n+\t[13] = NULL,\n+};\n+\n static const struct vcap_typegroup *es2_keyfield_set_typegroups[] = {\n \t[12] = es2_x12_keyfield_set_typegroups,\n \t[6] = es2_x6_keyfield_set_typegroups,\n@@ -3453,6 +3760,10 @@ static const struct vcap_typegroup es0_x1_actionfield_set_typegroups[] = {\n \t{}\n };\n \n+static const struct vcap_typegroup lpm_x1_actionfield_set_typegroups[] = {\n+\t{}\n+};\n+\n static const struct vcap_typegroup es2_x3_actionfield_set_typegroups[] = {\n \t{\n \t\t.offset = 0,\n@@ -3494,6 +3805,11 @@ static const struct vcap_typegroup *es0_actionfield_set_typegroups[] = {\n \t[2] = NULL,\n };\n \n+static const struct vcap_typegroup *lpm_actionfield_set_typegroups[] = {\n+\t[1] = lpm_x1_actionfield_set_typegroups,\n+\t[13] = NULL,\n+};\n+\n static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {\n \t[3] = es2_x3_actionfield_set_typegroups,\n \t[1] = es2_x1_actionfield_set_typegroups,\n@@ -3504,6 +3820,8 @@ static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {\n static const char * const vcap_keyfield_set_names[] = {\n \t[VCAP_KFS_NO_VALUE] = \"(None)\",\n \t[VCAP_KFS_ARP] = \"VCAP_KFS_ARP\",\n+\t[VCAP_KFS_DBL_IP4] = \"VCAP_KFS_DBL_IP4\",\n+\t[VCAP_KFS_DBL_IP6] = \"VCAP_KFS_DBL_IP6\",\n \t[VCAP_KFS_ETAG] = \"VCAP_KFS_ETAG\",\n \t[VCAP_KFS_IP4_OTHER] = \"VCAP_KFS_IP4_OTHER\",\n \t[VCAP_KFS_IP4_TCP_UDP] = \"VCAP_KFS_IP4_TCP_UDP\",\n@@ -3522,6 +3840,8 @@ static const char * const vcap_keyfield_set_names[] = {\n \t[VCAP_KFS_NORMAL_7TUPLE] = \"VCAP_KFS_NORMAL_7TUPLE\",\n \t[VCAP_KFS_OAM] = \"VCAP_KFS_OAM\",\n \t[VCAP_KFS_PURE_5TUPLE_IP4] = \"VCAP_KFS_PURE_5TUPLE_IP4\",\n+\t[VCAP_KFS_SGL_IP4] = \"VCAP_KFS_SGL_IP4\",\n+\t[VCAP_KFS_SGL_IP6] = \"VCAP_KFS_SGL_IP6\",\n \t[VCAP_KFS_SMAC_SIP4] = \"VCAP_KFS_SMAC_SIP4\",\n \t[VCAP_KFS_SMAC_SIP6] = \"VCAP_KFS_SMAC_SIP6\",\n };\n@@ -3529,6 +3849,8 @@ static const char * const vcap_keyfield_set_names[] = {\n /* Actionfieldset names */\n static const char * const vcap_actionfield_set_names[] = {\n \t[VCAP_AFS_NO_VALUE] = \"(None)\",\n+\t[VCAP_AFS_ARP_ENTRY] = \"VCAP_AFS_ARP_ENTRY\",\n+\t[VCAP_AFS_ARP_PTR] = \"VCAP_AFS_ARP_PTR\",\n \t[VCAP_AFS_BASE_TYPE] = \"VCAP_AFS_BASE_TYPE\",\n \t[VCAP_AFS_CLASSIFICATION] = \"VCAP_AFS_CLASSIFICATION\",\n \t[VCAP_AFS_CLASS_REDUCED] = \"VCAP_AFS_CLASS_REDUCED\",\n@@ -3565,6 +3887,7 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_8021Q_VLAN_TAGGED_IS] = \"8021Q_VLAN_TAGGED_IS\",\n \t[VCAP_KF_8021Q_VLAN_TAGS] = \"8021Q_VLAN_TAGS\",\n \t[VCAP_KF_ACL_GRP_ID] = \"ACL_GRP_ID\",\n+\t[VCAP_KF_AFFIX] = \"AFFIX\",\n \t[VCAP_KF_ARP_ADDR_SPACE_OK_IS] = \"ARP_ADDR_SPACE_OK_IS\",\n \t[VCAP_KF_ARP_LEN_OK_IS] = \"ARP_LEN_OK_IS\",\n \t[VCAP_KF_ARP_OPCODE] = \"ARP_OPCODE\",\n@@ -3573,6 +3896,7 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_ARP_SENDER_MATCH_IS] = \"ARP_SENDER_MATCH_IS\",\n \t[VCAP_KF_ARP_TGT_MATCH_IS] = \"ARP_TGT_MATCH_IS\",\n \t[VCAP_KF_COSID_CLS] = \"COSID_CLS\",\n+\t[VCAP_KF_DST_FLAG] = \"DST_FLAG\",\n \t[VCAP_KF_ES0_ISDX_KEY_ENA] = \"ES0_ISDX_KEY_ENA\",\n \t[VCAP_KF_ETYPE] = \"ETYPE\",\n \t[VCAP_KF_ETYPE_LEN_IS] = \"ETYPE_LEN_IS\",\n@@ -3586,7 +3910,13 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_IF_IGR_PORT_MASK_RNG] = \"IF_IGR_PORT_MASK_RNG\",\n \t[VCAP_KF_IF_IGR_PORT_MASK_SEL] = \"IF_IGR_PORT_MASK_SEL\",\n \t[VCAP_KF_IF_IGR_PORT_SEL] = \"IF_IGR_PORT_SEL\",\n+\t[VCAP_KF_IP4_DIP] = \"IP4_DIP\",\n \t[VCAP_KF_IP4_IS] = \"IP4_IS\",\n+\t[VCAP_KF_IP4_SIP] = \"IP4_SIP\",\n+\t[VCAP_KF_IP4_XIP] = \"IP4_XIP\",\n+\t[VCAP_KF_IP6_DIP] = \"IP6_DIP\",\n+\t[VCAP_KF_IP6_SIP] = \"IP6_SIP\",\n+\t[VCAP_KF_IP6_XIP] = \"IP6_XIP\",\n \t[VCAP_KF_IP_MC_IS] = \"IP_MC_IS\",\n \t[VCAP_KF_IP_PAYLOAD_5TUPLE] = \"IP_PAYLOAD_5TUPLE\",\n \t[VCAP_KF_IP_SNAP_IS] = \"IP_SNAP_IS\",\n@@ -3659,6 +3989,10 @@ static const char * const vcap_keyfield_names[] = {\n static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_NO_VALUE] = \"(None)\",\n \t[VCAP_AF_ACL_ID] = \"ACL_ID\",\n+\t[VCAP_AF_ARP_ENA] = \"ARP_ENA\",\n+\t[VCAP_AF_ARP_PTR] = \"ARP_PTR\",\n+\t[VCAP_AF_ARP_PTR_REMAP_ENA] = \"ARP_PTR_REMAP_ENA\",\n+\t[VCAP_AF_ARP_VMID] = \"ARP_VMID\",\n \t[VCAP_AF_CLS_VID_SEL] = \"CLS_VID_SEL\",\n \t[VCAP_AF_CNT_ID] = \"CNT_ID\",\n \t[VCAP_AF_COPY_PORT_NUM] = \"COPY_PORT_NUM\",\n@@ -3676,6 +4010,8 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_DSCP_ENA] = \"DSCP_ENA\",\n \t[VCAP_AF_DSCP_SEL] = \"DSCP_SEL\",\n \t[VCAP_AF_DSCP_VAL] = \"DSCP_VAL\",\n+\t[VCAP_AF_ECMP_CNT] = \"ECMP_CNT\",\n+\t[VCAP_AF_ENCAP_ID] = \"ENCAP_ID\",\n \t[VCAP_AF_ES2_REW_CMD] = \"ES2_REW_CMD\",\n \t[VCAP_AF_ESDX] = \"ESDX\",\n \t[VCAP_AF_FWD_KILL_ENA] = \"FWD_KILL_ENA\",\n@@ -3690,6 +4026,8 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_ISDX_VAL] = \"ISDX_VAL\",\n \t[VCAP_AF_LOOP_ENA] = \"LOOP_ENA\",\n \t[VCAP_AF_LRN_DIS] = \"LRN_DIS\",\n+\t[VCAP_AF_MAC_LSB] = \"MAC_LSB\",\n+\t[VCAP_AF_MAC_MSB] = \"MAC_MSB\",\n \t[VCAP_AF_MAP_IDX] = \"MAP_IDX\",\n \t[VCAP_AF_MAP_KEY] = \"MAP_KEY\",\n \t[VCAP_AF_MAP_LOOKUP_SEL] = \"MAP_LOOKUP_SEL\",\n@@ -3723,7 +4061,12 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_QOS_ENA] = \"QOS_ENA\",\n \t[VCAP_AF_QOS_VAL] = \"QOS_VAL\",\n \t[VCAP_AF_REW_OP] = \"REW_OP\",\n+\t[VCAP_AF_RGID] = \"RGID\",\n+\t[VCAP_AF_RSDX] = \"RSDX\",\n \t[VCAP_AF_RT_DIS] = \"RT_DIS\",\n+\t[VCAP_AF_SECUR_MATCH_MAC_ENA] = \"SECUR_MATCH_MAC_ENA\",\n+\t[VCAP_AF_SECUR_MATCH_VMID_ENA] = \"SECUR_MATCH_VMID_ENA\",\n+\t[VCAP_AF_SIP_RPF_ENA] = \"SIP_RPF_ENA\",\n \t[VCAP_AF_SWAP_MACS_ENA] = \"SWAP_MACS_ENA\",\n \t[VCAP_AF_TAG_A_DEI_SEL] = \"TAG_A_DEI_SEL\",\n \t[VCAP_AF_TAG_A_PCP_SEL] = \"TAG_A_PCP_SEL\",\n@@ -3743,6 +4086,7 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_VID_B_VAL] = \"VID_B_VAL\",\n \t[VCAP_AF_VID_C_VAL] = \"VID_C_VAL\",\n \t[VCAP_AF_VID_VAL] = \"VID_VAL\",\n+\t[VCAP_AF_ZERO_DMAC_CPU_QU] = \"ZERO_DMAC_CPU_QU\",\n };\n \n /* VCAPs */\n@@ -3810,6 +4154,27 @@ const struct vcap_info lan969x_vcaps[] = {\n \t\t.keyfield_set_typegroups = es0_keyfield_set_typegroups,\n \t\t.actionfield_set_typegroups = es0_actionfield_set_typegroups,\n \t},\n+\t[VCAP_TYPE_LPM] = {\n+\t\t.name = \"lpm\",\n+\t\t.rows = 512,\n+\t\t.sw_count = 12,\n+\t\t.sw_width = 52,\n+\t\t.sticky_width = 1,\n+\t\t.act_width = 83,\n+\t\t.default_cnt = 0,\n+\t\t.require_cnt_dis = 0,\n+\t\t.version = 1,\n+\t\t.keyfield_set = lpm_keyfield_set,\n+\t\t.keyfield_set_size = ARRAY_SIZE(lpm_keyfield_set),\n+\t\t.actionfield_set = lpm_actionfield_set,\n+\t\t.actionfield_set_size = ARRAY_SIZE(lpm_actionfield_set),\n+\t\t.keyfield_set_map = lpm_keyfield_set_map,\n+\t\t.keyfield_set_map_size = lpm_keyfield_set_map_size,\n+\t\t.actionfield_set_map = lpm_actionfield_set_map,\n+\t\t.actionfield_set_map_size = lpm_actionfield_set_map_size,\n+\t\t.keyfield_set_typegroups = lpm_keyfield_set_typegroups,\n+\t\t.actionfield_set_typegroups = lpm_actionfield_set_typegroups,\n+\t},\n \t[VCAP_TYPE_ES2] = {\n \t\t.name = \"es2\",\n \t\t.rows = 256,\n@@ -3835,7 +4200,7 @@ const struct vcap_info lan969x_vcaps[] = {\n \n const struct vcap_statistics lan969x_vcap_stats = {\n \t.name = \"lan969x\",\n-\t.count = 4,\n+\t.count = 5,\n \t.keyfield_set_names = vcap_keyfield_set_names,\n \t.actionfield_set_names = vcap_actionfield_set_names,\n \t.keyfield_names = vcap_keyfield_names,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c\nindex 543a1f2bf6bd9..6e6a1f833deac 100644\n--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c\n+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c\n@@ -82,4 +82,16 @@ const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[] = {\n \t\t.count = 1024,\n \t\t.ingress = false,\n \t},\n+\t{\n+\t\t.vtype = VCAP_TYPE_LPM,\n+\t\t.vinst = 0,\n+\t\t.map_id = 6,\n+\t\t.lookups = SPARX5_LPM_LOOKUPS,\n+\t\t.lookups_per_instance = SPARX5_LPM_LOOKUPS,\n+\t\t.first_cid = SPARX5_VCAP_CID_LPM_L0,\n+\t\t.last_cid = SPARX5_VCAP_CID_LPM_MAX,\n+\t\t.blockno = 5,\n+\t\t.blocks = 1,\n+\t\t.ingress = true,\n+\t},\n };\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c\nindex dad713e9ddd5b..24acdbc20dc8e 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c\n@@ -994,14 +994,22 @@ static int mchp_sparx5_probe(struct platform_device *pdev)\n \t\tgoto cleanup_ptp;\n \t}\n \n+\terr = sparx5_rr_router_init(sparx5);\n+\tif (err) {\n+\t\tdev_err(sparx5-\u003edev, \"Router initialization failed\\n\");\n+\t\tgoto cleanup_netdevs;\n+\t}\n+\n \terr = sparx5_register_notifier_blocks(sparx5);\n \tif (err) {\n \t\tdev_err(sparx5-\u003edev, \"Failed to register notifier blocks\\n\");\n-\t\tgoto cleanup_netdevs;\n+\t\tgoto cleanup_router;\n \t}\n \n \tgoto cleanup_config;\n \n+cleanup_router:\n+\tsparx5_rr_router_deinit(sparx5);\n cleanup_netdevs:\n \tsparx5_unregister_netdevs(sparx5);\n cleanup_ptp:\n@@ -1031,6 +1039,7 @@ static void mchp_sparx5_remove(struct platform_device *pdev)\n \tsparx5_unregister_notifier_blocks(sparx5);\n \tsparx5_unregister_netdevs(sparx5);\n \tsparx5_ptp_deinit(sparx5);\n+\tsparx5_rr_router_deinit(sparx5);\n \tsparx5_frame_io_deinit(sparx5);\n \tsparx5_stats_deinit(sparx5);\n \tsparx5_mact_deinit(sparx5);\n@@ -1067,6 +1076,8 @@ static const struct sparx5_consts sparx5_consts = {\n \t.qres_max_prio_idx = 630,\n \t.qres_max_colour_idx = 638,\n \t.tod_pin = 4,\n+\t.vmid_cnt = 511,\n+\t.arp_tbl_cnt = 2048,\n \t.vcaps = sparx5_vcaps,\n \t.vcaps_cfg = sparx5_vcap_inst_cfg,\n \t.vcap_stats = \u0026sparx5_vcap_stats,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h\nindex eb57b86fbe22f..2394ba5107e71 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h\n@@ -136,6 +136,10 @@ enum sparx5_feature {\n \n #define SPARX5_MAX_PTP_ID\t512\n \n+/* Must be maximum values across all L3 enabled platforms */\n+#define SPARX5_ROUTER_LEG_N_VMID 511\n+#define SPARX5_ARP_TBL_SIZE 2048\n+\n struct sparx5;\n \n struct sparx5_calendar_data {\n@@ -320,6 +324,8 @@ struct sparx5_consts {\n \tu32 qres_max_prio_idx; /* Maximum QRES prio index */\n \tu32 qres_max_colour_idx; /* Maximum QRES colour index */\n \tu32 tod_pin; /* PTP TOD pin */\n+\tu32 vmid_cnt; /* Number of router leg VMID's */\n+\tu32 arp_tbl_cnt; /* Number of ARP table entries */\n \tconst struct sparx5_vcap_inst *vcaps_cfg;\n \tconst struct vcap_info *vcaps;\n \tconst struct vcap_statistics *vcap_stats;\n@@ -434,6 +440,8 @@ struct sparx5 {\n \t/* Common root for debugfs */\n \tstruct dentry *debugfs_root;\n \tconst struct sparx5_match_data *data;\n+\t/* L3 Forwarding */\n+\tstruct sparx5_router *router;\n };\n \n /* sparx5_main.c */\n@@ -503,6 +511,42 @@ int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,\n int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid);\n void sparx5_vlan_port_apply(struct sparx5 *sparx5, struct sparx5_port *port);\n \n+/* sparx5_router.c */\n+int sparx5_rr_router_init(struct sparx5 *sparx5);\n+void sparx5_rr_router_deinit(struct sparx5 *sparx5);\n+\n+struct sparx5_rr_hw_route {\n+\tu32 vrule_id;\n+\tbool vrule_id_valid;\n+};\n+\n+struct sparx5_router {\n+\tstruct sparx5 *sparx5;\n+\tstruct notifier_block fib_nb;\n+\tstruct notifier_block netevent_nb;\n+\tstruct notifier_block inetaddr_nb;\n+\tstruct notifier_block inetaddr_valid_nb;\n+\tstruct notifier_block netdevice_nb;\n+\tstruct notifier_block inet6addr_nb;\n+\tstruct notifier_block inet6addr_valid_nb;\n+\tstruct rhashtable neigh_ht;\n+\tstruct rhashtable fib_ht;\n+\tstruct sparx5_rr_hw_route link_local; /* Trap all link-local traffic. */\n+\tstruct net_device *port_dev; /* For VCAP API. */\n+\n+\tstruct list_head fib_list; /* All fib entries, for teardown */\n+\tstruct mutex lock; /* Global router lock for all shared data. */\n+\n+\tstruct workqueue_struct *sparx5_router_owq;\n+\n+\tatomic_t legs_count;\n+\tstruct list_head leg_list;\n+\t/* Track allocated router leg indices in hw */\n+\tDECLARE_BITMAP(vmid_mask, SPARX5_ROUTER_LEG_N_VMID);\n+\t/* Track allocated arp table indices in hw */\n+\tDECLARE_BITMAP(arp_tbl_mask, SPARX5_ARP_TBL_SIZE);\n+};\n+\n /* sparx5_calendar.c */\n int sparx5_calendar_init(struct sparx5 *sparx5);\n int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h\nindex d344675136484..6cf8c191a460d 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h\n@@ -1,11 +1,11 @@\n /* SPDX-License-Identifier: GPL-2.0+\n * Microchip Sparx5 Switch driver\n *\n- * Copyright (c) 2024 Microchip Technology Inc.\n+ * Copyright (c) 2026 Microchip Technology Inc.\n */\n \n-/* This file is autogenerated by cml-utils 2024-10-04 10:40:40 +0200.\n- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b\n+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.\n+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2\n */\n \n #ifndef _SPARX5_MAIN_REGS_H_\n@@ -711,6 +711,124 @@ extern const struct sparx5_regs *regs;\n #define ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA_GET(x)\\\n \tFIELD_GET(ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA, x)\n \n+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL */\n+#define ANA_ACL_VCAP_S2_MISC_CTRL \\\n+\t__REG(TARGET_ANA_ACL, 0, 1, regs-\u003egaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \\\n+\t 416, 0, 1, 4)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA GENMASK(27, 24)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA GENMASK(23, 20)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA GENMASK(19, 16)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA GENMASK(15, 12)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA BIT(11)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE BIT(10)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE BIT(9)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA BIT(8)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA BIT(7)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL GENMASK(6, 5)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA BIT(4)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA BIT(3)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA BIT(2)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA BIT(1)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA BIT(0)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)\n+\n+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL2 */\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2 \\\n+\t__REG(TARGET_ANA_ACL, 0, 1, regs-\u003egaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \\\n+\t 420, 0, 1, 4)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT GENMASK(12, 8)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA GENMASK(7, 4)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)\n+\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA GENMASK(3, 0)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)\n+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_GET(x)\\\n+\tFIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)\n+\n /* ANA_ACL:COMMON:VCAP_S2_RLEG_STAT */\n #define ANA_ACL_VCAP_S2_RLEG_STAT(r) \\\n \t__REG(TARGET_ANA_ACL, 0, 1, regs-\u003egaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \\\n@@ -1812,6 +1930,338 @@ extern const struct sparx5_regs *regs;\n #define ANA_L3_VLAN_CTRL_VLAN_ENA_GET(x)\\\n \tFIELD_GET(ANA_L3_VLAN_CTRL_VLAN_ENA, x)\n \n+/* ANA_L3:COMMON:L3_UC_ENA */\n+#define ANA_L3_L3_UC_ENA \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, 8,\\\n+\t 0, 1, 4)\n+\n+/* SPARX5 ONLY */\n+/* ANA_L3:COMMON:L3_UC_ENA1 */\n+#define ANA_L3_L3_UC_ENA1 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 12, 0, 1, 4)\n+\n+/* SPARX5 ONLY */\n+/* ANA_L3:COMMON:L3_UC_ENA2 */\n+#define ANA_L3_L3_UC_ENA2 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 16, 0, 1, 4)\n+\n+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2 BIT(0)\n+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_SET(x)\\\n+\tFIELD_PREP(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)\n+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_GET(x)\\\n+\tFIELD_GET(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)\n+\n+/* ANA_L3:COMMON:ROUTING_CFG */\n+#define ANA_L3_ROUTING_CFG \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 104, 0, 1, 4)\n+\n+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE BIT(30)\n+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)\n+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA BIT(29)\n+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA BIT(28)\n+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA BIT(27)\n+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)\n+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE GENMASK(26, 25)\n+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)\n+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR BIT(22)\n+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)\n+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR BIT(21)\n+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)\n+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA BIT(20)\n+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA BIT(19)\n+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA BIT(18)\n+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA BIT(17)\n+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(16, 15)\n+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(13, 11)\n+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(10, 8)\n+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(7, 5)\n+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA BIT(4)\n+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA BIT(3)\n+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA BIT(2)\n+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA BIT(1)\n+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA BIT(0)\n+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)\n+\n+/* ANA_L3:COMMON:ROUTING_CFG2 */\n+#define ANA_L3_ROUTING_CFG2 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 108, 0, 1, 4)\n+\n+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA BIT(4)\n+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)\n+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA BIT(3)\n+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)\n+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA BIT(1)\n+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)\n+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA BIT(0)\n+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)\n+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)\n+\n+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA BIT(2)\n+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)\n+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)\n+\n+/* ANA_L3:COMMON:RLEG_CFG_0 */\n+#define ANA_L3_RLEG_CFG_0 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 112, 0, 1, 4)\n+\n+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(31, 8)\n+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+\n+/* ANA_L3:COMMON:RLEG_CFG_1 */\n+#define ANA_L3_RLEG_CFG_1 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 116, 0, 1, 4)\n+\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+\n+/* ANA_L3:COMMON:CPU_QU_CFG */\n+#define ANA_L3_CPU_QU_CFG \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 120, 0, 1, 4)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU GENMASK(30, 28)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU GENMASK(26, 24)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU GENMASK(22, 20)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU GENMASK(18, 16)\n+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU GENMASK(14, 12)\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU GENMASK(10, 8)\n+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU GENMASK(6, 4)\n+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)\n+\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU GENMASK(2, 0)\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)\n+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)\n+\n+/* ANA_L3:COMMON:CPU_QU_CFG2 */\n+#define ANA_L3_CPU_QU_CFG2 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 124, 0, 1, 4)\n+\n+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU GENMASK(2, 0)\n+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)\n+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)\n+\n+/* ANA_L3:COMMON:SIP_SECURE_ENA */\n+#define ANA_L3_SIP_SECURE_ENA \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 144, 0, 1, 4)\n+\n+/* ANA_L3:COMMON:SIP_SECURE_ENA1 */\n+#define ANA_L3_SIP_SECURE_ENA1 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 148, 0, 1, 4)\n+\n+/* SPARX5 ONLY */\n+/* ANA_L3:COMMON:SIP_SECURE_ENA2 */\n+#define ANA_L3_SIP_SECURE_ENA2 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 152, 0, 1, 4)\n+\n+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2 GENMASK(5, 0)\n+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_SET(x)\\\n+\tFIELD_PREP(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)\n+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_GET(x)\\\n+\tFIELD_GET(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)\n+\n+/* ANA_L3:COMMON:DIP_SECURE_ENA */\n+#define ANA_L3_DIP_SECURE_ENA \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 156, 0, 1, 4)\n+\n+/* SPARX5 ONLY */\n+/* ANA_L3:COMMON:DIP_SECURE_ENA1 */\n+#define ANA_L3_DIP_SECURE_ENA1 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 160, 0, 1, 4)\n+\n+/* SPARX5 ONLY */\n+/* ANA_L3:COMMON:DIP_SECURE_ENA2 */\n+#define ANA_L3_DIP_SECURE_ENA2 \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_COMMON], 0, 1, 184, \\\n+\t 164, 0, 1, 4)\n+\n+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2 BIT(0)\n+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_SET(x)\\\n+\tFIELD_PREP(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)\n+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_GET(x)\\\n+\tFIELD_GET(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)\n+\n+/* ANA_L3:VLAN:VMID_CFG */\n+#define ANA_L3_VMID_CFG(g) \\\n+\t__REG(TARGET_ANA_L3, 0, 1, 0, g, regs-\u003egcnt[GC_ANA_L3_VLAN], 64, 0, 0, \\\n+\t 1, 4)\n+\n+#define ANA_L3_VMID_CFG_VMID\\\n+\tGENMASK(regs-\u003efsize[FW_ANA_L3_VMID_CFG_VMID] + 0 - 1, 0)\n+#define ANA_L3_VMID_CFG_VMID_SET(x)\\\n+\tspx5_field_prep(ANA_L3_VMID_CFG_VMID, x)\n+#define ANA_L3_VMID_CFG_VMID_GET(x)\\\n+\tspx5_field_get(ANA_L3_VMID_CFG_VMID, x)\n+\n /* ANA_L3:VLAN:VLAN_CFG */\n #define ANA_L3_VLAN_CFG(g) \\\n \t__REG(TARGET_ANA_L3, 0, 1, 0, g, regs-\u003egcnt[GC_ANA_L3_VLAN], 64, 8, 0, \\\n@@ -1871,6 +2321,13 @@ extern const struct sparx5_regs *regs;\n #define ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA_GET(x)\\\n \tFIELD_GET(ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA, x)\n \n+/* LAN969X ONLY */\n+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS BIT(31)\n+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_SET(x)\\\n+\tFIELD_PREP(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)\n+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_GET(x)\\\n+\tFIELD_GET(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)\n+\n /* ANA_L3:VLAN:VLAN_MASK_CFG */\n #define ANA_L3_VLAN_MASK_CFG(g) \\\n \t__REG(TARGET_ANA_L3, 0, 1, 0, g, regs-\u003egcnt[GC_ANA_L3_VLAN], 64, 16, 0,\\\n@@ -1894,6 +2351,154 @@ extern const struct sparx5_regs *regs;\n #define ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2_GET(x)\\\n \tFIELD_GET(ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2, x)\n \n+/* ANA_L3:VMID:RLEG_CTRL */\n+#define ANA_L3_RLEG_CTRL(g) \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_VMID], g, \\\n+\t regs-\u003egcnt[GC_ANA_L3_VMID], 64, 0, 0, 1, 4)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_EVID GENMASK(31, 19)\n+#define ANA_L3_RLEG_CTRL_RLEG_EVID_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_EVID, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_EVID_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_EVID, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA BIT(18)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA BIT(17)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE GENMASK(15, 14)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE GENMASK(13, 12)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS BIT(9)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS BIT(8)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA BIT(7)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA BIT(6)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA BIT(5)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA BIT(4)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA BIT(3)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA BIT(2)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA BIT(1)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)\n+\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA BIT(0)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)\n+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)\n+\n+/* ANA_L3:ARP:ARP_CFG_0 */\n+#define ANA_L3_ARP_CFG_0(g) \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_ARP], g, \\\n+\t regs-\u003egcnt[GC_ANA_L3_ARP], 32, 0, 0, 1, 4)\n+\n+#define ANA_L3_ARP_CFG_0_MAC_MSB GENMASK(31, 16)\n+#define ANA_L3_ARP_CFG_0_MAC_MSB_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_MAC_MSB, x)\n+#define ANA_L3_ARP_CFG_0_MAC_MSB_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_MAC_MSB, x)\n+\n+#define ANA_L3_ARP_CFG_0_ARP_VMID\\\n+\tGENMASK(regs-\u003efsize[FW_ANA_L3_ARP_CFG_0_ARP_VMID] + 7 - 1, 7)\n+#define ANA_L3_ARP_CFG_0_ARP_VMID_SET(x)\\\n+\tspx5_field_prep(ANA_L3_ARP_CFG_0_ARP_VMID, x)\n+#define ANA_L3_ARP_CFG_0_ARP_VMID_GET(x)\\\n+\tspx5_field_get(ANA_L3_ARP_CFG_0_ARP_VMID, x)\n+\n+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU GENMASK(6, 4)\n+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)\n+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)\n+\n+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA BIT(3)\n+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)\n+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)\n+\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA BIT(2)\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)\n+\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA BIT(1)\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)\n+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)\n+\n+#define ANA_L3_ARP_CFG_0_ARP_ENA BIT(0)\n+#define ANA_L3_ARP_CFG_0_ARP_ENA_SET(x)\\\n+\tFIELD_PREP(ANA_L3_ARP_CFG_0_ARP_ENA, x)\n+#define ANA_L3_ARP_CFG_0_ARP_ENA_GET(x)\\\n+\tFIELD_GET(ANA_L3_ARP_CFG_0_ARP_ENA, x)\n+\n+/* ANA_L3:ARP:ARP_CFG_1 */\n+#define ANA_L3_ARP_CFG_1(g) \\\n+\t__REG(TARGET_ANA_L3, 0, 1, regs-\u003egaddr[GA_ANA_L3_ARP], g, \\\n+\t regs-\u003egcnt[GC_ANA_L3_ARP], 32, 4, 0, 1, 4)\n+\n /* ASM:DEV_STATISTICS:RX_IN_BYTES_CNT */\n #define ASM_RX_IN_BYTES_CNT(g) \\\n \t__REG(TARGET_ASM, 0, 1, 0, g, regs-\u003egcnt[GC_ASM_DEV_STATISTICS], 512, \\\n@@ -4515,6 +5120,34 @@ extern const struct sparx5_regs *regs;\n #define DSM_TAXI_CAL_CFG_CAL_PGM_SEL_GET(x)\\\n \tFIELD_GET(DSM_TAXI_CAL_CFG_CAL_PGM_SEL, x)\n \n+/* EACL:COMMON:RLEG_CFG_0 */\n+#define EACL_RLEG_CFG_0 \\\n+\t__REG(TARGET_EACL, 0, 1, regs-\u003egaddr[GA_EACL_COMMON], 0, 1, 216, 64, 0,\\\n+\t 1, 4)\n+\n+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)\n+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\\\n+\tFIELD_PREP(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\\\n+\tFIELD_GET(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+\n+/* EACL:COMMON:RLEG_CFG_1 */\n+#define EACL_RLEG_CFG_1 \\\n+\t__REG(TARGET_EACL, 0, 1, regs-\u003egaddr[GA_EACL_COMMON], 0, 1, 216, 68, 0,\\\n+\t 1, 4)\n+\n+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)\n+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\\\n+\tFIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\\\n+\tFIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+\n+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)\n+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\\\n+\tFIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\\\n+\tFIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+\n /* EACL:ES2_KEY_SELECT_PROFILE:VCAP_ES2_KEY_SEL */\n #define EACL_VCAP_ES2_KEY_SEL(g, r) \\\n \t__REG(TARGET_EACL, 0, 1, regs-\u003egaddr[GA_EACL_ES2_KEY_SELECT_PROFILE], \\\n@@ -7353,6 +7986,34 @@ extern const struct sparx5_regs *regs;\n #define REW_ES0_CTRL_ES0_LU_ENA_GET(x)\\\n \tFIELD_GET(REW_ES0_CTRL_ES0_LU_ENA, x)\n \n+/* REW:COMMON:RLEG_CFG_0 */\n+#define REW_RLEG_CFG_0 \\\n+\t__REG(TARGET_REW, 0, 1, regs-\u003egaddr[GA_REW_COMMON], 0, 1, 1232, 1156, \\\n+\t 0, 1, 4)\n+\n+#define REW_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)\n+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\\\n+\tFIELD_PREP(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\\\n+\tFIELD_GET(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)\n+\n+/* REW:COMMON:RLEG_CFG_1 */\n+#define REW_RLEG_CFG_1 \\\n+\t__REG(TARGET_REW, 0, 1, regs-\u003egaddr[GA_REW_COMMON], 0, 1, 1232, 1160, \\\n+\t 0, 1, 4)\n+\n+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)\n+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\\\n+\tFIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\\\n+\tFIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)\n+\n+#define REW_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)\n+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\\\n+\tFIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\\\n+\tFIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)\n+\n /* REW:PORT:PORT_VLAN_CFG */\n #define REW_PORT_VLAN_CFG(g) \\\n \t__REG(TARGET_REW, 0, 1, regs-\u003egaddr[GA_REW_PORT], g, \\\n@@ -7579,6 +8240,30 @@ extern const struct sparx5_regs *regs;\n #define REW_PTP_GEN_STAMP_FMT_RT_FMT_GET(x)\\\n \tFIELD_GET(REW_PTP_GEN_STAMP_FMT_RT_FMT, x)\n \n+/* REW:VMID:RLEG_CTRL */\n+#define REW_RLEG_CTRL(g) \\\n+\t__REG(TARGET_REW, 0, 1, regs-\u003egaddr[GA_REW_VMID], g, \\\n+\t regs-\u003egcnt[GC_REW_VMID], 4, 0, 0, 1, 4)\n+\n+#define REW_RLEG_CTRL_DECAP_IRLEG\\\n+\tGENMASK(regs-\u003efsize[FW_REW_RLEG_CTRL_DECAP_IRLEG] + 13 - 1, 13)\n+#define REW_RLEG_CTRL_DECAP_IRLEG_SET(x)\\\n+\tspx5_field_prep(REW_RLEG_CTRL_DECAP_IRLEG, x)\n+#define REW_RLEG_CTRL_DECAP_IRLEG_GET(x)\\\n+\tspx5_field_get(REW_RLEG_CTRL_DECAP_IRLEG, x)\n+\n+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED BIT(12)\n+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_SET(x)\\\n+\tFIELD_PREP(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)\n+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_GET(x)\\\n+\tFIELD_GET(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)\n+\n+#define REW_RLEG_CTRL_RLEG_EVID GENMASK(11, 0)\n+#define REW_RLEG_CTRL_RLEG_EVID_SET(x)\\\n+\tFIELD_PREP(REW_RLEG_CTRL_RLEG_EVID, x)\n+#define REW_RLEG_CTRL_RLEG_EVID_GET(x)\\\n+\tFIELD_GET(REW_RLEG_CTRL_RLEG_EVID, x)\n+\n /* REW:RAM_CTRL:RAM_INIT */\n #define REW_RAM_INIT \\\n \t__REG(TARGET_REW, 0, 1, regs-\u003egaddr[GA_REW_RAM_CTRL], 0, 1, 4, 0, 0, 1,\\\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c\nindex 3863f954bd832..36e772cfdb6df 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c\n@@ -1,11 +1,11 @@\n // SPDX-License-Identifier: GPL-2.0+\n /* Microchip Sparx5 Switch driver\n *\n- * Copyright (c) 2024 Microchip Technology Inc.\n+ * Copyright (c) 2026 Microchip Technology Inc.\n */\n \n-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.\n- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b\n+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.\n+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2\n */\n \n #include \"sparx5_regs.h\"\n@@ -77,12 +77,16 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {\n \t[GA_ANA_CL_COMMON] = 166912,\n \t[GA_ANA_L2_COMMON] = 566024,\n \t[GA_ANA_L3_COMMON] = 493632,\n+\t[GA_ANA_L3_VMID] = 458752,\n+\t[GA_ANA_L3_ARP_PTR_REMAP] = 493824,\n+\t[GA_ANA_L3_ARP] = 327680,\n \t[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 491460,\n \t[GA_ASM_CFG] = 33280,\n \t[GA_ASM_PFC_TIMER_CFG] = 34716,\n \t[GA_ASM_LBK_WM_CFG] = 34744,\n \t[GA_ASM_LBK_MISC_CFG] = 34756,\n \t[GA_ASM_RAM_CTRL] = 34832,\n+\t[GA_EACL_COMMON] = 118480,\n \t[GA_EACL_ES2_KEY_SELECT_PROFILE] = 149504,\n \t[GA_EACL_CNT_TBL] = 122880,\n \t[GA_EACL_POL_CFG] = 150608,\n@@ -103,6 +107,7 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {\n \t[GA_QSYS_RAM_CTRL] = 2344,\n \t[GA_REW_COMMON] = 387264,\n \t[GA_REW_PORT] = 360448,\n+\t[GA_REW_VMID] = 389120,\n \t[GA_REW_VOE_PORT_LM_CNT] = 393216,\n \t[GA_REW_RAM_CTRL] = 378696,\n \t[GA_VOP_RAM_CTRL] = 279176,\n@@ -124,6 +129,8 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {\n \t[GC_ANA_L2_ISDX_LIMIT] = 1536,\n \t[GC_ANA_L2_ISDX] = 4096,\n \t[GC_ANA_L3_VLAN] = 5120,\n+\t[GC_ANA_L3_VMID] = 511,\n+\t[GC_ANA_L3_ARP] = 2048,\n \t[GC_ASM_DEV_STATISTICS] = 65,\n \t[GC_EACL_ES2_KEY_SELECT_PROFILE] = 138,\n \t[GC_EACL_CNT_TBL] = 2048,\n@@ -134,6 +141,7 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {\n \t[GC_PTP_PTP_PINS] = 5,\n \t[GC_PTP_PHASE_DETECTOR_CTRL] = 5,\n \t[GC_REW_PORT] = 70,\n+\t[GC_REW_VMID] = 511,\n \t[GC_REW_VOE_PORT_LM_CNT] = 520,\n };\n \n@@ -191,7 +199,12 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {\n \t[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 32,\n \t[FW_ANA_L2_DLB_CFG_DLB_IDX] = 13,\n \t[FW_ANA_L2_TSN_CFG_TSN_SFID] = 10,\n+\t[FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 32,\n+\t[FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 32,\n+\t[FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 32,\n+\t[FW_ANA_L3_VMID_CFG_VMID] = 9,\n \t[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 32,\n+\t[FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 9,\n \t[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 4,\n \t[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 9,\n \t[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 7,\n@@ -217,6 +230,7 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {\n \t[FW_QSYS_ATOP_ATOP] = 12,\n \t[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 12,\n \t[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 7,\n+\t[FW_REW_RLEG_CTRL_DECAP_IRLEG] = 9,\n \t[FW_XQS_STAT_CFG_STAT_VIEW] = 13,\n \t[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 15,\n \t[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 15,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h\nindex 585589a31e906..45b13da98af76 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h\n@@ -1,11 +1,11 @@\n /* SPDX-License-Identifier: GPL-2.0+ */\n /* Microchip Sparx5 Switch driver\n *\n- * Copyright (c) 2024 Microchip Technology Inc.\n+ * Copyright (c) 2026 Microchip Technology Inc.\n */\n \n-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.\n- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b\n+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.\n+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2\n */\n \n #ifndef _SPARX5_REGS_H_\n@@ -86,12 +86,16 @@ enum sparx5_gaddr_enum {\n \tGA_ANA_CL_COMMON,\n \tGA_ANA_L2_COMMON,\n \tGA_ANA_L3_COMMON,\n+\tGA_ANA_L3_VMID,\n+\tGA_ANA_L3_ARP_PTR_REMAP,\n+\tGA_ANA_L3_ARP,\n \tGA_ANA_L3_VLAN_ARP_L3MC_STICKY,\n \tGA_ASM_CFG,\n \tGA_ASM_PFC_TIMER_CFG,\n \tGA_ASM_LBK_WM_CFG,\n \tGA_ASM_LBK_MISC_CFG,\n \tGA_ASM_RAM_CTRL,\n+\tGA_EACL_COMMON,\n \tGA_EACL_ES2_KEY_SELECT_PROFILE,\n \tGA_EACL_CNT_TBL,\n \tGA_EACL_POL_CFG,\n@@ -112,6 +116,7 @@ enum sparx5_gaddr_enum {\n \tGA_QSYS_RAM_CTRL,\n \tGA_REW_COMMON,\n \tGA_REW_PORT,\n+\tGA_REW_VMID,\n \tGA_REW_VOE_PORT_LM_CNT,\n \tGA_REW_RAM_CTRL,\n \tGA_VOP_RAM_CTRL,\n@@ -134,6 +139,8 @@ enum sparx5_gcnt_enum {\n \tGC_ANA_L2_ISDX_LIMIT,\n \tGC_ANA_L2_ISDX,\n \tGC_ANA_L3_VLAN,\n+\tGC_ANA_L3_VMID,\n+\tGC_ANA_L3_ARP,\n \tGC_ASM_DEV_STATISTICS,\n \tGC_EACL_ES2_KEY_SELECT_PROFILE,\n \tGC_EACL_CNT_TBL,\n@@ -144,6 +151,7 @@ enum sparx5_gcnt_enum {\n \tGC_PTP_PTP_PINS,\n \tGC_PTP_PHASE_DETECTOR_CTRL,\n \tGC_REW_PORT,\n+\tGC_REW_VMID,\n \tGC_REW_VOE_PORT_LM_CNT,\n \tGCNT_LAST,\n };\n@@ -204,7 +212,12 @@ enum sparx5_fsize_enum {\n \tFW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA,\n \tFW_ANA_L2_DLB_CFG_DLB_IDX,\n \tFW_ANA_L2_TSN_CFG_TSN_SFID,\n+\tFW_ANA_L3_L3_UC_ENA_L3_UC_ENA,\n+\tFW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1,\n+\tFW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA,\n+\tFW_ANA_L3_VMID_CFG_VMID,\n \tFW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK,\n+\tFW_ANA_L3_ARP_CFG_0_ARP_VMID,\n \tFW_FDMA_CH_CFG_CH_DCB_DB_CNT,\n \tFW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL,\n \tFW_HSCH_SE_CFG_SE_DWRR_CNT,\n@@ -230,6 +243,7 @@ enum sparx5_fsize_enum {\n \tFW_QSYS_ATOP_ATOP,\n \tFW_QSYS_ATOP_TOT_CFG_ATOP_TOT,\n \tFW_REW_RTAG_ETAG_CTRL_IPE_TBL,\n+\tFW_REW_RLEG_CTRL_DECAP_IRLEG,\n \tFW_XQS_STAT_CFG_STAT_VIEW,\n \tFW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP,\n \tFW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c\nnew file mode 100644\nindex 0000000000000..5f6add84b0d6c\n--- /dev/null\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c\n@@ -0,0 +1,3021 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/* Microchip Sparx5 Switch driver\n+ *\n+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.\n+ */\n+\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003clinux/if_bridge.h\u003e\n+#include \u003clinux/if_ether.h\u003e\n+#include \u003clinux/inet.h\u003e\n+#include \u003clinux/inetdevice.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/rhashtable.h\u003e\n+#include \u003cnet/addrconf.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/fib_notifier.h\u003e\n+#include \u003cnet/ip6_fib.h\u003e\n+#include \u003cnet/ipv6.h\u003e\n+#include \u003cnet/ndisc.h\u003e\n+#include \u003cnet/neighbour.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/nexthop.h\u003e\n+\n+#include \"sparx5_main.h\"\n+#include \"sparx5_port.h\"\n+#include \"sparx5_vcap_ag_api.h\"\n+#include \"sparx5_vcap_impl.h\"\n+\n+/* The main routing objects are\n+ *\n+ * 1) Router legs, which correspond to IP interfaces. They can have multiple IPs\n+ * attached and are associated with a VLAN. Only routes with nexthops that\n+ * egress on ports that are part of a router leg are considered for\n+ * offloading.\n+ *\n+ * 2) Fib entries, which correspond to entries in the routing table.\n+ * \u003e ip route add 6.6.0.0/16 nexthop via 1.0.10.10\n+ * will create fib entry for 6.6.0.0/16, if it is offloadable.\n+ *\n+ * 3) Nexthop groups and nexthops. Each fib entry has exactly one nexthop hop.\n+ * To simplify the SW representation, we duplicate nexthops and nexthops\n+ * groups for each fib entry, even when the same nexthop is used for many\n+ * different routes.\n+ *\n+ * 4) Neigh entries, which correspond to directly connected neighbours.\n+ * These are created mainly by ARP events. Their job is to maintain the\n+ * association of L3 and L2 addresses.\n+ *\n+ * Neigh entries are anchored to the router leg they belong to, and live as\n+ * long as that leg exists. They are also referenced by any nexthops using\n+ * them as a gateway, so we keep track of those references.\n+ *\n+ * If a neighbour which is used in a nexthop group dies, we will set the mac\n+ * to zero, so traffic for this nexthop is trapped.\n+ *\n+ *\n+ * Both fib_entry and neigh_entry can trigger writes to the LPM VCAP, and own\n+ * entries in HW. Fib entries own the corresponding route associated with them.\n+ * Neigh entries own a /32 route, for traffic destined directly to the\n+ * neighbour.\n+ *\n+ * We have 3 main cases for routing:\n+ *\n+ * 1) Routes for directly connected subnets. E.g. router has IP 1.0.10.1, and\n+ * routes subnet 1.0.10.0/24.\n+ *\n+ * In this case, we have a fib_entry, with a non-gateway nexthop group. We\n+ * install a LPM VCAP route, with action type arp entry, for 1.0.10.0/24 with\n+ * a zero mac, to ensure frames destined for this subnet will be sent to the\n+ * CPU and start the ARP process.\n+ *\n+ * When we get the arp reply, we create a neigh_entry for each neighbour and\n+ * install a direct route in the LPM VCAP for this neighbour. For example,\n+ * 1.0.10.11 is sent to DMAC 0x1111110001. This is how we route directly\n+ * connected subnets.\n+ *\n+ * Moreover, each router leg maintains a list of all neighbours discovered\n+ * on it. Neighbours are anchored to the leg, not to the covering route, so\n+ * they persist as long as the leg exists, independent of which fib entry\n+ * covers their subnet.\n+ *\n+ * +-------------+ |-\u003e neigh_entry 1.0.10.10\n+ * | router leg | neigh_list |-\u003e neigh_entry 1.0.10.11\n+ * | 1.0.10.1 |------------|-\u003e neigh_entry 1.0.10.12\n+ * +-------------+\n+ *\n+ * +-------------+ +--------------+\n+ * | fib_entry | | nexthop group|\n+ * | 1.0.10.0/24 |----+ |\n+ * +-------------+ | nexthop --------\u003e NULL (Write zero mac in VCAP)\n+ * +--------------+\n+ *\n+ *\n+ * 2) Routes for non-connected subnets. E.g. we are routing subnet 6.6.0.0/16,\n+ * but we have no IP in this subnet. We are routing via nexthops, which are\n+ * directly connected. Say we have \u003e=2 nexthops.\n+ *\n+ * In this case, have a fib_entry with a gateway nexthop group. Each nexthop\n+ * points to a neigh_entry, corresponding to the gateway used for routing.\n+ *\n+ * Say we use the nexthops:\n+ *\n+ * - 1.0.11.10\n+ * - 1.0.10.10\n+ * - 1.0.9.10\n+ *\n+ * We install a LPM VCAP route for 6.6.0.0/16, which contains a pointer\n+ * to the hw arp table, and the size of the group. The ARP table\n+ * contains the Mac addresses of the nexthops. The Mac addresses are\n+ * supplied by neigh_entries.\n+ *\n+ *\n+ * +-------------+ +--------------+\n+ * | fib_entry | | nexthop group|\n+ * | 6.6.0.0/164 |----+ |\n+ * +-------------+ | nexthop --------\u003e neigh_entry 1.0.11.10\n+ * | nexthop --------\u003e neigh_entry 1.0.10.10\n+ * | nexthop --------\u003e neigh_entry 1.0.9.10\n+ * +--------------+\n+ *\n+ *\n+ * 3) Local routes for traffic destined to the router. If the router has an IP\n+ * 1.0.10.1, then we must ensure this traffic is sent to the CPU.\n+ * Therefore, we install a direct route for 1.0.10.1/32 in the LPM VCAP, with\n+ * zero mac.\n+ *\n+ * 4) All IPv6 link-local traffic is explicitly trapped.\n+ *\n+ * On the hardware side, we use the VCAP LPM and ARP table for UC IPv4\n+ * routing. HW picks the match found at the highest address in the VCAP LPM.\n+ * To ensure the longest prefix match we make sure to order the entries\n+ * according to mask length, with longer masks at higher addresses.\n+ *\n+ * It is possible to store ARP data, such as DMAC, directly in the VCAP LPM\n+ * using ARP entry actions. We do this whenever possible, so the ARP table\n+ * is only used when a route has multiple nexthops.\n+ *\n+ * With the above breakdown in mind, cases 1) and 3) use arp entries, and\n+ * case 2) use the arp table if the number of nexthops is \u003e1.\n+ *\n+ * If the DMAC written to HW is all zero, the chip will trap the frame,\n+ * redirecting it to the CPU. This is how we get the kernel to perform ARP\n+ * requests on our behalf.\n+ *\n+ * The nexthop group must be laid out at contiguous addresses in the ARP table.\n+ * The VCAP LPM stores a pointer to the bottom address in the group, and the\n+ * group size. We do not use the arp pointer remap table.\n+ *\n+ * The layout of nexthops in a nexthop group matches the layout in HW, e.g.\n+ *\n+ * nhgi-\u003enexthops[0] -\u003e arp table address n\n+ * ...\n+ * nhgi-\u003enexthops[k] -\u003e arp table address n+k\n+ *\n+ * where the n is the ARP table offset (atbl_offset) for the group.\n+ */\n+\n+#define SPARX5_MAX_ECMP_SIZE\t\t16\n+#define SPARX5_RLEG_USE_GLOBAL_BASE_MAC\t2\n+#define SPARX5_LINK_LOCAL_PREFIX_LEN\t64\n+#define SPARX5_BLACKHOLE_VMID(spx5)\t((spx5)-\u003edata-\u003econsts-\u003evmid_cnt - 1)\n+/* The rewriter field REW_RLEG_CTRL_RLEG_EVID will be written with the\n+ * blackhole vid, but it is only 12 bits wide. However, this is only used for\n+ * routing based rewrites on egress, which is not used for the blackhole.\n+ * The important field is ANA_L3_RLEG_CTRL_RLEG_EVID which is 13 bits wide.\n+ * Therefore, it is safe to use VIDs wider than 12 bits for the blackhole\n+ * vid, frames will not be forwarded into VLAN 0s port mask.\n+ */\n+#define SPARX5_BLACKHOLE_VID\t\tVLAN_N_VID\n+\n+struct sparx5_rr_fib6_entry_info {\n+\tstruct fib6_info **rt_arr;\n+\tunsigned int nrt6;\n+};\n+\n+enum sparx5_rr_l3_version {\n+\tSPARX5_IPV4 = 0,\n+\tSPARX5_IPV6,\n+};\n+\n+#define SPARX5_IADDR_LEN(v)\t\t((v) == SPARX5_IPV4 ? 32 : 128)\n+/* Order longer prefixes at high addresses. */\n+#define LPM_SORT_KEY(plen)\t\t(SPARX5_IADDR_LEN(SPARX5_IPV6) - (plen))\n+\n+struct sparx5_rr_fib_info {\n+\tunion {\n+\t\tstruct fib_entry_notifier_info fen4_info;\n+\t\tstruct sparx5_rr_fib6_entry_info fe6_info;\n+\t};\n+\tenum sparx5_rr_l3_version version;\n+};\n+\n+struct sparx5_fib_event_work {\n+\tstruct work_struct work;\n+\tstruct sparx5_rr_fib_info fi;\n+\tstruct sparx5 *sparx5;\n+\tunsigned long event;\n+};\n+\n+struct sparx5_rr_netevent_work {\n+\tstruct work_struct work;\n+\tstruct sparx5 *sparx5;\n+\tstruct neighbour *neigh;\n+\tunsigned long event;\n+};\n+\n+struct sparx5_rr_router_leg {\n+\tstruct net_device *dev;\n+\tnetdevice_tracker dev_tracker;\n+\tstruct sparx5 *sparx5;\n+\tstruct list_head leg_list_node; /* Router member */\n+\tstruct list_head neigh_list; /* Neighbours bound to this leg */\n+\tu16 vmid; /* Internal id */\n+\tu32 vid; /* VLAN id */\n+};\n+\n+struct sparx5_iaddr {\n+\tunion {\n+\t\t__be32 ipv4;\n+\t\tstruct in6_addr ipv6;\n+\t}; /* Must be first */\n+\tenum sparx5_rr_l3_version version;\n+};\n+\n+#define LPM_PROTO(iaddr) ((iaddr)-\u003eversion ? ETH_P_IPV6 : ETH_P_IP)\n+\n+struct sparx5_rr_neigh_entry {\n+\tstruct sparx5_rr_neigh_key {\n+\t\tstruct net_device *dev;\n+\t\tstruct sparx5_iaddr iaddr;\n+\t} key;\n+\tnetdevice_tracker dev_tracker;\n+\tstruct rhash_head ht_node;\n+\tstruct sparx5_rr_router_leg *leg;\n+\tstruct list_head leg_list_node; /* Member of leg-\u003eneigh_list */\n+\tstruct neigh_table *neigh_tbl; /* Kernel neighbour table */\n+\tstruct list_head nexthop_list; /* Nexthops using this neigh entry */\n+\tstruct sparx5_rr_hw_route hw_route;\n+\tunsigned char hwaddr[ETH_ALEN];\n+\tu16 vmid;\n+\tbool connected;\n+};\n+\n+struct sparx5_rr_nexthop {\n+\tstruct sparx5_rr_neigh_entry *neigh_entry;\n+\tstruct sparx5_rr_nexthop_group *grp;\n+\tstruct list_head neigh_list_node; /* Neigh entry member */\n+\tstruct neigh_table *neigh_tbl; /* Kernel neighbour table */\n+\tstruct sparx5_iaddr gw_addr;\n+\tint ifindex;\n+\tbool gateway;\n+\tbool trapped;\n+};\n+\n+struct sparx5_rr_nexthop_group_info {\n+\tstruct sparx5_rr_nexthop_group *grp;\n+\tu16 atbl_offset;\n+\tbool atbl_offset_valid;\n+\tu8 count; /* HW allows up to 16 nexthops */\n+\tstruct sparx5_rr_nexthop nexthops[] __counted_by(count);\n+};\n+\n+struct sparx5_rr_nexthop_group {\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tstruct sparx5_rr_nexthop_group_info *nhgi;\n+};\n+\n+enum sparx5_rr_fib_type {\n+\tSPARX5_RR_FIB_TYPE_INVALID = 0,\n+\tSPARX5_RR_FIB_TYPE_LOCAL,\n+\tSPARX5_RR_FIB_TYPE_UNICAST,\n+\tSPARX5_RR_FIB_TYPE_MULTICAST,\n+\tSPARX5_RR_FIB_TYPE_BLACKHOLE,\n+\tSPARX5_RR_FIB_TYPE_PROHIBIT,\n+\tSPARX5_RR_FIB_TYPE_UNREACHABLE,\n+};\n+\n+struct sparx5_rr_fib_key {\n+\tstruct sparx5_iaddr addr;\n+\tu32 prefix_len;\n+\tu32 tb_id; /* Routing table type: RT_TABLE_* */\n+};\n+\n+struct sparx5_rr_fib_entry {\n+\tstruct sparx5_rr_fib_key key;\n+\tenum sparx5_rr_fib_type type;\n+\tstruct rhash_head ht_node; /* Router member */\n+\tstruct list_head fib_node; /* Router member, all fib entries */\n+\tstruct sparx5_rr_hw_route hw_route;\n+\tstruct sparx5_rr_nexthop_group *nh_grp;\n+\tstruct sparx5_rr_fib_info fi;\n+\tbool trap;\n+\tbool offload_fail;\n+};\n+\n+struct sparx5_rr_inet6addr_event_work {\n+\tstruct work_struct work;\n+\tstruct sparx5 *sparx5;\n+\tstruct net_device *dev;\n+\tnetdevice_tracker dev_tracker;\n+\tunsigned long event;\n+};\n+\n+static void sparx5_rr_schedule_work(struct sparx5 *sparx5,\n+\t\t\t\t struct work_struct *work)\n+{\n+\tqueue_work(sparx5-\u003erouter-\u003esparx5_router_owq, work);\n+}\n+\n+static void sparx5_rr_fib_info_init(struct sparx5_rr_fib_info *fi,\n+\t\t\t\t enum sparx5_rr_l3_version version)\n+{\n+\tfi-\u003eversion = version;\n+\n+\tswitch (version) {\n+\tcase SPARX5_IPV4:\n+\t\tfi-\u003efen4_info.fi = NULL;\n+\t\treturn;\n+\tcase SPARX5_IPV6:\n+\t\tfi-\u003efe6_info.nrt6 = 0;\n+\t\tfi-\u003efe6_info.rt_arr = NULL;\n+\t\treturn;\n+\t}\n+}\n+\n+/* Return number of nexthops. */\n+static int sparx5_rr_fib_info_nhs(struct sparx5_rr_fib_info *fi)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn fib_info_num_path(fi-\u003efen4_info.fi);\n+\tcase SPARX5_IPV6:\n+\t\treturn fi-\u003efe6_info.nrt6;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn 0;\n+\t}\n+}\n+\n+static struct fib_nh_common *\n+sparx5_rr_fib_info_nhc(struct sparx5_rr_fib_info *fi, int nhsel)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn fib_info_nhc(fi-\u003efen4_info.fi, nhsel);\n+\tcase SPARX5_IPV6:\n+\t\treturn \u0026fi-\u003efe6_info.rt_arr[nhsel]-\u003efib6_nh-\u003enh_common;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static bool sparx5_rr_fib_info_is_nh_obj(struct sparx5_rr_fib_info *fi)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn !!fi-\u003efen4_info.fi-\u003enh;\n+\tcase SPARX5_IPV6:\n+\t\treturn !!fi-\u003efe6_info.rt_arr[0]-\u003enh;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn false;\n+\t}\n+}\n+\n+static u8 sparx5_rr_fib_info_type(struct sparx5_rr_fib_info *fi)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn fi-\u003efen4_info.type;\n+\tcase SPARX5_IPV6:\n+\t\treturn fi-\u003efe6_info.rt_arr[0]-\u003efib6_type;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn RTN_UNSPEC;\n+\t}\n+}\n+\n+static u32 sparx5_rr_fib_info_tb_id(struct sparx5_rr_fib_info *fi)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn fi-\u003efen4_info.tb_id;\n+\tcase SPARX5_IPV6:\n+\t\treturn fi-\u003efe6_info.rt_arr[0]-\u003efib6_table-\u003etb6_id;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn RT_TABLE_UNSPEC;\n+\t}\n+}\n+\n+static bool sparx5_rr_fib_info_should_ignore(struct sparx5_rr_fib_info *fi)\n+{\n+\treturn fi-\u003eversion == SPARX5_IPV6 \u0026\u0026\n+\t ipv6_addr_type(\u0026fi-\u003efe6_info.rt_arr[0]-\u003efib6_dst.addr) \u0026\n+\t\t (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL);\n+}\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+static void sparx5_rr_rt6_release(struct fib6_info *rt)\n+{\n+\tif (!rt-\u003enh)\n+\t\trt-\u003efib6_nh-\u003efib_nh_flags \u0026= ~RTNH_F_OFFLOAD;\n+\n+\tfib6_info_release(rt);\n+}\n+#else\n+static void sparx5_rr_rt6_release(struct fib6_info *rt)\n+{\n+}\n+#endif\n+\n+static void sparx5_rr_fib6_info_put(struct sparx5_rr_fib6_entry_info *fi)\n+{\n+\tfor (int i = 0; i \u003c fi-\u003enrt6; i++)\n+\t\tsparx5_rr_rt6_release(fi-\u003ert_arr[i]);\n+\n+\tkfree(fi-\u003ert_arr);\n+\tfi-\u003enrt6 = 0;\n+\tfi-\u003ert_arr = NULL;\n+}\n+\n+static void sparx5_rr_fib_info_put(struct sparx5_rr_fib_info *fi)\n+{\n+\tif (fi-\u003eversion == SPARX5_IPV4) {\n+\t\tif (fi-\u003efen4_info.fi) {\n+\t\t\tfib_info_put(fi-\u003efen4_info.fi);\n+\t\t\tfi-\u003efen4_info.fi = NULL;\n+\t\t}\n+\t\treturn;\n+\t}\n+\n+\tsparx5_rr_fib6_info_put(\u0026fi-\u003efe6_info);\n+}\n+\n+static void sparx5_rr_split_mac(unsigned char mac[ETH_ALEN], u32 split,\n+\t\t\t\tu32 *msb, u32 *lsb)\n+{\n+\tu32 mask = GENMASK(split - 1, 0);\n+\tu64 m = ether_addr_to_u64(mac);\n+\n+\t*lsb = m \u0026 mask;\n+\t*msb = m \u003e\u003e split;\n+}\n+\n+static int sparx5_rr_arp_tbl_grp_alloc(struct sparx5 *sparx5,\n+\t\t\t\t unsigned int nh_grp_size)\n+{\n+\tint offset;\n+\n+\toffset = bitmap_find_next_zero_area(sparx5-\u003erouter-\u003earp_tbl_mask,\n+\t\t\t\t\t sparx5-\u003edata-\u003econsts-\u003earp_tbl_cnt,\n+\t\t\t\t\t 0, nh_grp_size, 0);\n+\tif (offset \u003e= sparx5-\u003edata-\u003econsts-\u003earp_tbl_cnt)\n+\t\treturn -ENOMEM;\n+\n+\tbitmap_set(sparx5-\u003erouter-\u003earp_tbl_mask, offset, nh_grp_size);\n+\n+\treturn offset;\n+}\n+\n+static void sparx5_rr_arp_tbl_grp_free(struct sparx5 *sparx5,\n+\t\t\t\t unsigned int nh_grp_size, int offset)\n+{\n+\tbitmap_clear(sparx5-\u003erouter-\u003earp_tbl_mask, offset, nh_grp_size);\n+}\n+\n+static int sparx5_vmid_alloc(struct sparx5 *sparx5)\n+{\n+\tint vmid;\n+\n+\tvmid = find_first_zero_bit(sparx5-\u003erouter-\u003evmid_mask,\n+\t\t\t\t sparx5-\u003edata-\u003econsts-\u003evmid_cnt);\n+\tif (vmid \u003e= sparx5-\u003edata-\u003econsts-\u003evmid_cnt)\n+\t\treturn -ENOMEM;\n+\n+\tset_bit(vmid, sparx5-\u003erouter-\u003evmid_mask);\n+\n+\treturn vmid;\n+}\n+\n+static void sparx5_vmid_free(struct sparx5 *sparx5, u16 vmid)\n+{\n+\tclear_bit(vmid, sparx5-\u003erouter-\u003evmid_mask);\n+}\n+\n+static void sparx5_rr_nb2neigh_key(struct neighbour *n,\n+\t\t\t\t struct sparx5_rr_neigh_key *key)\n+{\n+\tmemset(key, 0, sizeof(*key));\n+\n+\t/* The primary_key, tbl-\u003efamily and dev are constant for the lifetime of\n+\t * the neighbour, so we can read them without n-\u003elock.\n+\t */\n+\tif (n-\u003etbl-\u003efamily == AF_INET) {\n+\t\tkey-\u003eiaddr.version = SPARX5_IPV4;\n+\t\tkey-\u003eiaddr.ipv4 = *(__be32 *)n-\u003eprimary_key;\n+\t} else {\n+\t\tkey-\u003eiaddr.version = SPARX5_IPV6;\n+\t\tkey-\u003eiaddr.ipv6 = *(struct in6_addr *)n-\u003eprimary_key;\n+\t}\n+\n+\tkey-\u003edev = n-\u003edev;\n+}\n+\n+static void\n+sparx5_rr_neigh_entry_offload_mark(struct sparx5_rr_neigh_entry *entry,\n+\t\t\t\t bool offloaded)\n+{\n+\tstruct neighbour *n;\n+\n+\tif (!entry-\u003eneigh_tbl)\n+\t\treturn;\n+\n+\tn = neigh_lookup(entry-\u003eneigh_tbl, \u0026entry-\u003ekey.iaddr,\n+\t\t\t entry-\u003ekey.dev);\n+\tif (!n)\n+\t\treturn;\n+\n+\twrite_lock_bh(\u0026n-\u003elock);\n+\tif (offloaded)\n+\t\tn-\u003eflags |= NTF_OFFLOADED;\n+\telse\n+\t\tn-\u003eflags \u0026= ~NTF_OFFLOADED;\n+\twrite_unlock_bh(\u0026n-\u003elock);\n+\n+\tneigh_release(n);\n+}\n+\n+static const struct rhashtable_params sparx5_neigh_ht_params = {\n+\t.key_offset = offsetof(struct sparx5_rr_neigh_entry, key),\n+\t.head_offset = offsetof(struct sparx5_rr_neigh_entry, ht_node),\n+\t.key_len = sizeof(struct sparx5_rr_neigh_key),\n+\t.automatic_shrinking = true,\n+};\n+\n+static const struct rhashtable_params sparx5_rr_fib_entry_ht_params = {\n+\t.key_offset = offsetof(struct sparx5_rr_fib_entry, key),\n+\t.head_offset = offsetof(struct sparx5_rr_fib_entry, ht_node),\n+\t.key_len = sizeof(struct sparx5_rr_fib_key),\n+\t.automatic_shrinking = true,\n+};\n+\n+static void sparx5_rr_to_fib4_key(u32 dst, int dst_len, u32 tb_id,\n+\t\t\t\t struct sparx5_rr_fib_key *key)\n+{\n+\tmemset(key, 0, sizeof(*key));\n+\tkey-\u003eaddr.version = SPARX5_IPV4;\n+\tkey-\u003eaddr.ipv4 = cpu_to_be32(dst);\n+\tkey-\u003eprefix_len = dst_len;\n+\tkey-\u003etb_id = tb_id;\n+}\n+\n+static void sparx5_rr_to_fib6_key(struct in6_addr *addr, int prefix_len,\n+\t\t\t\t u32 tb_id, struct sparx5_rr_fib_key *key)\n+{\n+\tmemset(key, 0, sizeof(*key));\n+\tkey-\u003eaddr.version = SPARX5_IPV6;\n+\tmemcpy(\u0026key-\u003eaddr.ipv6, addr, sizeof(*addr));\n+\tkey-\u003eprefix_len = prefix_len;\n+\tkey-\u003etb_id = tb_id;\n+}\n+\n+static void sparx5_rr_fib_info_to_fib_key(struct sparx5_rr_fib_info *fi,\n+\t\t\t\t\t struct sparx5_rr_fib_key *key)\n+{\n+\tstruct fib_entry_notifier_info *fen_info;\n+\tstruct fib6_info *rt;\n+\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\tfen_info = \u0026fi-\u003efen4_info;\n+\t\tsparx5_rr_to_fib4_key(fen_info-\u003edst, fen_info-\u003edst_len,\n+\t\t\t\t fen_info-\u003etb_id, key);\n+\t\treturn;\n+\tcase SPARX5_IPV6:\n+\t\trt = fi-\u003efe6_info.rt_arr[0];\n+\n+\t\tsparx5_rr_to_fib6_key(\u0026rt-\u003efib6_dst.addr, rt-\u003efib6_dst.plen,\n+\t\t\t\t rt-\u003efib6_table-\u003etb6_id, key);\n+\t\treturn;\n+\t}\n+}\n+\n+static void sparx5_rr_inet6_make_mask_le(int logmask, u8 *mask)\n+{\n+\t/* Caller must ensure 0 \u003c= logmask \u003c= 128 */\n+\tint byte_prefix = logmask / BITS_PER_BYTE;\n+\tint rem = logmask % BITS_PER_BYTE;\n+\n+\tmemset(mask, 0, 16);\n+\n+\tfor (int i = 0; i \u003c byte_prefix; i++)\n+\t\tmask[15 - i] = 0xff;\n+\n+\tif (rem)\n+\t\tmask[15 - byte_prefix] = GENMASK(7, 7 - rem + 1);\n+}\n+\n+static int sparx5_rr_lpm_rule_xip_add(struct vcap_rule *rule,\n+\t\t\t\t struct sparx5_iaddr *addr, u32 prefix_len)\n+{\n+\tstruct vcap_u128_key addr_key;\n+\tu32 mask, iaddr;\n+\n+\tswitch (addr-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\tmask = ntohl(inet_make_mask(prefix_len));\n+\t\tiaddr = ntohl(addr-\u003eipv4);\n+\n+\t\treturn vcap_rule_add_key_u32(rule, VCAP_KF_IP4_XIP, iaddr,\n+\t\t\t\t\t mask);\n+\tcase SPARX5_IPV6:\n+\t\tsparx5_rr_inet6_make_mask_le(prefix_len, addr_key.mask);\n+\t\tvcap_netbytes_copy(addr_key.value, addr-\u003eipv6.s6_addr, 16);\n+\n+\t\treturn vcap_rule_add_key_u128(rule, VCAP_KF_IP6_XIP, \u0026addr_key);\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn -EINVAL;\n+\t}\n+}\n+\n+static struct sparx5_rr_router_leg *\n+sparx5_rr_leg_find_by_dev(struct sparx5 *sparx5, struct net_device *dev)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\n+\tlist_for_each_entry(leg, \u0026sparx5-\u003erouter-\u003eleg_list, leg_list_node) {\n+\t\tif (leg-\u003edev == dev)\n+\t\t\treturn leg;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static struct sparx5_rr_fib_entry *\n+sparx5_rr_fib_entry_lookup(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key)\n+{\n+\treturn rhashtable_lookup_fast(\u0026sparx5-\u003erouter-\u003efib_ht, key,\n+\t\t\t\t sparx5_rr_fib_entry_ht_params);\n+}\n+\n+static int sparx5_rr_fib_entry_insert(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\treturn rhashtable_insert_fast(\u0026sparx5-\u003erouter-\u003efib_ht,\n+\t\t\t\t \u0026fib_entry-\u003eht_node,\n+\t\t\t\t sparx5_rr_fib_entry_ht_params);\n+}\n+\n+static void sparx5_rr_fib_entry_remove(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\trhashtable_remove_fast(\u0026sparx5-\u003erouter-\u003efib_ht, \u0026fib_entry-\u003eht_node,\n+\t\t\t sparx5_rr_fib_entry_ht_params);\n+}\n+\n+static struct sparx5_rr_neigh_entry *\n+sparx5_rr_neigh_entry_lookup(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_neigh_key *key)\n+{\n+\treturn rhashtable_lookup_fast(\u0026sparx5-\u003erouter-\u003eneigh_ht, key,\n+\t\t\t\t sparx5_neigh_ht_params);\n+}\n+\n+static int sparx5_rr_neigh_entry_insert(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_neigh_entry *entry)\n+{\n+\treturn rhashtable_insert_fast(\u0026sparx5-\u003erouter-\u003eneigh_ht,\n+\t\t\t\t \u0026entry-\u003eht_node, sparx5_neigh_ht_params);\n+}\n+\n+static void sparx5_rr_neigh_entry_remove(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_neigh_entry *entry)\n+{\n+\trhashtable_remove_fast(\u0026sparx5-\u003erouter-\u003eneigh_ht, \u0026entry-\u003eht_node,\n+\t\t\t sparx5_neigh_ht_params);\n+}\n+\n+static int sparx5_lower_dev_walk(struct net_device *lower_dev,\n+\t\t\t\t struct netdev_nested_priv *priv)\n+{\n+\tint ret = 0;\n+\n+\tif (sparx5_netdevice_check(lower_dev)) {\n+\t\tpriv-\u003edata = (void *)netdev_priv(lower_dev);\n+\t\tret = 1;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static struct sparx5_port *\n+sparx5_port_dev_lower_find_rcu(struct net_device *dev)\n+{\n+\tstruct netdev_nested_priv priv = {\n+\t\t.data = NULL,\n+\t};\n+\n+\tif (sparx5_netdevice_check(dev))\n+\t\treturn netdev_priv(dev);\n+\n+\tnetdev_walk_all_lower_dev_rcu(dev, sparx5_lower_dev_walk, \u0026priv);\n+\n+\treturn priv.data;\n+}\n+\n+static struct sparx5_port *sparx5_port_dev_lower_find(struct net_device *dev)\n+{\n+\tstruct sparx5_port *port;\n+\n+\trcu_read_lock();\n+\tport = sparx5_port_dev_lower_find_rcu(dev);\n+\trcu_read_unlock();\n+\n+\treturn port;\n+}\n+\n+static struct sparx5_rr_neigh_entry *\n+sparx5_rr_neigh_entry_alloc(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_neigh_key *key,\n+\t\t\t struct sparx5_rr_router_leg *leg)\n+{\n+\tstruct sparx5_rr_neigh_entry *entry;\n+\n+\tentry = kzalloc_obj(*entry);\n+\tif (!entry)\n+\t\treturn NULL;\n+\n+\tmemcpy(\u0026entry-\u003ekey, key, sizeof(*key));\n+\tentry-\u003evmid = leg-\u003evmid;\n+\tentry-\u003eleg = leg;\n+\n+\tswitch (key-\u003eiaddr.version) {\n+\tcase SPARX5_IPV4:\n+\t\tentry-\u003eneigh_tbl = \u0026arp_tbl;\n+\t\tbreak;\n+\tcase SPARX5_IPV6:\n+#if IS_ENABLED(CONFIG_IPV6)\n+\t\tentry-\u003eneigh_tbl = \u0026nd_tbl;\n+\t\tbreak;\n+#else\n+\t\tkfree(entry);\n+\t\treturn NULL;\n+#endif\n+\t}\n+\n+\tINIT_LIST_HEAD(\u0026entry-\u003enexthop_list);\n+\tINIT_LIST_HEAD(\u0026entry-\u003eleg_list_node);\n+\n+\treturn entry;\n+}\n+\n+static struct sparx5_rr_neigh_entry *\n+sparx5_rr_neigh_entry_create(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_neigh_key *key)\n+{\n+\tstruct sparx5_rr_neigh_entry *entry;\n+\tstruct sparx5_rr_router_leg *leg;\n+\tstruct sparx5_port *port_below;\n+\tint err;\n+\n+\tport_below = sparx5_port_dev_lower_find(key-\u003edev);\n+\tif (!port_below)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tleg = sparx5_rr_leg_find_by_dev(sparx5, key-\u003edev);\n+\tif (!leg)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tentry = sparx5_rr_neigh_entry_alloc(sparx5, key, leg);\n+\tif (!entry)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\terr = sparx5_rr_neigh_entry_insert(sparx5, entry);\n+\tif (err)\n+\t\tgoto err_insert;\n+\n+\tlist_add(\u0026entry-\u003eleg_list_node, \u0026leg-\u003eneigh_list);\n+\n+\tnetdev_hold(entry-\u003ekey.dev, \u0026entry-\u003edev_tracker, GFP_KERNEL);\n+\n+\treturn entry;\n+\n+err_insert:\n+\tkfree(entry);\n+\treturn ERR_PTR(err);\n+}\n+\n+static int sparx5_rr_nexthop_neigh_init(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_router_leg *leg,\n+\t\t\t\t\tstruct sparx5_rr_nexthop *nh)\n+{\n+\tstruct sparx5_rr_neigh_entry *neigh_entry;\n+\tstruct net_device *dev = leg-\u003edev;\n+\tstruct sparx5_rr_neigh_key key;\n+\tstruct neighbour *n;\n+\tint err = 0;\n+\n+\tif (!nh-\u003egateway || nh-\u003eneigh_entry || !nh-\u003eneigh_tbl)\n+\t\treturn 0;\n+\n+\t/* Look up neighbor in the global neighbor table. Takes ref to n. */\n+\tn = neigh_lookup(nh-\u003eneigh_tbl, \u0026nh-\u003egw_addr, dev);\n+\tif (!n) {\n+\t\tn = neigh_create(nh-\u003eneigh_tbl, \u0026nh-\u003egw_addr, dev);\n+\t\tif (IS_ERR(n))\n+\t\t\treturn PTR_ERR(n);\n+\t\t/* Start arp process */\n+\t\tneigh_event_send(n, NULL);\n+\t}\n+\n+\tsparx5_rr_nb2neigh_key(n, \u0026key);\n+\n+\tneigh_entry = sparx5_rr_neigh_entry_lookup(sparx5, \u0026key);\n+\tif (!neigh_entry) {\n+\t\tneigh_entry = sparx5_rr_neigh_entry_create(sparx5, \u0026key);\n+\t\tif (IS_ERR(neigh_entry)) {\n+\t\t\terr = PTR_ERR(neigh_entry);\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tnh-\u003eneigh_entry = neigh_entry;\n+\tlist_add_tail(\u0026nh-\u003eneigh_list_node, \u0026neigh_entry-\u003enexthop_list);\n+\n+out:\n+\tneigh_release(n);\n+\treturn err;\n+}\n+\n+static void sparx5_rr_neigh_entry_destroy(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_neigh_entry *entry)\n+{\n+\tWARN_ON(entry-\u003ehw_route.vrule_id_valid);\n+\n+\tif (entry-\u003eleg)\n+\t\tlist_del(\u0026entry-\u003eleg_list_node);\n+\n+\tsparx5_rr_neigh_entry_offload_mark(entry, false);\n+\tsparx5_rr_neigh_entry_remove(sparx5, entry);\n+\tnetdev_put(entry-\u003ekey.dev, \u0026entry-\u003edev_tracker);\n+\n+\tkfree(entry);\n+}\n+\n+static void sparx5_rr_neigh_entry_put(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_neigh_entry *neigh_entry)\n+{\n+\tif (neigh_entry \u0026\u0026 list_empty(\u0026neigh_entry-\u003enexthop_list) \u0026\u0026\n+\t !neigh_entry-\u003ehw_route.vrule_id_valid)\n+\t\tsparx5_rr_neigh_entry_destroy(sparx5, neigh_entry);\n+}\n+\n+static void sparx5_rr_nexthop_deinit(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_nexthop *nh)\n+{\n+\tstruct sparx5_rr_neigh_entry *neigh_entry = nh-\u003eneigh_entry;\n+\n+\tif (neigh_entry) {\n+\t\tlist_del(\u0026nh-\u003eneigh_list_node);\n+\t\tsparx5_rr_neigh_entry_put(sparx5, neigh_entry);\n+\t}\n+\n+\tnh-\u003eneigh_entry = NULL;\n+}\n+\n+static int sparx5_rr_nexthop_init(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_nexthop_group *nh_grp,\n+\t\t\t\t struct sparx5_rr_nexthop *nh,\n+\t\t\t\t struct fib_nh_common *fnhc)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\n+\tnh-\u003eifindex = -1;\n+\tnh-\u003egrp = nh_grp;\n+\tnh-\u003egateway = fnhc-\u003enhc_gw_family != 0;\n+\tnh-\u003etrapped = false;\n+\tnh-\u003eneigh_entry = NULL;\n+\tnh-\u003eneigh_tbl = NULL;\n+\n+\tmemset(\u0026nh-\u003egw_addr, 0, sizeof(nh-\u003egw_addr));\n+\n+\tif (!nh-\u003egateway)\n+\t\treturn 0;\n+\n+\tswitch (fnhc-\u003enhc_gw_family) {\n+\tcase AF_INET:\n+\t\tnh-\u003egw_addr.version = SPARX5_IPV4;\n+\t\tnh-\u003egw_addr.ipv4 = fnhc-\u003enhc_gw.ipv4;\n+\t\tnh-\u003eneigh_tbl = \u0026arp_tbl;\n+\t\tbreak;\n+\tcase AF_INET6:\n+\t\tnh-\u003egw_addr.version = SPARX5_IPV6;\n+\t\tnh-\u003egw_addr.ipv6 = fnhc-\u003enhc_gw.ipv6;\n+#if IS_ENABLED(CONFIG_IPV6)\n+\t\tnh-\u003eneigh_tbl = \u0026nd_tbl;\n+\t\tbreak;\n+#else\n+\t\treturn -EINVAL;\n+#endif\n+\tdefault:\n+\t\tWARN_ON_ONCE(1); /* BUG */\n+\t\treturn 0;\n+\t}\n+\n+\t/* Blackhole route nexthops have no egress device. */\n+\tif (!fnhc-\u003enhc_dev)\n+\t\treturn 0;\n+\n+\tnh-\u003eifindex = fnhc-\u003enhc_dev-\u003eifindex;\n+\n+\t/* When a router leg is removed, all the nexthops with gateway IPs in a\n+\t * subnet governed by the leg will receive fib delete events. However,\n+\t * these delete events are received one by one. Therefore, this nexthop\n+\t * init could have been triggered by a group resize action for such an\n+\t * event, where the underlying leg is already removed.\n+\t *\n+\t * This is not an error. We handle this during offloading by\n+\t * trapping nexthops which do not have a neigh_entry. As fib deletion\n+\t * events are processed, we converge to the proper state.\n+\t */\n+\tleg = sparx5_rr_leg_find_by_dev(sparx5, fnhc-\u003enhc_dev);\n+\tif (!leg)\n+\t\treturn 0;\n+\n+\treturn sparx5_rr_nexthop_neigh_init(sparx5, leg, nh);\n+}\n+\n+static int\n+sparx5_rr_nexthop_group_info_init(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_nexthop_group *nh_grp,\n+\t\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tunsigned int nhs = sparx5_rr_fib_info_nhs(fi);\n+\tstruct sparx5_rr_nexthop_group_info *nhgi;\n+\tstruct sparx5_rr_nexthop *nh;\n+\tint err, i;\n+\n+\tnhgi = kzalloc_flex(*nhgi, nexthops, nhs);\n+\tif (!nhgi)\n+\t\treturn -ENOMEM;\n+\n+\tnh_grp-\u003enhgi = nhgi;\n+\tnhgi-\u003egrp = nh_grp;\n+\tnhgi-\u003eatbl_offset_valid = false;\n+\tnhgi-\u003eatbl_offset = 0;\n+\tnhgi-\u003ecount = nhs;\n+\n+\tfor (i = 0; i \u003c nhgi-\u003ecount; i++) {\n+\t\tstruct fib_nh_common *fnhc;\n+\n+\t\tnh = \u0026nhgi-\u003enexthops[i];\n+\t\tfnhc = sparx5_rr_fib_info_nhc(fi, i);\n+\t\terr = sparx5_rr_nexthop_init(sparx5, nh_grp, nh, fnhc);\n+\t\tif (err)\n+\t\t\tgoto err_nexthop_init;\n+\t}\n+\n+\treturn 0;\n+\n+err_nexthop_init:\n+\tfor (i--; i \u003e= 0; i--) {\n+\t\tnh = \u0026nhgi-\u003enexthops[i];\n+\t\tsparx5_rr_nexthop_deinit(sparx5, nh);\n+\t}\n+\tkfree(nhgi);\n+\treturn err;\n+}\n+\n+static void\n+sparx5_rr_nexthop_group_info_deinit(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_nexthop_group *nh_grp)\n+{\n+\tstruct sparx5_rr_nexthop_group_info *nhgi = nh_grp-\u003enhgi;\n+\tstruct sparx5_rr_nexthop *nh;\n+\tint i;\n+\n+\tWARN_ON(!nhgi-\u003ecount);\n+\tWARN_ON_ONCE(nhgi-\u003eatbl_offset_valid);\n+\n+\tfor (i = nhgi-\u003ecount - 1; i \u003e= 0; i--) {\n+\t\tnh = \u0026nhgi-\u003enexthops[i];\n+\n+\t\tsparx5_rr_nexthop_deinit(sparx5, nh);\n+\t}\n+\n+\tkfree(nhgi);\n+}\n+\n+static void sparx5_rr_arp_tbl_hw_addr_apply(struct sparx5 *sparx5,\n+\t\t\t\t\t unsigned char mac[ETH_ALEN],\n+\t\t\t\t\t u16 evmid, int offset)\n+{\n+\tu32 mac_msb, mac_lsb;\n+\n+\tsparx5_rr_split_mac(mac, 32, \u0026mac_msb, \u0026mac_lsb);\n+\n+\tspx5_rmw(ANA_L3_ARP_CFG_0_MAC_MSB_SET(mac_msb) |\n+\t\t ANA_L3_ARP_CFG_0_ARP_VMID_SET(evmid) |\n+\t\t ANA_L3_ARP_CFG_0_ARP_ENA_SET(1),\n+\t\t ANA_L3_ARP_CFG_0_ARP_ENA |\n+\t\t ANA_L3_ARP_CFG_0_ARP_VMID |\n+\t\t ANA_L3_ARP_CFG_0_MAC_MSB,\n+\t\t sparx5, ANA_L3_ARP_CFG_0(offset));\n+\n+\tspx5_wr(mac_lsb, sparx5, ANA_L3_ARP_CFG_1(offset));\n+}\n+\n+static void sparx5_rr_arp_tbl_hw_addr_clear(struct sparx5 *sparx5, int offset)\n+{\n+\tspx5_rmw(ANA_L3_ARP_CFG_0_ARP_ENA_SET(0), ANA_L3_ARP_CFG_0_ARP_ENA,\n+\t\t sparx5, ANA_L3_ARP_CFG_0(offset));\n+}\n+\n+static void\n+sparx5_rr_nh_grp_arp_tbl_grp_clear(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_nexthop_group *nh_grp)\n+{\n+\tint offset = nh_grp-\u003enhgi-\u003eatbl_offset;\n+\n+\tif (nh_grp-\u003enhgi-\u003eatbl_offset_valid) {\n+\t\tfor (u8 i = 0; i \u003c nh_grp-\u003enhgi-\u003ecount; i++)\n+\t\t\tsparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);\n+\t\tsparx5_rr_arp_tbl_grp_free(sparx5, nh_grp-\u003enhgi-\u003ecount,\n+\t\t\t\t\t offset);\n+\t}\n+\tnh_grp-\u003enhgi-\u003eatbl_offset_valid = false;\n+}\n+\n+static void sparx5_rr_nexthop_group_put(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_nexthop_group *nh_grp)\n+{\n+\tsparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, nh_grp);\n+\tsparx5_rr_nexthop_group_info_deinit(sparx5, nh_grp);\n+\tkfree(nh_grp);\n+}\n+\n+static struct sparx5_rr_nexthop_group *\n+sparx5_rr_nexthop_group_create(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tstruct sparx5_rr_nexthop_group *nh_grp;\n+\tint err;\n+\n+\tnh_grp = kzalloc_obj(*nh_grp);\n+\tif (!nh_grp)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\terr = sparx5_rr_nexthop_group_info_init(sparx5, nh_grp, \u0026fib_entry-\u003efi);\n+\tif (err)\n+\t\tgoto err_group_info_init;\n+\n+\treturn nh_grp;\n+\n+err_group_info_init:\n+\tkfree(nh_grp);\n+\treturn ERR_PTR(err);\n+}\n+\n+static enum sparx5_rr_fib_type sparx5_rr_rtm_type2fib_type(u8 type)\n+{\n+\tswitch (type) {\n+\tcase RTN_UNICAST:\n+\t\treturn SPARX5_RR_FIB_TYPE_UNICAST;\n+\tcase RTN_LOCAL:\n+\t\treturn SPARX5_RR_FIB_TYPE_LOCAL;\n+\tcase RTN_MULTICAST:\n+\t\treturn SPARX5_RR_FIB_TYPE_MULTICAST;\n+\tcase RTN_BLACKHOLE:\n+\t\treturn SPARX5_RR_FIB_TYPE_BLACKHOLE;\n+\tcase RTN_PROHIBIT:\n+\t\treturn SPARX5_RR_FIB_TYPE_PROHIBIT;\n+\tcase RTN_UNREACHABLE:\n+\t\treturn SPARX5_RR_FIB_TYPE_UNREACHABLE;\n+\tdefault:\n+\t\treturn SPARX5_RR_FIB_TYPE_INVALID;\n+\t}\n+}\n+\n+static void\n+sparx5_rr_fib_entry_fib4_info_set(struct sparx5_rr_fib_entry *fib_entry,\n+\t\t\t\t struct fib_entry_notifier_info *fen4_info)\n+{\n+\t/* Prevent the fib_info from being deleted while we store the\n+\t * fen_info\n+\t */\n+\tfib_info_hold(fen4_info-\u003efi);\n+\tmemcpy(\u0026fib_entry-\u003efi.fen4_info, fen4_info, sizeof(*fen4_info));\n+}\n+\n+static int\n+sparx5_rr_fib_entry_fib6_info_add(struct sparx5_rr_fib_entry *fib_entry,\n+\t\t\t\t struct sparx5_rr_fib6_entry_info *fib6_info)\n+{\n+\tstruct sparx5_rr_fib6_entry_info *f6i = \u0026fib_entry-\u003efi.fe6_info;\n+\tunsigned int old_ntr6, new_ntr6;\n+\tstruct fib6_info **rt_arr;\n+\n+\told_ntr6 = f6i-\u003enrt6;\n+\tnew_ntr6 = old_ntr6 + fib6_info-\u003enrt6;\n+\n+\trt_arr = kzalloc_objs(struct fib6_info *, new_ntr6);\n+\tif (!rt_arr)\n+\t\treturn -ENOMEM;\n+\n+\t/* Copy existing */\n+\tfor (int i = 0; i \u003c old_ntr6; i++)\n+\t\trt_arr[i] = f6i-\u003ert_arr[i];\n+\n+\t/* Copy new and hold fib6_info */\n+\tfor (int i = 0; i \u003c fib6_info-\u003enrt6; i++) {\n+\t\tstruct fib6_info *rt = fib6_info-\u003ert_arr[i];\n+\n+\t\trt_arr[old_ntr6 + i] = rt;\n+\t\tfib6_info_hold(rt);\n+\t}\n+\n+\t/* Free old fib6_info */\n+\tkfree(f6i-\u003ert_arr);\n+\tf6i-\u003ert_arr = rt_arr;\n+\tf6i-\u003enrt6 = new_ntr6;\n+\n+\tWARN_ON(!fib_entry-\u003efi.fe6_info.rt_arr);\n+\tWARN_ON(!fib_entry-\u003efi.fe6_info.nrt6);\n+\n+\treturn 0;\n+}\n+\n+static int\n+sparx5_rr_fib_entry_fib_info_add(struct sparx5_rr_fib_entry *fib_entry,\n+\t\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\t/* IPv4 nexthops can not be added/removed piecemeal similar to\n+\t\t * IPv6, so this is a replace in practice.\n+\t\t */\n+\t\tsparx5_rr_fib_entry_fib4_info_set(fib_entry, \u0026fi-\u003efen4_info);\n+\t\treturn 0;\n+\tcase SPARX5_IPV6:\n+\t\treturn sparx5_rr_fib_entry_fib6_info_add(fib_entry,\n+\t\t\t\t\t\t\t \u0026fi-\u003efe6_info);\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn 0;\n+\t}\n+}\n+\n+static struct sparx5_rr_fib_entry *\n+sparx5_rr_fib_entry_create(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key,\n+\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tstruct sparx5_rr_nexthop_group *nh_grp;\n+\tu8 type = sparx5_rr_fib_info_type(fi);\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tint err;\n+\n+\tfib_entry = kzalloc_obj(*fib_entry);\n+\tif (!fib_entry)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tmemcpy(\u0026fib_entry-\u003ekey, key, sizeof(*key));\n+\tsparx5_rr_fib_info_init(\u0026fib_entry-\u003efi, fi-\u003eversion);\n+\tfib_entry-\u003etype = sparx5_rr_rtm_type2fib_type(type);\n+\n+\terr = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);\n+\tif (err)\n+\t\tgoto err_fib_info_set;\n+\n+\terr = sparx5_rr_fib_entry_insert(sparx5, fib_entry);\n+\tif (err)\n+\t\tgoto err_fib_entry_insert;\n+\n+\tnh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);\n+\tif (IS_ERR(nh_grp)) {\n+\t\terr = PTR_ERR(nh_grp);\n+\t\tgoto err_nexthop_group_create;\n+\t}\n+\n+\tfib_entry-\u003enh_grp = nh_grp;\n+\tnh_grp-\u003efib_entry = fib_entry;\n+\n+\tlist_add(\u0026fib_entry-\u003efib_node, \u0026sparx5-\u003erouter-\u003efib_list);\n+\n+\treturn fib_entry;\n+\n+err_nexthop_group_create:\n+\tsparx5_rr_fib_entry_remove(sparx5, fib_entry);\n+err_fib_entry_insert:\n+\tsparx5_rr_fib_info_put(\u0026fib_entry-\u003efi);\n+err_fib_info_set:\n+\tkfree(fib_entry);\n+\n+\treturn ERR_PTR(err);\n+}\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+static void\n+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib6_entry_info *fen6_info,\n+\t\t\t\t bool offload,\n+\t\t\t\t bool trap,\n+\t\t\t\t bool offload_failed)\n+{\n+\tfor (int i = 0; i \u003c fen6_info-\u003enrt6; i++)\n+\t\tfib6_info_hw_flags_set(\u0026init_net, fen6_info-\u003ert_arr[i], offload,\n+\t\t\t\t trap, offload_failed);\n+}\n+#else\n+static void\n+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib6_entry_info *fen6_info,\n+\t\t\t\t bool offload,\n+\t\t\t\t bool trap,\n+\t\t\t\t bool offload_failed)\n+{\n+}\n+#endif\n+\n+static void\n+sparx5_rr_fib4_entry_offload_mark(struct sparx5 *sparx5,\n+\t\t\t\t struct fib_entry_notifier_info *fen4_info,\n+\t\t\t\t bool offload,\n+\t\t\t\t bool trap,\n+\t\t\t\t bool offload_failed)\n+{\n+\tstruct fib_rt_info fri;\n+\n+\tfri.fi = fen4_info-\u003efi;\n+\tfri.tb_id = fen4_info-\u003etb_id;\n+\tfri.dst = cpu_to_be32(fen4_info-\u003edst);\n+\tfri.dst_len = fen4_info-\u003edst_len;\n+\tfri.dscp = fen4_info-\u003edscp;\n+\tfri.type = fen4_info-\u003etype;\n+\tfri.offload = offload;\n+\tfri.trap = trap;\n+\tfri.offload_failed = offload_failed;\n+\n+\tfib_alias_hw_flags_set(\u0026init_net, \u0026fri);\n+}\n+\n+static void sparx5_rr_fib_info_offload_mark(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_fib_info *fi,\n+\t\t\t\t\t bool offload, bool trap,\n+\t\t\t\t\t bool offload_failed)\n+{\n+\tswitch (fi-\u003eversion) {\n+\tcase SPARX5_IPV4:\n+\t\treturn sparx5_rr_fib4_entry_offload_mark(sparx5,\n+\t\t\t\t\t\t\t \u0026fi-\u003efen4_info,\n+\t\t\t\t\t\t\t offload, trap,\n+\t\t\t\t\t\t\t offload_failed);\n+\tcase SPARX5_IPV6:\n+\t\treturn sparx5_rr_fib6_entry_offload_mark(sparx5,\n+\t\t\t\t\t\t\t \u0026fi-\u003efe6_info,\n+\t\t\t\t\t\t\t offload, trap,\n+\t\t\t\t\t\t\t offload_failed);\n+\t}\n+}\n+\n+static void\n+sparx5_rr_fib_entry_offload_mark(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tbool offload, trap, offload_failed;\n+\n+\toffload_failed = fib_entry-\u003eoffload_fail;\n+\toffload = !fib_entry-\u003eoffload_fail;\n+\ttrap = !fib_entry-\u003eoffload_fail \u0026\u0026 fib_entry-\u003etrap;\n+\n+\tsparx5_rr_fib_info_offload_mark(sparx5, \u0026fib_entry-\u003efi, offload, trap,\n+\t\t\t\t\toffload_failed);\n+}\n+\n+static int\n+sparx5_rr_lpm_arp_entry_create(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_iaddr *addr,\n+\t\t\t u32 prefix_len, unsigned char mac[ETH_ALEN],\n+\t\t\t u16 evmid, struct sparx5_rr_hw_route *hw_route)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\tu32 priority = LPM_SORT_KEY(prefix_len);\n+\tstruct vcap_rule *rule;\n+\tu32 mac_msb, mac_lsb;\n+\tint err;\n+\n+\tsparx5_rr_split_mac(mac, 32, \u0026mac_msb, \u0026mac_lsb);\n+\n+\trule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,\n+\t\t\t VCAP_USER_L3, priority, 0);\n+\tif (IS_ERR(rule))\n+\t\treturn PTR_ERR(rule);\n+\n+\terr = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);\n+\terr |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);\n+\terr |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_MSB, mac_msb);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_LSB, mac_lsb);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_VMID, evmid);\n+\terr |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_ENA, VCAP_BIT_1);\n+\n+\terr = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));\n+\tif (!err) {\n+\t\thw_route-\u003evrule_id = rule-\u003eid;\n+\t\thw_route-\u003evrule_id_valid = true;\n+\t}\n+\tvcap_free_rule(rule);\n+\treturn err;\n+}\n+\n+static int sparx5_rr_lpm_arp_entry_mod(struct sparx5 *sparx5,\n+\t\t\t\t unsigned char mac[ETH_ALEN], u16 evmid,\n+\t\t\t\t u32 vrule_id)\n+{\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\tstruct vcap_rule *vrule;\n+\tu32 mac_msb, mac_lsb;\n+\tint err;\n+\n+\tsparx5_rr_split_mac(mac, 32, \u0026mac_msb, \u0026mac_lsb);\n+\n+\tvrule = vcap_get_rule(vctrl, vrule_id);\n+\tif (IS_ERR(vrule))\n+\t\treturn -EINVAL;\n+\n+\terr = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB, mac_msb);\n+\terr |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB, mac_lsb);\n+\terr |= vcap_rule_mod_action_u32(vrule, VCAP_AF_ARP_VMID, evmid);\n+\terr |= vcap_rule_mod_action_bit(vrule, VCAP_AF_ARP_ENA, VCAP_BIT_1);\n+\n+\terr = err ? -EINVAL : vcap_mod_rule(vrule);\n+\n+\tvcap_free_rule(vrule);\n+\treturn err;\n+}\n+\n+static int\n+sparx5_rr_fib_entry_update_arp_entry(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry,\n+\t\t\t\t unsigned char mac[ETH_ALEN], u16 evmid)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\tu32 vrule_id = fib_entry-\u003ehw_route.vrule_id;\n+\tstruct vcap_rule *vrule;\n+\tu32 mac_msb, mac_lsb;\n+\tint err;\n+\n+\tsparx5_rr_split_mac(mac, 32, \u0026mac_msb, \u0026mac_lsb);\n+\n+\tvrule = vcap_get_rule(vctrl, vrule_id);\n+\tif (IS_ERR(vrule)) {\n+\t\tfib_entry-\u003ehw_route.vrule_id_valid = false;\n+\t\treturn PTR_ERR(vrule);\n+\t}\n+\n+\tswitch (vrule-\u003eactionset) {\n+\tcase VCAP_AFS_ARP_ENTRY:\n+\t\terr = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB,\n+\t\t\t\t\t mac_msb);\n+\t\terr |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB,\n+\t\t\t\t\t\tmac_lsb);\n+\n+\t\terr = err ? -EINVAL : vcap_mod_rule(vrule);\n+\t\tgoto free_rule;\n+\tcase VCAP_AFS_ARP_PTR:\n+\t\t/* Convert arp_ptr to arp_entry */\n+\t\terr = sparx5_rr_lpm_arp_entry_create(sparx5,\n+\t\t\t\t\t\t \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t\t fib_entry-\u003ekey.prefix_len,\n+\t\t\t\t\t\t mac, evmid,\n+\t\t\t\t\t\t \u0026fib_entry-\u003ehw_route);\n+\t\tif (err)\n+\t\t\tgoto free_rule;\n+\n+\t\tsparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, fib_entry-\u003enh_grp);\n+\t\terr = vcap_del_rule(vctrl, pdev, vrule_id);\n+\t\tgoto free_rule;\n+\tdefault:\n+\t\terr = -EINVAL;\n+\t\tWARN_ON(1); /* BUG */\n+\t}\n+\n+free_rule:\n+\tvcap_free_rule(vrule);\n+\n+\treturn err;\n+}\n+\n+static int sparx5_rr_lpm_arp_ptr_create(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_iaddr *addr,\n+\t\t\t\t\tu32 prefix_len, u32 arp_offset_addr,\n+\t\t\t\t\tu8 ecmp_size,\n+\t\t\t\t\tstruct sparx5_rr_hw_route *hw_route)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\tu32 priority = LPM_SORT_KEY(prefix_len);\n+\tstruct vcap_rule *rule;\n+\tint err;\n+\n+\trule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,\n+\t\t\t VCAP_USER_L3, priority, 0);\n+\tif (IS_ERR(rule))\n+\t\treturn PTR_ERR(rule);\n+\n+\terr = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);\n+\terr |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);\n+\terr |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_PTR, arp_offset_addr);\n+\terr |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_PTR_REMAP_ENA,\n+\t\t\t\t VCAP_BIT_0);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_ECMP_CNT, ecmp_size - 1);\n+\terr |= vcap_rule_add_action_u32(rule, VCAP_AF_RGID, 0);\n+\n+\terr = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));\n+\tif (!err) {\n+\t\thw_route-\u003evrule_id_valid = true;\n+\t\thw_route-\u003evrule_id = rule-\u003eid;\n+\t}\n+\n+\tvcap_free_rule(rule);\n+\treturn err;\n+}\n+\n+/* Get egress mac and vmid for nexthop. */\n+static void sparx5_rr_nexthop_egress_derive(struct sparx5_rr_nexthop *nh,\n+\t\t\t\t\t u8 *mac, u16 *vmid)\n+{\n+\tstruct sparx5_rr_neigh_entry *nh_neigh = nh-\u003eneigh_entry;\n+\n+\tnh-\u003etrapped = !nh_neigh || is_zero_ether_addr(nh_neigh-\u003ehwaddr);\n+\n+\tif (nh_neigh) {\n+\t\tmemcpy(mac, nh_neigh-\u003ehwaddr, ETH_ALEN);\n+\t\t*vmid = nh_neigh-\u003evmid;\n+\t\treturn;\n+\t}\n+\n+\teth_zero_addr(mac);\n+\t*vmid = 0;\n+}\n+\n+static int\n+sparx5_rr_fib_entry_ecmp_hw_apply(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tstruct sparx5_rr_nexthop_group_info *nhgi = fib_entry-\u003enh_grp-\u003enhgi;\n+\tunsigned char mac[ETH_ALEN] __aligned(2);\n+\tstruct sparx5_rr_nexthop *nh;\n+\tint err, i, offset;\n+\tu16 vmid;\n+\n+\toffset = sparx5_rr_arp_tbl_grp_alloc(sparx5, nhgi-\u003ecount);\n+\n+\tif (offset \u003c 0) {\n+\t\tfib_entry-\u003eoffload_fail = true;\n+\t\treturn offset;\n+\t}\n+\n+\tfor (i = 0; i \u003c nhgi-\u003ecount; i++) {\n+\t\tnh = \u0026nhgi-\u003enexthops[i];\n+\n+\t\tsparx5_rr_nexthop_egress_derive(nh, mac, \u0026vmid);\n+\n+\t\tsparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, offset + i);\n+\t}\n+\n+\terr = sparx5_rr_lpm_arp_ptr_create(sparx5,\n+\t\t\t\t\t \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t fib_entry-\u003ekey.prefix_len, offset,\n+\t\t\t\t\t nhgi-\u003ecount, \u0026fib_entry-\u003ehw_route);\n+\tif (err)\n+\t\tgoto err_arp_ptr_create;\n+\n+\tnhgi-\u003eatbl_offset = offset;\n+\tnhgi-\u003eatbl_offset_valid = true;\n+\tfib_entry-\u003eoffload_fail = false;\n+\n+\treturn 0;\n+\n+err_arp_ptr_create:\n+\tfor (i--; i \u003e= 0; i--)\n+\t\tsparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);\n+\tsparx5_rr_arp_tbl_grp_free(sparx5, nhgi-\u003ecount, offset);\n+\tfib_entry-\u003eoffload_fail = true;\n+\tnhgi-\u003eatbl_offset_valid = false;\n+\n+\treturn err;\n+}\n+\n+static int\n+sparx5_rr_fib_blackhole_hw_apply(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tunsigned char mac[ETH_ALEN];\n+\n+\t/* Hardware blackholes are implemented by:\n+\t *\n+\t * 1) Making sure traffic is not trapped with non-zero dmac.\n+\t * 2) Using reserved router leg vmid for egress.\n+\t * 3) This router leg is attached to a VLAN id \u003e 4095.\n+\t * 4) The port-mask for this VLAN is all zero.\n+\t *\n+\t * The hardware VLAN table has more than 4096 entries. The specific\n+\t * size depends on the chip. LAN969x has 4608 and Sparx5 has 5120\n+\t * entries. These additional VLAN entries can be used for internal\n+\t * logic.\n+\t *\n+\t * The port-mask for the blackhole VLAN is zero. Therefore, frames\n+\t * routed to the blackhole leg will not egress on any ports.\n+\t */\n+\teth_zero_addr(mac);\n+\tmac[5] = 0xff;\n+\n+\treturn sparx5_rr_lpm_arp_entry_create(sparx5, \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t fib_entry-\u003ekey.prefix_len, mac,\n+\t\t\t\t\t SPARX5_BLACKHOLE_VMID(sparx5),\n+\t\t\t\t\t \u0026fib_entry-\u003ehw_route);\n+}\n+\n+static int sparx5_rr_fib_entry_hw_apply(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tstruct sparx5_rr_nexthop_group_info *nhgi = fib_entry-\u003enh_grp-\u003enhgi;\n+\tunsigned char mac[ETH_ALEN] __aligned(2);\n+\tstruct sparx5_rr_nexthop *nh;\n+\tint err = 0;\n+\tu16 vmid;\n+\n+\tswitch (fib_entry-\u003etype) {\n+\tcase SPARX5_RR_FIB_TYPE_UNREACHABLE:\n+\t\tfallthrough;\n+\tcase SPARX5_RR_FIB_TYPE_PROHIBIT:\n+\t\t/* Ensure kernel can respond with correct ICMP packets. */\n+\t\tfallthrough;\n+\tcase SPARX5_RR_FIB_TYPE_LOCAL:\n+\t\t/* Trap traffic destined for device itself, to ensure\n+\t\t * device can receive traffic even when default gateways are\n+\t\t * configured.\n+\t\t */\n+\t\tfib_entry-\u003etrap = true;\n+\n+\t\t/* Trap frames with zero mac, VMID does not matter */\n+\t\teth_zero_addr(mac);\n+\t\terr = sparx5_rr_lpm_arp_entry_create(sparx5,\n+\t\t\t\t\t\t \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t\t fib_entry-\u003ekey.prefix_len,\n+\t\t\t\t\t\t mac, 0,\n+\t\t\t\t\t\t \u0026fib_entry-\u003ehw_route);\n+\t\tgoto out;\n+\n+\tcase SPARX5_RR_FIB_TYPE_UNICAST:\n+\t\tfib_entry-\u003etrap = false;\n+\n+\t\tif (!nhgi-\u003enexthops-\u003egateway) {\n+\t\t\t/* Directly connected subnet. Trap traffic so kernel\n+\t\t\t * can perform ARP/NDP on our behalf.\n+\t\t\t */\n+\t\t\teth_zero_addr(mac);\n+\t\t\terr = sparx5_rr_lpm_arp_entry_create(sparx5,\n+\t\t\t\t\t\t\t \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t\t\t fib_entry-\u003ekey.prefix_len,\n+\t\t\t\t\t\t\t mac, 0,\n+\t\t\t\t\t\t\t \u0026fib_entry-\u003ehw_route);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (nhgi-\u003ecount == 1) { /* Use arp_entry */\n+\t\t\tnh = \u0026nhgi-\u003enexthops[0];\n+\t\t\tsparx5_rr_nexthop_egress_derive(nh, mac, \u0026vmid);\n+\t\t\terr = sparx5_rr_lpm_arp_entry_create(sparx5,\n+\t\t\t\t\t\t\t \u0026fib_entry-\u003ekey.addr,\n+\t\t\t\t\t\t\t fib_entry-\u003ekey.prefix_len,\n+\t\t\t\t\t\t\t mac, vmid,\n+\t\t\t\t\t\t\t \u0026fib_entry-\u003ehw_route);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/* Multiple nexthops so we use the HW arp table. */\n+\t\terr = sparx5_rr_fib_entry_ecmp_hw_apply(sparx5,\n+\t\t\t\t\t\t\tfib_entry);\n+\t\tgoto out;\n+\n+\tcase SPARX5_RR_FIB_TYPE_BLACKHOLE:\n+\t\tfib_entry-\u003etrap = false;\n+\t\terr = sparx5_rr_fib_blackhole_hw_apply(sparx5, fib_entry);\n+\t\tgoto out;\n+\n+\tdefault:\n+\t\tdev_warn(sparx5-\u003edev, \"Fib entry offload, unhandled type=%d\\n\",\n+\t\t\t fib_entry-\u003etype);\n+\t\treturn -EINVAL;\n+\t}\n+\n+out:\n+\tfib_entry-\u003eoffload_fail = !!err;\n+\n+\treturn err;\n+}\n+\n+static void sparx5_rr_nexthop_neigh_update(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_nexthop *nh,\n+\t\t\t\t\t bool entry_connected)\n+{\n+\tunsigned char mac[ETH_ALEN] __aligned(2);\n+\tint err, nh_offset, grp_idx;\n+\tu16 vmid;\n+\n+\tif (!nh-\u003egateway)\n+\t\treturn;\n+\n+\tvmid = nh-\u003eneigh_entry-\u003evmid;\n+\n+\t/* Trap traffic with zero mac */\n+\teth_zero_addr(mac);\n+\n+\tif (entry_connected)\n+\t\tether_addr_copy(mac, nh-\u003eneigh_entry-\u003ehwaddr);\n+\n+\tif (nh-\u003etrapped \u0026\u0026 !entry_connected)\n+\t\treturn;\n+\n+\tnh-\u003etrapped = !entry_connected;\n+\n+\tif (nh-\u003egrp-\u003enhgi-\u003ecount == 1) {\n+\t\terr = sparx5_rr_fib_entry_update_arp_entry(sparx5,\n+\t\t\t\t\t\t\t nh-\u003egrp-\u003efib_entry,\n+\t\t\t\t\t\t\t mac, vmid);\n+\t\tif (err)\n+\t\t\tdev_err(sparx5-\u003edev,\n+\t\t\t\t\"Nexthop fib entry update failed\\n\");\n+\n+\t\treturn;\n+\t}\n+\n+\tif (!nh-\u003egrp-\u003enhgi-\u003eatbl_offset_valid)\n+\t\treturn;\n+\n+\tnh_offset = (int)(ptrdiff_t)(nh - nh-\u003egrp-\u003enhgi-\u003enexthops);\n+\tgrp_idx = nh-\u003egrp-\u003enhgi-\u003eatbl_offset;\n+\n+\tsparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, grp_idx + nh_offset);\n+}\n+\n+static void\n+sparx5_rr_nexthops_update_notify(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_neigh_entry *neigh_entry,\n+\t\t\t\t bool entry_connected)\n+{\n+\tstruct sparx5_rr_nexthop *nh;\n+\n+\tlist_for_each_entry(nh, \u0026neigh_entry-\u003enexthop_list, neigh_list_node)\n+\t\tsparx5_rr_nexthop_neigh_update(sparx5, nh, entry_connected);\n+}\n+\n+static int sparx5_rr_neigh_entry_hw_apply(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_neigh_entry *entry)\n+{\n+\tu32 prefix_len = SPARX5_IADDR_LEN(entry-\u003ekey.iaddr.version);\n+\n+\tif (!entry-\u003ehw_route.vrule_id_valid)\n+\t\treturn sparx5_rr_lpm_arp_entry_create(sparx5,\n+\t\t\t\t\t\t \u0026entry-\u003ekey.iaddr,\n+\t\t\t\t\t\t prefix_len,\n+\t\t\t\t\t\t entry-\u003ehwaddr,\n+\t\t\t\t\t\t entry-\u003evmid,\n+\t\t\t\t\t\t \u0026entry-\u003ehw_route);\n+\n+\treturn sparx5_rr_lpm_arp_entry_mod(sparx5, entry-\u003ehwaddr, entry-\u003evmid,\n+\t\t\t\t\t entry-\u003ehw_route.vrule_id);\n+}\n+\n+static void sparx5_rr_neigh_entry_update(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_neigh_entry *entry,\n+\t\t\t\t\t bool adding)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tbool offloaded = adding;\n+\tint err;\n+\n+\tif (!adding \u0026\u0026 !entry-\u003econnected \u0026\u0026 !entry-\u003ehw_route.vrule_id_valid)\n+\t\treturn;\n+\n+\tentry-\u003econnected = adding;\n+\n+\tif (adding) {\n+\t\terr = sparx5_rr_neigh_entry_hw_apply(sparx5, entry);\n+\t\tif (err)\n+\t\t\toffloaded = false;\n+\t} else if (entry-\u003ehw_route.vrule_id_valid) {\n+\t\tvcap_del_rule(sparx5-\u003evcap_ctrl, pdev,\n+\t\t\t entry-\u003ehw_route.vrule_id);\n+\t\tentry-\u003ehw_route.vrule_id_valid = false;\n+\t}\n+\n+\treturn sparx5_rr_neigh_entry_offload_mark(entry, offloaded);\n+}\n+\n+static void sparx5_rr_fib_entry_destroy(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\n+\tlist_del(\u0026fib_entry-\u003efib_node);\n+\tsparx5_rr_fib_entry_remove(sparx5, fib_entry);\n+\tsparx5_rr_nexthop_group_put(sparx5, fib_entry-\u003enh_grp);\n+\tif (fib_entry-\u003ehw_route.vrule_id_valid)\n+\t\tvcap_del_rule(vctrl, pdev, fib_entry-\u003ehw_route.vrule_id);\n+\tsparx5_rr_fib_info_put(\u0026fib_entry-\u003efi);\n+\tkfree(fib_entry);\n+}\n+\n+/* Update nexthop group based on current fib_info state. */\n+static int\n+sparx5_rr_entry_nexthop_group_update(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_entry *fib_entry)\n+{\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\tstruct sparx5_rr_nexthop_group *new_nh_grp;\n+\tstruct sparx5_rr_nexthop_group *old_nh_grp;\n+\tbool old_vrule_id_valid;\n+\tu32 old_vrule_id;\n+\tint err;\n+\n+\told_nh_grp = fib_entry-\u003enh_grp;\n+\told_vrule_id = fib_entry-\u003ehw_route.vrule_id;\n+\told_vrule_id_valid = fib_entry-\u003ehw_route.vrule_id_valid;\n+\n+\t/* Prepare new group in SW representation */\n+\tnew_nh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);\n+\tif (IS_ERR(new_nh_grp)) {\n+\t\tdev_warn(sparx5-\u003edev, \"Failed to create nexthop group\\n\");\n+\t\treturn PTR_ERR(new_nh_grp);\n+\t}\n+\n+\tfib_entry-\u003enh_grp = new_nh_grp;\n+\tnew_nh_grp-\u003efib_entry = fib_entry;\n+\n+\t/* Write new rule to HW */\n+\terr = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);\n+\tif (err)\n+\t\tgoto hw_apply_err;\n+\n+\t/* Clean up old rule and start routing traffic according to new rule */\n+\tif (old_vrule_id_valid \u0026\u0026 fib_entry-\u003ehw_route.vrule_id != old_vrule_id)\n+\t\tvcap_del_rule(vctrl, pdev, old_vrule_id);\n+\n+\t/* Remove old unused group */\n+\tsparx5_rr_nexthop_group_put(sparx5, old_nh_grp);\n+\n+\treturn 0;\n+\n+hw_apply_err:\n+\tfib_entry-\u003enh_grp = old_nh_grp;\n+\tnew_nh_grp-\u003efib_entry = NULL;\n+\tsparx5_rr_nexthop_group_put(sparx5, new_nh_grp);\n+\treturn err;\n+}\n+\n+static void sparx5_rr_leg_hw_init(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_router_leg *leg)\n+{\n+\t/* Associate Router leg VMID to VLAN */\n+\tspx5_rmw(ANA_L3_VMID_CFG_VMID_SET(leg-\u003evmid), ANA_L3_VMID_CFG_VMID,\n+\t\t sparx5, ANA_L3_VMID_CFG(leg-\u003evid));\n+\n+\t/* Enable Router leg for VLAN */\n+\tspx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(1),\n+\t\t ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,\n+\t\t ANA_L3_VLAN_CFG(leg-\u003evid));\n+\n+\t/* Configure router leg */\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tspx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg-\u003evid) |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(1),\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_EVID |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,\n+\t\t ANA_L3_RLEG_CTRL(leg-\u003evmid));\n+#else\n+\tspx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg-\u003evid),\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_EVID, sparx5,\n+\t\t ANA_L3_RLEG_CTRL(leg-\u003evmid));\n+#endif\n+\n+\t/* Configure egress VLAN in rewriter */\n+\tspx5_rmw(REW_RLEG_CTRL_RLEG_EVID_SET(leg-\u003evid), REW_RLEG_CTRL_RLEG_EVID,\n+\t\t sparx5, REW_RLEG_CTRL(leg-\u003evmid));\n+}\n+\n+static void sparx5_rr_leg_hw_deinit(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_router_leg *leg)\n+{\n+\t/* Disable Router leg for VLAN */\n+\tspx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(0),\n+\t\t ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,\n+\t\t ANA_L3_VLAN_CFG(leg-\u003evid));\n+\n+\t/* Disable IP UC routing on leg */\n+\tspx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(0) |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(0),\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |\n+\t\t ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,\n+\t\t ANA_L3_RLEG_CTRL(leg-\u003evmid));\n+}\n+\n+static int sparx5_rr_lpm_link_local_create(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_iaddr addr __aligned(2) = { };\n+\tunsigned char zero_mac[ETH_ALEN];\n+\n+\teth_zero_addr(zero_mac);\n+\n+\t/* Trap traffic to fe80::/64 */\n+\taddr.version = SPARX5_IPV6;\n+\taddr.ipv6.in6_u.u6_addr8[0] = 0xfe;\n+\taddr.ipv6.in6_u.u6_addr8[1] = 0x80;\n+\n+\treturn sparx5_rr_lpm_arp_entry_create(sparx5, \u0026addr,\n+\t\t\t\t\t SPARX5_LINK_LOCAL_PREFIX_LEN,\n+\t\t\t\t\t zero_mac, 0,\n+\t\t\t\t\t \u0026sparx5-\u003erouter-\u003elink_local);\n+}\n+\n+static void sparx5_rr_lpm_link_local_destroy(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_rr_hw_route *llocal = \u0026sparx5-\u003erouter-\u003elink_local;\n+\tstruct net_device *pdev = sparx5-\u003erouter-\u003eport_dev;\n+\tstruct vcap_control *vctrl = sparx5-\u003evcap_ctrl;\n+\n+\tif (!llocal-\u003evrule_id_valid)\n+\t\treturn;\n+\n+\tvcap_del_rule(vctrl, pdev, llocal-\u003evrule_id);\n+\tllocal-\u003evrule_id_valid = false;\n+}\n+\n+static struct sparx5_rr_router_leg *\n+__sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vmid,\n+\t\t u16 vid)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\n+\tleg = kzalloc_obj(*leg);\n+\tif (!leg)\n+\t\treturn NULL;\n+\n+\tINIT_LIST_HEAD(\u0026leg-\u003eleg_list_node);\n+\tINIT_LIST_HEAD(\u0026leg-\u003eneigh_list);\n+\tleg-\u003edev = dev;\n+\tleg-\u003evmid = vmid;\n+\tleg-\u003evid = vid;\n+\tleg-\u003esparx5 = sparx5;\n+\n+\treturn leg;\n+}\n+\n+/* Router legs are identified by their VMID in hw */\n+static struct sparx5_rr_router_leg *\n+sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vid)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\tint next_vmid;\n+\n+\tnext_vmid = sparx5_vmid_alloc(sparx5);\n+\tif (next_vmid \u003c 0)\n+\t\treturn NULL;\n+\n+\tleg = __sparx5_rr_leg_alloc(sparx5, dev, next_vmid, vid);\n+\tif (!leg)\n+\t\tgoto err_kzalloc;\n+\n+\treturn leg;\n+\n+err_kzalloc:\n+\tsparx5_vmid_free(sparx5, next_vmid);\n+\n+\treturn NULL;\n+}\n+\n+static void sparx5_rr_router_leg_destroy(struct sparx5_rr_router_leg *leg)\n+{\n+\tstruct sparx5_rr_neigh_entry *neigh, *neigh_tmp;\n+\tstruct sparx5 *sparx5 = leg-\u003esparx5;\n+\n+\tdev_dbg(sparx5-\u003edev, \"Leg destroy vid=%u vmid=%u dev=%s\\n\", leg-\u003evid,\n+\t\tleg-\u003evmid, leg-\u003edev ? netdev_name(leg-\u003edev) : \"blackhole\");\n+\n+\tlist_for_each_entry_safe(neigh, neigh_tmp, \u0026leg-\u003eneigh_list,\n+\t\t\t\t leg_list_node) {\n+\t\tstruct sparx5_rr_nexthop *nh, *nh_tmp;\n+\n+\t\tlist_for_each_entry_safe(nh, nh_tmp, \u0026neigh-\u003enexthop_list,\n+\t\t\t\t\t neigh_list_node) {\n+\t\t\tsparx5_rr_nexthop_neigh_update(sparx5, nh, false);\n+\t\t\tlist_del(\u0026nh-\u003eneigh_list_node);\n+\t\t\tnh-\u003eneigh_entry = NULL;\n+\t\t}\n+\n+\t\tsparx5_rr_neigh_entry_update(sparx5, neigh, false);\n+\t\tsparx5_rr_neigh_entry_destroy(sparx5, neigh);\n+\t}\n+\n+\tsparx5_rr_leg_hw_deinit(sparx5, leg);\n+\tsparx5_vmid_free(leg-\u003esparx5, leg-\u003evmid);\n+\tlist_del(\u0026leg-\u003eleg_list_node);\n+\n+\tif (leg-\u003edev) {\n+\t\tif (atomic_dec_return(\u0026sparx5-\u003erouter-\u003elegs_count) == 0)\n+\t\t\tsparx5_rr_lpm_link_local_destroy(sparx5);\n+\n+\t\tnetdev_put(leg-\u003edev, \u0026leg-\u003edev_tracker);\n+\t}\n+\tkfree(leg);\n+}\n+\n+static struct sparx5_rr_router_leg *\n+sparx5_rr_router_leg_create(struct sparx5 *sparx5, struct net_device *dev,\n+\t\t\t u16 vid)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\n+\tleg = sparx5_rr_leg_alloc(sparx5, dev, vid);\n+\tif (!leg)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\t/* Prevent net device from being freed while we have added it to a\n+\t * router leg.\n+\t */\n+\tnetdev_hold(dev, \u0026leg-\u003edev_tracker, GFP_KERNEL);\n+\n+\t/* While a router leg exists, add route to trap link-local traffic. */\n+\tif (atomic_inc_return(\u0026sparx5-\u003erouter-\u003elegs_count) == 1) {\n+\t\tif (sparx5_rr_lpm_link_local_create(sparx5))\n+\t\t\tdev_warn(sparx5-\u003edev,\n+\t\t\t\t \"Failed to create link-local route\\n\");\n+\t}\n+\n+\tlist_add(\u0026leg-\u003eleg_list_node, \u0026sparx5-\u003erouter-\u003eleg_list);\n+\tsparx5_rr_leg_hw_init(sparx5, leg);\n+\n+\tdev_dbg(sparx5-\u003edev, \"Leg create dev=%s vid=%u vmid=%u\\n\", dev-\u003ename,\n+\t\tleg-\u003evid, leg-\u003evmid);\n+\n+\treturn leg;\n+}\n+\n+static void sparx5_rr_fib4_del(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tstruct sparx5_rr_fib_key key;\n+\n+\tsparx5_rr_fib_info_to_fib_key(fi, \u0026key);\n+\n+\tfib_entry = sparx5_rr_fib_entry_lookup(sparx5, \u0026key);\n+\tif (!fib_entry)\n+\t\treturn;\n+\n+\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+}\n+\n+static bool sparx5_rr_dev_real_is_vlan_aware(struct net_device *dev)\n+{\n+\tstruct net_device *vlan_rdev;\n+\t/* Support l3 offloading for:\n+\t *\t1) upper vlan interfaces for the bridge.\n+\t */\n+\tif (is_vlan_dev(dev)) {\n+\t\tif (netif_is_bridge_port(dev))\n+\t\t\treturn false;\n+\n+\t\tvlan_rdev = vlan_dev_real_dev(dev);\n+\t\tif (sparx5_netdevice_check(vlan_rdev))\n+\t\t\treturn false;\n+\n+\t\treturn netif_is_bridge_master(vlan_rdev) \u0026\u0026\n+\t\t br_vlan_enabled(vlan_rdev) \u0026\u0026\n+\t\t sparx5_port_dev_lower_find(vlan_rdev);\n+\t}\n+\n+\treturn false;\n+}\n+\n+static bool sparx5_rr_fib_info_should_offload(struct sparx5 *sparx5,\n+\t\t\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tu32 tb_id = sparx5_rr_fib_info_tb_id(fi);\n+\tu8 type = sparx5_rr_fib_info_type(fi);\n+\tint nhs = sparx5_rr_fib_info_nhs(fi);\n+\n+\tif (!(type == RTN_UNICAST ||\n+\t type == RTN_LOCAL ||\n+\t type == RTN_BLACKHOLE ||\n+\t type == RTN_PROHIBIT ||\n+\t type == RTN_UNREACHABLE))\n+\t\treturn false;\n+\n+\tif (!(tb_id == RT_TABLE_MAIN ||\n+\t tb_id == RT_TABLE_LOCAL))\n+\t\treturn false;\n+\n+\t/* No support for nexthop objects (optimization for larger scale\n+\t * routing). Instead each route has a copy of it's nexthops.\n+\t */\n+\tif (sparx5_rr_fib_info_is_nh_obj(fi))\n+\t\treturn false;\n+\n+\t/* For IPv4 the nexthops of these route types have NULL egress device.\n+\t * However, for IPv6 the nexthops use the loopback interface, so accept\n+\t * early.\n+\t */\n+\tif (type == RTN_BLACKHOLE ||\n+\t type == RTN_PROHIBIT ||\n+\t type == RTN_UNREACHABLE)\n+\t\treturn true;\n+\n+\tif (nhs \u003e SPARX5_MAX_ECMP_SIZE)\n+\t\treturn false;\n+\n+\tfor (int i = 0; i \u003c nhs; i++) {\n+\t\tstruct fib_nh_common *nhc = sparx5_rr_fib_info_nhc(fi, i);\n+\n+\t\tif (nhc-\u003enhc_dev \u0026\u0026\n+\t\t !sparx5_rr_dev_real_is_vlan_aware(nhc-\u003enhc_dev))\n+\t\t\treturn false;\n+\n+\t\t/* HW only supports equal weight nexthops */\n+\t\tif (nhc-\u003enhc_weight != 1)\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static int sparx5_rr_fib_replace(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tu8 fi_type = sparx5_rr_fib_info_type(fi);\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tstruct sparx5_rr_fib_info old_fi;\n+\tstruct sparx5_rr_fib_key key;\n+\tint err = 0;\n+\n+\tif (sparx5_rr_fib_info_should_ignore(fi))\n+\t\treturn 0;\n+\n+\tsparx5_rr_fib_info_to_fib_key(fi, \u0026key);\n+\n+\tfib_entry = sparx5_rr_fib_entry_lookup(sparx5, \u0026key);\n+\n+\tif (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {\n+\t\t/* A previously offloadable fib, is modified to unoffloadable\n+\t\t * state, so we must remove it.\n+\t\t */\n+\t\tif (fib_entry)\n+\t\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!fib_entry) {\n+\t\t/* Holds refs to kernel fib_info */\n+\t\tfib_entry = sparx5_rr_fib_entry_create(sparx5, \u0026key, fi);\n+\t\tif (IS_ERR(fib_entry)) {\n+\t\t\tdev_warn(sparx5-\u003edev, \"Failed to create fib entry\\n\");\n+\t\t\tsparx5_rr_fib_info_offload_mark(sparx5, fi, false,\n+\t\t\t\t\t\t\tfalse, true);\n+\t\t\treturn PTR_ERR(fib_entry);\n+\t\t}\n+\n+\t\terr = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);\n+\t\tgoto out_fib_mark_offload;\n+\t}\n+\n+\t/* Save old fib_info, add new one, then release old. This ordering\n+\t * ensures fib_entry retains valid fi on allocation failure.\n+\t */\n+\told_fi = fib_entry-\u003efi;\n+\n+\t/* Clear fib_entry fi */\n+\tsparx5_rr_fib_info_init(\u0026fib_entry-\u003efi, fi-\u003eversion);\n+\n+\t/* Hold and replace with new fib_info */\n+\terr = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);\n+\tif (err) {\n+\t\tfib_entry-\u003efi = old_fi;\n+\t\tdev_err(sparx5-\u003edev, \"Failed to replace fib info\\n\");\n+\t\tsparx5_rr_fib_info_offload_mark(sparx5, fi, false, false, true);\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\t\treturn err;\n+\t}\n+\n+\t/* Release and allow any previous fib_info to be deleted */\n+\tsparx5_rr_fib_info_put(\u0026old_fi);\n+\n+\tfib_entry-\u003etype = sparx5_rr_rtm_type2fib_type(fi_type);\n+\n+\terr = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);\n+\n+out_fib_mark_offload:\n+\tfib_entry-\u003eoffload_fail = !!err;\n+\tsparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);\n+\tif (err)\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\treturn err;\n+}\n+\n+static void sparx5_rr_fib4_event_work(struct work_struct *work)\n+{\n+\tstruct sparx5_fib_event_work *fib_work =\n+\t\tcontainer_of(work, struct sparx5_fib_event_work, work);\n+\tstruct sparx5 *sparx5 = fib_work-\u003esparx5;\n+\tint err;\n+\n+\tmutex_lock(\u0026sparx5-\u003erouter-\u003elock);\n+\n+\tswitch (fib_work-\u003eevent) {\n+\tcase FIB_EVENT_ENTRY_REPLACE:\n+\t\terr = sparx5_rr_fib_replace(sparx5, \u0026fib_work-\u003efi);\n+\t\tif (err)\n+\t\t\tdev_warn(sparx5-\u003edev, \"FIB replace failed, ip=%pI4l\\n\",\n+\t\t\t\t \u0026fib_work-\u003efi.fen4_info.dst);\n+\n+\t\tbreak;\n+\tcase FIB_EVENT_ENTRY_DEL:\n+\t\tsparx5_rr_fib4_del(sparx5, \u0026fib_work-\u003efi);\n+\t\tbreak;\n+\tdefault:\n+\t\t/* FIB_EVENT_ENTRY_APPEND only occurs for IPv6. */\n+\t\tWARN_ON_ONCE(1); /* BUG */\n+\t\tbreak;\n+\t}\n+\n+\t/* Release fib_info hold for workqueue. */\n+\tsparx5_rr_fib_info_put(\u0026fib_work-\u003efi);\n+\tmutex_unlock(\u0026sparx5-\u003erouter-\u003elock);\n+\tkfree(fib_work);\n+}\n+\n+static int sparx5_rr_fib6_append(struct sparx5 *sparx5,\n+\t\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tstruct sparx5_rr_fib_key key;\n+\tint err = 0;\n+\n+\tif (sparx5_rr_fib_info_should_ignore(fi))\n+\t\treturn 0;\n+\n+\tsparx5_rr_fib_info_to_fib_key(fi, \u0026key);\n+\n+\tfib_entry = sparx5_rr_fib_entry_lookup(sparx5, \u0026key);\n+\tif (!fib_entry)\n+\t\treturn 0;\n+\n+\t/* Are we adding new nexthops which can not be offloaded */\n+\tif (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {\n+\t\terr = -EINVAL;\n+\t\tgoto out_fib_mark_offload;\n+\t}\n+\n+\t/* Append new rt_arr data to fen6_info rt data */\n+\terr = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);\n+\tif (err)\n+\t\tgoto out_fib_mark_offload;\n+\n+\t/* Realloc nexthop group and apply to hw. */\n+\terr = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);\n+\n+out_fib_mark_offload:\n+\tfib_entry-\u003eoffload_fail = !!err;\n+\tsparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);\n+\tif (err)\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\n+\treturn err;\n+}\n+\n+static bool sparx5_rr_fib6_rt_exists(struct sparx5_rr_fib6_entry_info *f6i,\n+\t\t\t\t struct fib6_info *rt)\n+{\n+\tfor (int i = 0; i \u003c f6i-\u003enrt6; i++)\n+\t\tif (f6i-\u003ert_arr[i] == rt)\n+\t\t\treturn true;\n+\n+\treturn false;\n+}\n+\n+static int sparx5_rr_fib6_nexthop_prune(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct sparx5_rr_fib_entry *fib_entry,\n+\t\t\t\t\tstruct sparx5_rr_fib6_entry_info *f6i)\n+{\n+\tstruct fib6_info **old_rt_arr = fib_entry-\u003efi.fe6_info.rt_arr;\n+\tunsigned int old_nrt6, new_nrt6;\n+\tstruct fib6_info **rt_arr;\n+\tint j = 0;\n+\n+\told_nrt6 = fib_entry-\u003efi.fe6_info.nrt6;\n+\tnew_nrt6 = old_nrt6 \u003e= f6i-\u003enrt6 ? old_nrt6 - f6i-\u003enrt6 : 0;\n+\n+\trt_arr = kzalloc_objs(struct fib6_info *, old_nrt6);\n+\tif (!rt_arr)\n+\t\treturn -ENOMEM;\n+\n+\tfor (int i = 0; i \u003c old_nrt6; i++) {\n+\t\tstruct fib6_info *fi = old_rt_arr[i];\n+\n+\t\tif (sparx5_rr_fib6_rt_exists(f6i, fi)) {\n+\t\t\tsparx5_rr_rt6_release(fi);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trt_arr[j++] = fi;\n+\t}\n+\n+\tWARN_ON_ONCE(j != new_nrt6);\n+\n+\tkfree(fib_entry-\u003efi.fe6_info.rt_arr);\n+\tfib_entry-\u003efi.fe6_info.nrt6 = j;\n+\tfib_entry-\u003efi.fe6_info.rt_arr = rt_arr;\n+\treturn 0;\n+}\n+\n+static int sparx5_rr_fib6_del(struct sparx5 *sparx5,\n+\t\t\t struct sparx5_rr_fib_info *fi)\n+{\n+\tstruct sparx5_rr_fib_entry *fib_entry;\n+\tint nhs = sparx5_rr_fib_info_nhs(fi);\n+\tstruct sparx5_rr_fib_key key;\n+\tint err;\n+\n+\tsparx5_rr_fib_info_to_fib_key(fi, \u0026key);\n+\n+\tfib_entry = sparx5_rr_fib_entry_lookup(sparx5, \u0026key);\n+\tif (!fib_entry)\n+\t\treturn 0;\n+\n+\t/* Full delete. */\n+\tif (nhs == sparx5_rr_fib_info_nhs(\u0026fib_entry-\u003efi)) {\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Partial delete. Remove fi nexthops from fib_entry. */\n+\terr = sparx5_rr_fib6_nexthop_prune(sparx5, fib_entry, \u0026fi-\u003efe6_info);\n+\tif (err)\n+\t\tgoto err_nexthop_prune;\n+\n+\t/* Realloc nexthop group and apply to hw. */\n+\terr = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);\n+\n+err_nexthop_prune:\n+\tfib_entry-\u003eoffload_fail = !!err;\n+\tsparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);\n+\tif (err)\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+\n+\treturn err;\n+}\n+\n+static void sparx5_rr_fib6_event_work(struct work_struct *work)\n+{\n+\tstruct sparx5_fib_event_work *fib_work =\n+\t\tcontainer_of(work, struct sparx5_fib_event_work, work);\n+\tstruct sparx5_rr_fib_info *fi = \u0026fib_work-\u003efi;\n+\tstruct sparx5 *sparx5 = fib_work-\u003esparx5;\n+\tint err;\n+\n+\tmutex_lock(\u0026sparx5-\u003erouter-\u003elock);\n+\n+\tswitch (fib_work-\u003eevent) {\n+\tcase FIB_EVENT_ENTRY_REPLACE:\n+\t\terr = sparx5_rr_fib_replace(sparx5, fi);\n+\t\tif (err)\n+\t\t\tdev_warn(sparx5-\u003edev, \"FIB 6 replace failed.\\n\");\n+\n+\t\tbreak;\n+\n+\tcase FIB_EVENT_ENTRY_APPEND:\n+\t\t/* Netlink API for IPv6 is different from IPV4. It is\n+\t\t * possible to do partial update/deletes of nexthops on a\n+\t\t * route. In this case fi only contains the nexthops to\n+\t\t * add/remove, and must be merged with the existing nexthops\n+\t\t * on the route. Therefore, we only share fib_replace between\n+\t\t * IPv6 and IPv4 logic.\n+\t\t */\n+\t\terr = sparx5_rr_fib6_append(sparx5, fi);\n+\t\tif (err)\n+\t\t\tdev_warn(sparx5-\u003edev, \"FIB 6 append failed.\\n\");\n+\n+\t\tbreak;\n+\n+\tcase FIB_EVENT_ENTRY_DEL:\n+\t\terr = sparx5_rr_fib6_del(sparx5, fi);\n+\t\tif (err)\n+\t\t\tdev_warn(sparx5-\u003edev, \"FIB 6 delete failed.\\n\");\n+\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tWARN_ON_ONCE(1); /* BUG */\n+\t\tbreak;\n+\t}\n+\n+\t/* Release fib6_info holds for workqueue. */\n+\tsparx5_rr_fib_info_put(fi);\n+\tmutex_unlock(\u0026sparx5-\u003erouter-\u003elock);\n+\tkfree(fib_work);\n+}\n+\n+static int sparx5_rr_fib6_work_init(struct sparx5_fib_event_work *fib_work,\n+\t\t\t\t struct fib6_entry_notifier_info *fen6_info)\n+{\n+\tstruct sparx5_rr_fib6_entry_info *fib6_info = \u0026fib_work-\u003efi.fe6_info;\n+\tstruct fib6_info *rt = fen6_info-\u003ert;\n+\tstruct fib6_info **rt_arr;\n+\tstruct fib6_info *iter;\n+\tunsigned int nrt6;\n+\tint i = 0;\n+\n+\tnrt6 = fen6_info-\u003ensiblings + 1;\n+\n+\trt_arr = kzalloc_objs(struct fib6_info *, nrt6, GFP_ATOMIC);\n+\tif (!rt_arr)\n+\t\treturn -ENOMEM;\n+\n+\tfib6_info-\u003ert_arr = rt_arr;\n+\tfib6_info-\u003enrt6 = nrt6;\n+\n+\trt_arr[0] = rt;\n+\tfib6_info_hold(rt);\n+\n+\tif (!fen6_info-\u003ensiblings)\n+\t\treturn 0;\n+\n+\tlist_for_each_entry(iter, \u0026rt-\u003efib6_siblings, fib6_siblings) {\n+\t\tif (i == fen6_info-\u003ensiblings)\n+\t\t\tbreak;\n+\n+\t\trt_arr[i + 1] = iter;\n+\t\tfib6_info_hold(iter);\n+\t\ti++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Handle fib events, which manage fib_entries. Called in atomic context, with\n+ * rcu_read_lock().\n+ */\n+static int sparx5_rr_fib_event(struct notifier_block *nb, unsigned long event,\n+\t\t\t void *ptr)\n+{\n+\tstruct fib6_entry_notifier_info *fen6_info;\n+\tstruct fib_entry_notifier_info *fen_info;\n+\tstruct sparx5_fib_event_work *fib_work;\n+\tstruct fib_notifier_info *info = ptr;\n+\tstruct sparx5_router *router;\n+\tint err;\n+\n+\t/* Handle IPv4 and IPv6 */\n+\tif (info-\u003efamily != AF_INET \u0026\u0026 info-\u003efamily != AF_INET6)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (event != FIB_EVENT_ENTRY_REPLACE \u0026\u0026\n+\t event != FIB_EVENT_ENTRY_DEL \u0026\u0026\n+\t event != FIB_EVENT_ENTRY_APPEND)\n+\t\treturn NOTIFY_DONE;\n+\n+\trouter = container_of(nb, struct sparx5_router, fib_nb);\n+\n+\tfib_work = kzalloc_obj(*fib_work, GFP_ATOMIC);\n+\tif (!fib_work)\n+\t\treturn NOTIFY_BAD;\n+\n+\tfib_work-\u003esparx5 = router-\u003esparx5;\n+\tfib_work-\u003eevent = event;\n+\n+\tswitch (info-\u003efamily) {\n+\tcase AF_INET:\n+\t\tINIT_WORK(\u0026fib_work-\u003ework, sparx5_rr_fib4_event_work);\n+\n+\t\tfen_info = container_of(info, struct fib_entry_notifier_info,\n+\t\t\t\t\tinfo);\n+\t\tfib_work-\u003efi.fen4_info = *fen_info;\n+\t\tfib_work-\u003efi.version = SPARX5_IPV4;\n+\n+\t\t/* Hold fib_info while item is queued */\n+\t\tfib_info_hold(fib_work-\u003efi.fen4_info.fi);\n+\n+\t\tsparx5_rr_schedule_work(router-\u003esparx5, \u0026fib_work-\u003ework);\n+\t\tbreak;\n+\tcase AF_INET6:\n+\t\tINIT_WORK(\u0026fib_work-\u003ework, sparx5_rr_fib6_event_work);\n+\n+\t\t/* Copy and hold fib6_info for route and all nhs while item is\n+\t\t * queued.\n+\t\t */\n+\t\tfen6_info = container_of(info, struct fib6_entry_notifier_info,\n+\t\t\t\t\t info);\n+\t\terr = sparx5_rr_fib6_work_init(fib_work, fen6_info);\n+\t\tif (err)\n+\t\t\tgoto err_fib6;\n+\n+\t\tfib_work-\u003efi.version = SPARX5_IPV6;\n+\n+\t\tsparx5_rr_schedule_work(router-\u003esparx5, \u0026fib_work-\u003ework);\n+\t\tbreak;\n+\tdefault:\n+\t\tgoto err_fam_unhandled;\n+\t}\n+\n+\treturn NOTIFY_DONE;\n+\n+err_fam_unhandled:\n+\tWARN_ON_ONCE(1); /* BUG */\n+err_fib6:\n+\tkfree(fib_work);\n+\treturn NOTIFY_BAD;\n+}\n+\n+static void sparx5_rr_neigh_event_work(struct work_struct *work)\n+{\n+\tstruct sparx5_rr_netevent_work *net_work =\n+\t\tcontainer_of(work, struct sparx5_rr_netevent_work, work);\n+\tunsigned char hwaddr[ETH_ALEN] __aligned(2);\n+\tstruct sparx5 *sparx5 = net_work-\u003esparx5;\n+\tstruct neighbour *n = net_work-\u003eneigh;\n+\tstruct sparx5_rr_neigh_key key = { };\n+\tstruct sparx5_rr_neigh_entry *entry;\n+\tbool entry_connected;\n+\tu8 nud_state, dead;\n+\n+\tsparx5_rr_nb2neigh_key(n, \u0026key);\n+\n+\t/* Frames with link-local dip are trapped, so ignore the neighbour. */\n+\tif (key.iaddr.version == SPARX5_IPV6 \u0026\u0026\n+\t ipv6_addr_type(\u0026key.iaddr.ipv6) \u0026 IPV6_ADDR_LINKLOCAL)\n+\t\tgoto out;\n+\n+\t/* If n changes after this read section, we will get another neigh\n+\t * event, which is processed after the current one.\n+\t */\n+\tread_lock_bh(\u0026n-\u003elock);\n+\tether_addr_copy(hwaddr, n-\u003eha);\n+\tnud_state = n-\u003enud_state;\n+\tdead = n-\u003edead;\n+\tread_unlock_bh(\u0026n-\u003elock);\n+\n+\tmutex_lock(\u0026sparx5-\u003erouter-\u003elock);\n+\n+\tentry_connected = nud_state \u0026 NUD_VALID \u0026\u0026 !dead;\n+\tentry = sparx5_rr_neigh_entry_lookup(sparx5, \u0026key);\n+\tif (!entry_connected \u0026\u0026 !entry)\n+\t\tgoto out_mutex;\n+\n+\tif (!entry) {\n+\t\tentry = sparx5_rr_neigh_entry_create(sparx5, \u0026key);\n+\t\tif (IS_ERR(entry))\n+\t\t\tgoto out_mutex;\n+\t}\n+\n+\tif (entry-\u003econnected \u0026\u0026 entry_connected \u0026\u0026\n+\t ether_addr_equal(entry-\u003ehwaddr, hwaddr))\n+\t\tgoto out_mutex;\n+\n+\tether_addr_copy(entry-\u003ehwaddr, hwaddr);\n+\tsparx5_rr_neigh_entry_update(sparx5, entry, entry_connected);\n+\tsparx5_rr_nexthops_update_notify(sparx5, entry, entry_connected);\n+\tif (!entry_connected)\n+\t\tsparx5_rr_neigh_entry_put(sparx5, entry);\n+\n+out_mutex:\n+\tmutex_unlock(\u0026sparx5-\u003erouter-\u003elock);\n+out:\n+\tneigh_release(n);\n+\tkfree(net_work);\n+}\n+\n+/* Handle neighbour update events. Used to manage neigh_entries. Called in\n+ * atomic context, with rcu_read_lock().\n+ */\n+static int sparx5_rr_netevent_event(struct notifier_block *nb,\n+\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct sparx5_rr_netevent_work *net_work;\n+\tstruct sparx5_router *router;\n+\tstruct sparx5_port *port;\n+\tstruct neighbour *n;\n+\n+\trouter = container_of(nb, struct sparx5_router, netevent_nb);\n+\n+\tswitch (event) {\n+\tcase NETEVENT_NEIGH_UPDATE:\n+\t\tn = ptr;\n+\n+\t\tif (!net_eq(dev_net(n-\u003edev), \u0026init_net))\n+\t\t\treturn NOTIFY_DONE;\n+\n+\t\tif (n-\u003etbl-\u003efamily != AF_INET \u0026\u0026 n-\u003etbl-\u003efamily != AF_INET6)\n+\t\t\treturn NOTIFY_DONE;\n+\n+\t\tport = sparx5_port_dev_lower_find(n-\u003edev);\n+\t\tif (!port)\n+\t\t\treturn NOTIFY_DONE;\n+\n+\t\tnet_work = kzalloc_obj(*net_work, GFP_ATOMIC);\n+\t\tif (!net_work)\n+\t\t\treturn NOTIFY_BAD;\n+\n+\t\tINIT_WORK(\u0026net_work-\u003ework, sparx5_rr_neigh_event_work);\n+\t\tnet_work-\u003esparx5 = router-\u003esparx5;\n+\t\tnet_work-\u003eneigh = neigh_clone(n);\n+\t\tnet_work-\u003eevent = event;\n+\t\tsparx5_rr_schedule_work(router-\u003esparx5, \u0026net_work-\u003ework);\n+\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\treturn NOTIFY_DONE;\n+};\n+\n+static void sparx5_rr_leg_base_mac_set(struct sparx5 *sparx5,\n+\t\t\t\t unsigned char mac[ETH_ALEN])\n+{\n+\tu8 rleg_type_sel = SPARX5_RLEG_USE_GLOBAL_BASE_MAC;\n+\tu32 mac_msb, mac_lsb;\n+\n+\tsparx5_rr_split_mac(mac, 24, \u0026mac_msb, \u0026mac_lsb);\n+\n+\tdev_dbg(sparx5-\u003edev, \"Router leg base MAC=%pM\\n\", mac);\n+\n+\t/* The global router leg MAC must be set consistently across ANA_L3, REW\n+\t * and EACL.\n+\t */\n+\tspx5_wr(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,\n+\t\tANA_L3_RLEG_CFG_0);\n+\n+\tspx5_rmw(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |\n+\t\t ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),\n+\t\t ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB |\n+\t\t ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,\n+\t\t sparx5, ANA_L3_RLEG_CFG_1);\n+\n+\t/* Set global Router leg MAC (REW) */\n+\tspx5_wr(REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,\n+\t\tREW_RLEG_CFG_0);\n+\n+\tspx5_rmw(REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |\n+\t\t REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),\n+\t\t REW_RLEG_CFG_1_RLEG_MAC_MSB | REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,\n+\t\t sparx5, REW_RLEG_CFG_1);\n+\n+\t/* Set global Router leg MAC (EACL) */\n+\tspx5_wr(EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,\n+\t\tEACL_RLEG_CFG_0);\n+\n+\tspx5_rmw(EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |\n+\t\t EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),\n+\t\t EACL_RLEG_CFG_1_RLEG_MAC_MSB |\n+\t\t EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,\n+\t\t sparx5, EACL_RLEG_CFG_1);\n+}\n+\n+static bool\n+sparx5_rr_router_leg_addr_list_empty_rcu(struct sparx5_rr_router_leg *leg)\n+{\n+\tstruct inet6_dev *inet6_dev;\n+\tstruct in_device *in_dev;\n+\n+\tin_dev = __in_dev_get_rcu(leg-\u003edev);\n+\tif (in_dev \u0026\u0026 rcu_access_pointer(in_dev-\u003eifa_list))\n+\t\treturn false;\n+\n+\tinet6_dev = __in6_dev_get(leg-\u003edev);\n+\tif (inet6_dev \u0026\u0026 !list_empty(\u0026inet6_dev-\u003eaddr_list))\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static bool\n+sparx5_rr_router_leg_addr_list_empty(struct sparx5_rr_router_leg *leg)\n+{\n+\tbool addr_list_empty;\n+\n+\trcu_read_lock();\n+\taddr_list_empty = sparx5_rr_router_leg_addr_list_empty_rcu(leg);\n+\trcu_read_unlock();\n+\n+\treturn addr_list_empty;\n+}\n+\n+static int __sparx5_rr_inetaddr_event(struct sparx5 *sparx5,\n+\t\t\t\t struct net_device *dev,\n+\t\t\t\t unsigned long event)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\tu16 vid;\n+\n+\tif (!sparx5_rr_dev_real_is_vlan_aware(dev))\n+\t\treturn 0;\n+\n+\t/* Our basic case: ip addr/subnet added to vlan upper of\n+\t * bridge dev.\n+\t */\n+\tswitch (event) {\n+\tcase NETDEV_UP:\n+\t\tleg = sparx5_rr_leg_find_by_dev(sparx5, dev);\n+\t\tif (leg)\n+\t\t\treturn 0;\n+\n+\t\t/* HW allows at most 1 leg per VLAN, but we do not need to\n+\t\t * lookup leg by vid, since the kernel does not allow multiple\n+\t\t * vlan devs with the same vid on top of a given device.\n+\t\t */\n+\t\tvid = vlan_dev_vlan_id(dev);\n+\n+\t\tleg = sparx5_rr_router_leg_create(sparx5, dev, vid);\n+\t\tif (IS_ERR(leg))\n+\t\t\treturn PTR_ERR(leg);\n+\t\tbreak;\n+\tcase NETDEV_DOWN:\n+\t\tleg = sparx5_rr_leg_find_by_dev(sparx5, dev);\n+\t\tif (!leg || !sparx5_rr_router_leg_addr_list_empty(leg))\n+\t\t\treturn 0;\n+\n+\t\tsparx5_rr_router_leg_destroy(leg);\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int sparx5_rr_inetaddr_event_handle(struct sparx5 *sparx5,\n+\t\t\t\t\t struct net_device *dev,\n+\t\t\t\t\t unsigned long event)\n+{\n+\tint err;\n+\n+\tif (!net_eq(dev_net(dev), \u0026init_net))\n+\t\treturn NOTIFY_DONE;\n+\n+\tmutex_lock(\u0026sparx5-\u003erouter-\u003elock);\n+\terr = __sparx5_rr_inetaddr_event(sparx5, dev, event);\n+\tmutex_unlock(\u0026sparx5-\u003erouter-\u003elock);\n+\n+\treturn notifier_from_errno(err);\n+}\n+\n+/* Called with RTNL. */\n+static int sparx5_rr_inet6addr_valid_event(struct notifier_block *nb,\n+\t\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct in6_validator_info *i6vi = (struct in6_validator_info *)ptr;\n+\tstruct net_device *dev = i6vi-\u003ei6vi_dev-\u003edev;\n+\tstruct sparx5_router *router;\n+\n+\tASSERT_RTNL();\n+\n+\tif (event != NETDEV_UP)\n+\t\treturn NOTIFY_DONE;\n+\n+\trouter = container_of(nb, struct sparx5_router, inet6addr_valid_nb);\n+\n+\treturn sparx5_rr_inetaddr_event_handle(router-\u003esparx5, dev, event);\n+}\n+\n+static void sparx5_rr_inet6addr_event_work(struct work_struct *work)\n+{\n+\tstruct sparx5_rr_inet6addr_event_work *addr_work =\n+\t\tcontainer_of(work, struct sparx5_rr_inet6addr_event_work, work);\n+\tstruct sparx5_router *router = addr_work-\u003esparx5-\u003erouter;\n+\n+\trtnl_lock();\n+\tmutex_lock(\u0026router-\u003elock);\n+\n+\t__sparx5_rr_inetaddr_event(addr_work-\u003esparx5, addr_work-\u003edev,\n+\t\t\t\t addr_work-\u003eevent);\n+\n+\tmutex_unlock(\u0026router-\u003elock);\n+\trtnl_unlock();\n+\tnetdev_put(addr_work-\u003edev, \u0026addr_work-\u003edev_tracker);\n+\tkfree(addr_work);\n+}\n+\n+/* Called in atomic context. */\n+static int sparx5_rr_inet6addr_event(struct notifier_block *nb,\n+\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr;\n+\tstruct sparx5_rr_inet6addr_event_work *work;\n+\tstruct net_device *dev = if6-\u003eidev-\u003edev;\n+\tstruct sparx5_router *router;\n+\n+\tif (event != NETDEV_DOWN)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!net_eq(dev_net(dev), \u0026init_net))\n+\t\treturn NOTIFY_DONE;\n+\n+\twork = kzalloc_obj(*work, GFP_ATOMIC);\n+\tif (!work)\n+\t\treturn NOTIFY_BAD;\n+\n+\trouter = container_of(nb, struct sparx5_router, inet6addr_nb);\n+\tINIT_WORK(\u0026work-\u003ework, sparx5_rr_inet6addr_event_work);\n+\twork-\u003esparx5 = router-\u003esparx5;\n+\twork-\u003edev = dev;\n+\twork-\u003eevent = event;\n+\tnetdev_hold(dev, \u0026work-\u003edev_tracker, GFP_ATOMIC);\n+\tsparx5_rr_schedule_work(router-\u003esparx5, \u0026work-\u003ework);\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+/* Handle events for ip address changes on ifs. Used to manage router legs.\n+ * Called with RTNL.\n+ */\n+static int sparx5_rr_inetaddr_event(struct notifier_block *nb,\n+\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct in_ifaddr *ifa = (struct in_ifaddr *)ptr;\n+\tstruct net_device *dev = ifa-\u003eifa_dev-\u003edev;\n+\tstruct sparx5_router *router;\n+\n+\tASSERT_RTNL();\n+\n+\tif (event != NETDEV_DOWN)\n+\t\treturn NOTIFY_DONE;\n+\n+\trouter = container_of(nb, struct sparx5_router, inetaddr_nb);\n+\n+\treturn sparx5_rr_inetaddr_event_handle(router-\u003esparx5, dev, event);\n+}\n+\n+/* Called with RTNL. */\n+static int sparx5_rr_inetaddr_valid_event(struct notifier_block *nb,\n+\t\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct in_validator_info *ivi = (struct in_validator_info *)ptr;\n+\tstruct net_device *dev = ivi-\u003eivi_dev-\u003edev;\n+\tstruct sparx5_router *router;\n+\n+\tASSERT_RTNL();\n+\n+\tif (event != NETDEV_UP)\n+\t\treturn NOTIFY_DONE;\n+\n+\trouter = container_of(nb, struct sparx5_router, inetaddr_valid_nb);\n+\n+\treturn sparx5_rr_inetaddr_event_handle(router-\u003esparx5, dev, event);\n+}\n+\n+/* Called with RTNL. */\n+static int sparx5_rr_netdevice_event(struct notifier_block *nb,\n+\t\t\t\t unsigned long event, void *ptr)\n+{\n+\tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n+\tunsigned char mac[ETH_ALEN] __aligned(2);\n+\tstruct sparx5_rr_router_leg *leg;\n+\tstruct sparx5_router *router;\n+\tstruct sparx5 *sparx5;\n+\n+\tASSERT_RTNL();\n+\n+\tif (!net_eq(dev_net(dev), \u0026init_net))\n+\t\treturn NOTIFY_OK;\n+\n+\trouter = container_of(nb, struct sparx5_router, netdevice_nb);\n+\tsparx5 = router-\u003esparx5;\n+\n+\tswitch (event) {\n+\tcase NETDEV_CHANGEADDR:\n+\t\t/* Allow single bridge. Global router leg MAC tracks bridge mac. */\n+\t\tif (netif_is_bridge_master(dev) \u0026\u0026 sparx5_port_dev_lower_find(dev)) {\n+\t\t\tether_addr_copy(mac, dev-\u003edev_addr);\n+\t\t\tsparx5_rr_leg_base_mac_set(sparx5, mac);\n+\t\t}\n+\t\tbreak;\n+\tcase NETDEV_UNREGISTER:\n+\t\tmutex_lock(\u0026router-\u003elock);\n+\t\tleg = sparx5_rr_leg_find_by_dev(sparx5, dev);\n+\t\tif (leg)\n+\t\t\tsparx5_rr_router_leg_destroy(leg);\n+\t\tmutex_unlock(\u0026router-\u003elock);\n+\t\tbreak;\n+\t}\n+\n+\treturn NOTIFY_OK;\n+}\n+\n+static int sparx5_rr_blackhole_leg_create(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_rr_router_leg *leg;\n+\tu16 vmid, vid;\n+\n+\tvmid = SPARX5_BLACKHOLE_VMID(sparx5);\n+\tvid = SPARX5_BLACKHOLE_VID;\n+\n+\tleg = __sparx5_rr_leg_alloc(sparx5, NULL, vmid, vid);\n+\tif (!leg)\n+\t\treturn -ENOMEM;\n+\n+\tset_bit(vmid, sparx5-\u003erouter-\u003evmid_mask);\n+\n+\tlist_add(\u0026leg-\u003eleg_list_node, \u0026sparx5-\u003erouter-\u003eleg_list);\n+\tsparx5_rr_leg_hw_init(sparx5, leg);\n+\n+\tdev_dbg(sparx5-\u003edev, \"Blackhole leg create vid=%u vmid=%u\\n\",\n+\t\tleg-\u003evid, leg-\u003evmid);\n+\n+\treturn 0;\n+}\n+\n+static void sparx5_rr_router_legs_flush(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_rr_router_leg *leg, *tmp;\n+\n+\tlist_for_each_entry_safe(leg, tmp, \u0026sparx5-\u003erouter-\u003eleg_list,\n+\t\t\t\t leg_list_node)\n+\t\tsparx5_rr_router_leg_destroy(leg);\n+}\n+\n+static void sparx5_rr_fib_flush(struct sparx5 *sparx5);\n+\n+int sparx5_rr_router_init(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_router *r;\n+\tint err;\n+\n+\tr = kzalloc_obj(*sparx5-\u003erouter);\n+\tif (!r)\n+\t\treturn -ENOMEM;\n+\n+\tmutex_init(\u0026r-\u003elock);\n+\tsparx5-\u003erouter = r;\n+\tr-\u003esparx5 = sparx5;\n+\n+\tINIT_LIST_HEAD(\u0026r-\u003eleg_list);\n+\tINIT_LIST_HEAD(\u0026r-\u003efib_list);\n+\n+\t/* Add reserved leg for blackhole routes. */\n+\terr = sparx5_rr_blackhole_leg_create(sparx5);\n+\tif (err)\n+\t\tgoto err_free_router;\n+\n+\terr = rhashtable_init(\u0026r-\u003eneigh_ht, \u0026sparx5_neigh_ht_params);\n+\tif (err)\n+\t\tgoto err_blackhole_destroy;\n+\n+\terr = rhashtable_init(\u0026r-\u003efib_ht, \u0026sparx5_rr_fib_entry_ht_params);\n+\tif (err)\n+\t\tgoto err_neigh_ht_destroy;\n+\n+\tr-\u003esparx5_router_owq = alloc_ordered_workqueue(\"sparx5_router_owq\", 0);\n+\tif (!r-\u003esparx5_router_owq) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_fib_ht_destroy;\n+\t}\n+\n+\tatomic_set(\u0026r-\u003elegs_count, 0);\n+\tr-\u003elink_local.vrule_id = 0;\n+\tr-\u003elink_local.vrule_id_valid = false;\n+\t/* VCAP API requires a port net_device, to get a sparx5 reference.\n+\t * Fetch any valid port.\n+\t */\n+\tfor (int i = 0; i \u003c sparx5-\u003edata-\u003econsts-\u003en_ports; i++) {\n+\t\tif (!sparx5-\u003eports[i])\n+\t\t\tcontinue;\n+\n+\t\tr-\u003eport_dev = sparx5-\u003eports[i]-\u003endev;\n+\t\tif (r-\u003eport_dev)\n+\t\t\tbreak;\n+\t}\n+\tif (!r-\u003eport_dev) {\n+\t\terr = -ENXIO;\n+\t\tgoto err_workqueue_destroy;\n+\t}\n+\n+\t/* Enable L3 UC routing on all ports. */\n+\tspx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA);\n+\tif (is_sparx5(sparx5)) {\n+\t\tspx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA1);\n+\t\tspx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA2);\n+\t}\n+\n+\t/* Enable routing and global router options */\n+\tspx5_rmw(ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(1) |\n+\t\t ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(1),\n+\t\t ANA_L3_ROUTING_CFG_L3_ENA_MODE |\n+\t\t ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA |\n+\t\t ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA |\n+\t\t ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA |\n+\t\t ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA |\n+\t\t ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA |\n+\t\t ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA,\n+\t\t sparx5, ANA_L3_ROUTING_CFG);\n+\n+\t/* By default, routing related frame edits are done in REW, but when\n+\t * combining routing with PTP, ANA_ACL must be configured to change DMAC\n+\t * to next-hop DMAC in order to allow other information to be stored in\n+\t * the IFH.\n+\t *\n+\t * This enables routing related frame edits independently of VCAP_S2\n+\t * action ACL_RT_MODE.\n+\t */\n+\tspx5_rmw(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(1),\n+\t\t ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, sparx5,\n+\t\t ANA_ACL_VCAP_S2_MISC_CTRL);\n+\n+\tr-\u003enetevent_nb.notifier_call = sparx5_rr_netevent_event;\n+\terr = register_netevent_notifier(\u0026r-\u003enetevent_nb);\n+\tif (err)\n+\t\tgoto err_workqueue_destroy;\n+\n+\tr-\u003efib_nb.notifier_call = sparx5_rr_fib_event;\n+\terr = register_fib_notifier(\u0026init_net, \u0026r-\u003efib_nb, NULL, NULL);\n+\tif (err)\n+\t\tgoto err_unreg_netevent_notifier;\n+\n+\tr-\u003einetaddr_nb.notifier_call = sparx5_rr_inetaddr_event;\n+\terr = register_inetaddr_notifier(\u0026r-\u003einetaddr_nb);\n+\tif (err)\n+\t\tgoto err_unreg_fib_notifier;\n+\n+\tr-\u003einetaddr_valid_nb.notifier_call = sparx5_rr_inetaddr_valid_event;\n+\terr = register_inetaddr_validator_notifier(\u0026r-\u003einetaddr_valid_nb);\n+\tif (err)\n+\t\tgoto err_unreg_inet_notifier;\n+\n+\tr-\u003enetdevice_nb.notifier_call = sparx5_rr_netdevice_event;\n+\terr = register_netdevice_notifier(\u0026r-\u003enetdevice_nb);\n+\tif (err)\n+\t\tgoto err_unreg_inet_addr_val_notifier;\n+\n+\tr-\u003einet6addr_valid_nb.notifier_call = sparx5_rr_inet6addr_valid_event;\n+\terr = register_inet6addr_validator_notifier(\u0026r-\u003einet6addr_valid_nb);\n+\tif (err)\n+\t\tgoto err_unreg_netdev_notifier;\n+\n+\tr-\u003einet6addr_nb.notifier_call = sparx5_rr_inet6addr_event;\n+\terr = register_inet6addr_notifier(\u0026r-\u003einet6addr_nb);\n+\tif (err)\n+\t\tgoto err_unreg_inet6_addr_val_notifier;\n+\n+\treturn 0;\n+\n+err_unreg_inet6_addr_val_notifier:\n+\tunregister_inet6addr_validator_notifier(\u0026r-\u003einet6addr_valid_nb);\n+err_unreg_netdev_notifier:\n+\tunregister_netdevice_notifier(\u0026r-\u003enetdevice_nb);\n+err_unreg_inet_addr_val_notifier:\n+\tunregister_inetaddr_validator_notifier(\u0026r-\u003einetaddr_valid_nb);\n+err_unreg_inet_notifier:\n+\tunregister_inetaddr_notifier(\u0026r-\u003einetaddr_nb);\n+err_unreg_fib_notifier:\n+\tunregister_fib_notifier(\u0026init_net, \u0026r-\u003efib_nb);\n+err_unreg_netevent_notifier:\n+\tunregister_netevent_notifier(\u0026r-\u003enetevent_nb);\n+err_workqueue_destroy:\n+\tdestroy_workqueue(r-\u003esparx5_router_owq);\n+\tsparx5_rr_fib_flush(sparx5);\n+err_fib_ht_destroy:\n+\trhashtable_destroy(\u0026r-\u003efib_ht);\n+err_neigh_ht_destroy:\n+\trhashtable_destroy(\u0026r-\u003eneigh_ht);\n+err_blackhole_destroy:\n+\tsparx5_rr_router_legs_flush(sparx5);\n+err_free_router:\n+\tmutex_destroy(\u0026r-\u003elock);\n+\tkfree(r);\n+\n+\treturn err;\n+}\n+\n+static void sparx5_rr_fib_flush(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_rr_fib_entry *fib_entry, *tmp;\n+\n+\tlist_for_each_entry_safe(fib_entry, tmp, \u0026sparx5-\u003erouter-\u003efib_list,\n+\t\t\t\t fib_node)\n+\t\tsparx5_rr_fib_entry_destroy(sparx5, fib_entry);\n+}\n+\n+void sparx5_rr_router_deinit(struct sparx5 *sparx5)\n+{\n+\tstruct sparx5_router *router = sparx5-\u003erouter;\n+\n+\tunregister_inet6addr_notifier(\u0026router-\u003einet6addr_nb);\n+\tunregister_inet6addr_validator_notifier(\u0026router-\u003einet6addr_valid_nb);\n+\tunregister_netdevice_notifier(\u0026router-\u003enetdevice_nb);\n+\tunregister_inetaddr_validator_notifier(\u0026router-\u003einetaddr_valid_nb);\n+\tunregister_inetaddr_notifier(\u0026router-\u003einetaddr_nb);\n+\tunregister_fib_notifier(\u0026init_net, \u0026router-\u003efib_nb);\n+\tunregister_netevent_notifier(\u0026router-\u003enetevent_nb);\n+\tdestroy_workqueue(router-\u003esparx5_router_owq);\n+\tsparx5_rr_fib_flush(sparx5);\n+\tsparx5_rr_router_legs_flush(sparx5);\n+\tWARN_ON(atomic_read(\u0026router-\u003eneigh_ht.nelems));\n+\trhashtable_destroy(\u0026router-\u003efib_ht);\n+\trhashtable_destroy(\u0026router-\u003eneigh_ht);\n+\tmutex_destroy(\u0026router-\u003elock);\n+\tkfree(router);\n+}\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c\nindex 556d6ea0acd1b..7bc8b29757b14 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c\n@@ -1,10 +1,10 @@\n // SPDX-License-Identifier: BSD-3-Clause\n-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.\n+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.\n * Microchip VCAP API\n */\n \n-/* This file is autogenerated by cml-utils 2023-02-10 11:15:56 +0100.\n- * Commit ID: c30fb4bf0281cd4a7133bdab6682f9e43c872ada\n+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.\n+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a\n */\n \n #include \u003clinux/types.h\u003e\n@@ -1381,6 +1381,83 @@ static const struct vcap_field es0_isdx_keyfield[] = {\n \t},\n };\n \n+static const struct vcap_field lpm_sgl_ip4_keyfield[] = {\n+\t[VCAP_KF_DST_FLAG] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP4_XIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 11,\n+\t\t.width = 32,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_dbl_ip4_keyfield[] = {\n+\t[VCAP_KF_TYPE] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP4_SIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 11,\n+\t\t.width = 32,\n+\t},\n+\t[VCAP_KF_IP4_DIP] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 43,\n+\t\t.width = 32,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_sgl_ip6_keyfield[] = {\n+\t[VCAP_KF_DST_FLAG] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 1,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP6_XIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 11,\n+\t\t.width = 128,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_dbl_ip6_keyfield[] = {\n+\t[VCAP_KF_AFFIX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_KF_IP6_SIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 10,\n+\t\t.width = 128,\n+\t},\n+\t[VCAP_KF_IP6_DIP] = {\n+\t\t.type = VCAP_FIELD_U128,\n+\t\t.offset = 138,\n+\t\t.width = 128,\n+\t},\n+};\n+\n static const struct vcap_field es2_mac_etype_keyfield[] = {\n \t[VCAP_KF_TYPE] = {\n \t\t.type = VCAP_FIELD_U32,\n@@ -2339,6 +2416,29 @@ static const struct vcap_set es0_keyfield_set[] = {\n \t},\n };\n \n+static const struct vcap_set lpm_keyfield_set[] = {\n+\t[VCAP_KFS_SGL_IP4] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+\t[VCAP_KFS_DBL_IP4] = {\n+\t\t.type_id = 0,\n+\t\t.sw_per_item = 2,\n+\t\t.sw_cnt = 6,\n+\t},\n+\t[VCAP_KFS_SGL_IP6] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 3,\n+\t\t.sw_cnt = 4,\n+\t},\n+\t[VCAP_KFS_DBL_IP6] = {\n+\t\t.type_id = -1,\n+\t\t.sw_per_item = 6,\n+\t\t.sw_cnt = 2,\n+\t},\n+};\n+\n static const struct vcap_set es2_keyfield_set[] = {\n \t[VCAP_KFS_MAC_ETYPE] = {\n \t\t.type_id = 0,\n@@ -2391,6 +2491,13 @@ static const struct vcap_field *es0_keyfield_set_map[] = {\n \t[VCAP_KFS_ISDX] = es0_isdx_keyfield,\n };\n \n+static const struct vcap_field *lpm_keyfield_set_map[] = {\n+\t[VCAP_KFS_SGL_IP4] = lpm_sgl_ip4_keyfield,\n+\t[VCAP_KFS_DBL_IP4] = lpm_dbl_ip4_keyfield,\n+\t[VCAP_KFS_SGL_IP6] = lpm_sgl_ip6_keyfield,\n+\t[VCAP_KFS_DBL_IP6] = lpm_dbl_ip6_keyfield,\n+};\n+\n static const struct vcap_field *es2_keyfield_set_map[] = {\n \t[VCAP_KFS_MAC_ETYPE] = es2_mac_etype_keyfield,\n \t[VCAP_KFS_ARP] = es2_arp_keyfield,\n@@ -2419,6 +2526,13 @@ static int es0_keyfield_set_map_size[] = {\n \t[VCAP_KFS_ISDX] = ARRAY_SIZE(es0_isdx_keyfield),\n };\n \n+static int lpm_keyfield_set_map_size[] = {\n+\t[VCAP_KFS_SGL_IP4] = ARRAY_SIZE(lpm_sgl_ip4_keyfield),\n+\t[VCAP_KFS_DBL_IP4] = ARRAY_SIZE(lpm_dbl_ip4_keyfield),\n+\t[VCAP_KFS_SGL_IP6] = ARRAY_SIZE(lpm_sgl_ip6_keyfield),\n+\t[VCAP_KFS_DBL_IP6] = ARRAY_SIZE(lpm_dbl_ip6_keyfield),\n+};\n+\n static int es2_keyfield_set_map_size[] = {\n \t[VCAP_KFS_MAC_ETYPE] = ARRAY_SIZE(es2_mac_etype_keyfield),\n \t[VCAP_KFS_ARP] = ARRAY_SIZE(es2_arp_keyfield),\n@@ -2994,6 +3108,92 @@ static const struct vcap_field es0_es0_actionfield[] = {\n \t},\n };\n \n+static const struct vcap_field lpm_arp_ptr_actionfield[] = {\n+\t[VCAP_AF_TYPE] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t},\n+\t[VCAP_AF_ARP_PTR] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 2,\n+\t\t.width = 11,\n+\t},\n+\t[VCAP_AF_ARP_PTR_REMAP_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 13,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ECMP_CNT] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 14,\n+\t\t.width = 4,\n+\t},\n+\t[VCAP_AF_RGID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 18,\n+\t\t.width = 3,\n+\t},\n+};\n+\n+static const struct vcap_field lpm_arp_entry_actionfield[] = {\n+\t[VCAP_AF_TYPE] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t},\n+\t[VCAP_AF_MAC_MSB] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 2,\n+\t\t.width = 16,\n+\t},\n+\t[VCAP_AF_MAC_LSB] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 18,\n+\t\t.width = 32,\n+\t},\n+\t[VCAP_AF_ARP_VMID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 50,\n+\t\t.width = 9,\n+\t},\n+\t[VCAP_AF_ZERO_DMAC_CPU_QU] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 59,\n+\t\t.width = 3,\n+\t},\n+\t[VCAP_AF_SIP_RPF_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 62,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_SECUR_MATCH_VMID_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 63,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_SECUR_MATCH_MAC_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 64,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ARP_ENA] = {\n+\t\t.type = VCAP_FIELD_BIT,\n+\t\t.offset = 65,\n+\t\t.width = 1,\n+\t},\n+\t[VCAP_AF_ENCAP_ID] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 66,\n+\t\t.width = 10,\n+\t},\n+\t[VCAP_AF_RSDX] = {\n+\t\t.type = VCAP_FIELD_U32,\n+\t\t.offset = 76,\n+\t\t.width = 12,\n+\t},\n+};\n+\n static const struct vcap_field es2_base_type_actionfield[] = {\n \t[VCAP_AF_HIT_ME_ONCE] = {\n \t\t.type = VCAP_FIELD_BIT,\n@@ -3102,6 +3302,19 @@ static const struct vcap_set es0_actionfield_set[] = {\n \t},\n };\n \n+static const struct vcap_set lpm_actionfield_set[] = {\n+\t[VCAP_AFS_ARP_PTR] = {\n+\t\t.type_id = 0,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+\t[VCAP_AFS_ARP_ENTRY] = {\n+\t\t.type_id = 2,\n+\t\t.sw_per_item = 1,\n+\t\t.sw_cnt = 12,\n+\t},\n+};\n+\n static const struct vcap_set es2_actionfield_set[] = {\n \t[VCAP_AFS_BASE_TYPE] = {\n \t\t.type_id = -1,\n@@ -3125,6 +3338,11 @@ static const struct vcap_field *es0_actionfield_set_map[] = {\n \t[VCAP_AFS_ES0] = es0_es0_actionfield,\n };\n \n+static const struct vcap_field *lpm_actionfield_set_map[] = {\n+\t[VCAP_AFS_ARP_PTR] = lpm_arp_ptr_actionfield,\n+\t[VCAP_AFS_ARP_ENTRY] = lpm_arp_entry_actionfield,\n+};\n+\n static const struct vcap_field *es2_actionfield_set_map[] = {\n \t[VCAP_AFS_BASE_TYPE] = es2_base_type_actionfield,\n };\n@@ -3144,6 +3362,11 @@ static int es0_actionfield_set_map_size[] = {\n \t[VCAP_AFS_ES0] = ARRAY_SIZE(es0_es0_actionfield),\n };\n \n+static int lpm_actionfield_set_map_size[] = {\n+\t[VCAP_AFS_ARP_PTR] = ARRAY_SIZE(lpm_arp_ptr_actionfield),\n+\t[VCAP_AFS_ARP_ENTRY] = ARRAY_SIZE(lpm_arp_entry_actionfield),\n+};\n+\n static int es2_actionfield_set_map_size[] = {\n \t[VCAP_AFS_BASE_TYPE] = ARRAY_SIZE(es2_base_type_actionfield),\n };\n@@ -3334,6 +3557,82 @@ static const struct vcap_typegroup es0_x1_keyfield_set_typegroups[] = {\n \t{}\n };\n \n+static const struct vcap_typegroup lpm_x6_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 4,\n+\t\t.value = 8,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 104,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 156,\n+\t\t.width = 3,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 208,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 260,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x3_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 3,\n+\t\t.value = 4,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{\n+\t\t.offset = 104,\n+\t\t.width = 2,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x2_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 2,\n+\t\t.value = 2,\n+\t},\n+\t{\n+\t\t.offset = 52,\n+\t\t.width = 1,\n+\t\t.value = 0,\n+\t},\n+\t{}\n+};\n+\n+static const struct vcap_typegroup lpm_x1_keyfield_set_typegroups[] = {\n+\t{\n+\t\t.offset = 0,\n+\t\t.width = 1,\n+\t\t.value = 1,\n+\t},\n+\t{}\n+};\n+\n static const struct vcap_typegroup es2_x12_keyfield_set_typegroups[] = {\n \t{\n \t\t.offset = 0,\n@@ -3407,6 +3706,14 @@ static const struct vcap_typegroup *es0_keyfield_set_typegroups[] = {\n \t[2] = NULL,\n };\n \n+static const struct vcap_typegroup *lpm_keyfield_set_typegroups[] = {\n+\t[6] = lpm_x6_keyfield_set_typegroups,\n+\t[3] = lpm_x3_keyfield_set_typegroups,\n+\t[2] = lpm_x2_keyfield_set_typegroups,\n+\t[1] = lpm_x1_keyfield_set_typegroups,\n+\t[13] = NULL,\n+};\n+\n static const struct vcap_typegroup *es2_keyfield_set_typegroups[] = {\n \t[12] = es2_x12_keyfield_set_typegroups,\n \t[6] = es2_x6_keyfield_set_typegroups,\n@@ -3484,6 +3791,10 @@ static const struct vcap_typegroup es0_x1_actionfield_set_typegroups[] = {\n \t{}\n };\n \n+static const struct vcap_typegroup lpm_x1_actionfield_set_typegroups[] = {\n+\t{}\n+};\n+\n static const struct vcap_typegroup es2_x3_actionfield_set_typegroups[] = {\n \t{\n \t\t.offset = 0,\n@@ -3525,6 +3836,11 @@ static const struct vcap_typegroup *es0_actionfield_set_typegroups[] = {\n \t[2] = NULL,\n };\n \n+static const struct vcap_typegroup *lpm_actionfield_set_typegroups[] = {\n+\t[1] = lpm_x1_actionfield_set_typegroups,\n+\t[13] = NULL,\n+};\n+\n static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {\n \t[3] = es2_x3_actionfield_set_typegroups,\n \t[1] = es2_x1_actionfield_set_typegroups,\n@@ -3535,6 +3851,8 @@ static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {\n static const char * const vcap_keyfield_set_names[] = {\n \t[VCAP_KFS_NO_VALUE] = \"(None)\",\n \t[VCAP_KFS_ARP] = \"VCAP_KFS_ARP\",\n+\t[VCAP_KFS_DBL_IP4] = \"VCAP_KFS_DBL_IP4\",\n+\t[VCAP_KFS_DBL_IP6] = \"VCAP_KFS_DBL_IP6\",\n \t[VCAP_KFS_ETAG] = \"VCAP_KFS_ETAG\",\n \t[VCAP_KFS_IP4_OTHER] = \"VCAP_KFS_IP4_OTHER\",\n \t[VCAP_KFS_IP4_TCP_UDP] = \"VCAP_KFS_IP4_TCP_UDP\",\n@@ -3553,6 +3871,8 @@ static const char * const vcap_keyfield_set_names[] = {\n \t[VCAP_KFS_NORMAL_7TUPLE] = \"VCAP_KFS_NORMAL_7TUPLE\",\n \t[VCAP_KFS_OAM] = \"VCAP_KFS_OAM\",\n \t[VCAP_KFS_PURE_5TUPLE_IP4] = \"VCAP_KFS_PURE_5TUPLE_IP4\",\n+\t[VCAP_KFS_SGL_IP4] = \"VCAP_KFS_SGL_IP4\",\n+\t[VCAP_KFS_SGL_IP6] = \"VCAP_KFS_SGL_IP6\",\n \t[VCAP_KFS_SMAC_SIP4] = \"VCAP_KFS_SMAC_SIP4\",\n \t[VCAP_KFS_SMAC_SIP6] = \"VCAP_KFS_SMAC_SIP6\",\n };\n@@ -3560,6 +3880,8 @@ static const char * const vcap_keyfield_set_names[] = {\n /* Actionfieldset names */\n static const char * const vcap_actionfield_set_names[] = {\n \t[VCAP_AFS_NO_VALUE] = \"(None)\",\n+\t[VCAP_AFS_ARP_ENTRY] = \"VCAP_AFS_ARP_ENTRY\",\n+\t[VCAP_AFS_ARP_PTR] = \"VCAP_AFS_ARP_PTR\",\n \t[VCAP_AFS_BASE_TYPE] = \"VCAP_AFS_BASE_TYPE\",\n \t[VCAP_AFS_CLASSIFICATION] = \"VCAP_AFS_CLASSIFICATION\",\n \t[VCAP_AFS_CLASS_REDUCED] = \"VCAP_AFS_CLASS_REDUCED\",\n@@ -3596,6 +3918,7 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_8021Q_VLAN_TAGGED_IS] = \"8021Q_VLAN_TAGGED_IS\",\n \t[VCAP_KF_8021Q_VLAN_TAGS] = \"8021Q_VLAN_TAGS\",\n \t[VCAP_KF_ACL_GRP_ID] = \"ACL_GRP_ID\",\n+\t[VCAP_KF_AFFIX] = \"AFFIX\",\n \t[VCAP_KF_ARP_ADDR_SPACE_OK_IS] = \"ARP_ADDR_SPACE_OK_IS\",\n \t[VCAP_KF_ARP_LEN_OK_IS] = \"ARP_LEN_OK_IS\",\n \t[VCAP_KF_ARP_OPCODE] = \"ARP_OPCODE\",\n@@ -3604,6 +3927,7 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_ARP_SENDER_MATCH_IS] = \"ARP_SENDER_MATCH_IS\",\n \t[VCAP_KF_ARP_TGT_MATCH_IS] = \"ARP_TGT_MATCH_IS\",\n \t[VCAP_KF_COSID_CLS] = \"COSID_CLS\",\n+\t[VCAP_KF_DST_FLAG] = \"DST_FLAG\",\n \t[VCAP_KF_ES0_ISDX_KEY_ENA] = \"ES0_ISDX_KEY_ENA\",\n \t[VCAP_KF_ETYPE] = \"ETYPE\",\n \t[VCAP_KF_ETYPE_LEN_IS] = \"ETYPE_LEN_IS\",\n@@ -3617,7 +3941,13 @@ static const char * const vcap_keyfield_names[] = {\n \t[VCAP_KF_IF_IGR_PORT_MASK_RNG] = \"IF_IGR_PORT_MASK_RNG\",\n \t[VCAP_KF_IF_IGR_PORT_MASK_SEL] = \"IF_IGR_PORT_MASK_SEL\",\n \t[VCAP_KF_IF_IGR_PORT_SEL] = \"IF_IGR_PORT_SEL\",\n+\t[VCAP_KF_IP4_DIP] = \"IP4_DIP\",\n \t[VCAP_KF_IP4_IS] = \"IP4_IS\",\n+\t[VCAP_KF_IP4_SIP] = \"IP4_SIP\",\n+\t[VCAP_KF_IP4_XIP] = \"IP4_XIP\",\n+\t[VCAP_KF_IP6_DIP] = \"IP6_DIP\",\n+\t[VCAP_KF_IP6_SIP] = \"IP6_SIP\",\n+\t[VCAP_KF_IP6_XIP] = \"IP6_XIP\",\n \t[VCAP_KF_IP_MC_IS] = \"IP_MC_IS\",\n \t[VCAP_KF_IP_PAYLOAD_5TUPLE] = \"IP_PAYLOAD_5TUPLE\",\n \t[VCAP_KF_IP_SNAP_IS] = \"IP_SNAP_IS\",\n@@ -3690,6 +4020,10 @@ static const char * const vcap_keyfield_names[] = {\n static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_NO_VALUE] = \"(None)\",\n \t[VCAP_AF_ACL_ID] = \"ACL_ID\",\n+\t[VCAP_AF_ARP_ENA] = \"ARP_ENA\",\n+\t[VCAP_AF_ARP_PTR] = \"ARP_PTR\",\n+\t[VCAP_AF_ARP_PTR_REMAP_ENA] = \"ARP_PTR_REMAP_ENA\",\n+\t[VCAP_AF_ARP_VMID] = \"ARP_VMID\",\n \t[VCAP_AF_CLS_VID_SEL] = \"CLS_VID_SEL\",\n \t[VCAP_AF_CNT_ID] = \"CNT_ID\",\n \t[VCAP_AF_COPY_PORT_NUM] = \"COPY_PORT_NUM\",\n@@ -3707,6 +4041,8 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_DSCP_ENA] = \"DSCP_ENA\",\n \t[VCAP_AF_DSCP_SEL] = \"DSCP_SEL\",\n \t[VCAP_AF_DSCP_VAL] = \"DSCP_VAL\",\n+\t[VCAP_AF_ECMP_CNT] = \"ECMP_CNT\",\n+\t[VCAP_AF_ENCAP_ID] = \"ENCAP_ID\",\n \t[VCAP_AF_ES2_REW_CMD] = \"ES2_REW_CMD\",\n \t[VCAP_AF_ESDX] = \"ESDX\",\n \t[VCAP_AF_FWD_KILL_ENA] = \"FWD_KILL_ENA\",\n@@ -3721,6 +4057,8 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_ISDX_VAL] = \"ISDX_VAL\",\n \t[VCAP_AF_LOOP_ENA] = \"LOOP_ENA\",\n \t[VCAP_AF_LRN_DIS] = \"LRN_DIS\",\n+\t[VCAP_AF_MAC_LSB] = \"MAC_LSB\",\n+\t[VCAP_AF_MAC_MSB] = \"MAC_MSB\",\n \t[VCAP_AF_MAP_IDX] = \"MAP_IDX\",\n \t[VCAP_AF_MAP_KEY] = \"MAP_KEY\",\n \t[VCAP_AF_MAP_LOOKUP_SEL] = \"MAP_LOOKUP_SEL\",\n@@ -3754,7 +4092,12 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_QOS_ENA] = \"QOS_ENA\",\n \t[VCAP_AF_QOS_VAL] = \"QOS_VAL\",\n \t[VCAP_AF_REW_OP] = \"REW_OP\",\n+\t[VCAP_AF_RGID] = \"RGID\",\n+\t[VCAP_AF_RSDX] = \"RSDX\",\n \t[VCAP_AF_RT_DIS] = \"RT_DIS\",\n+\t[VCAP_AF_SECUR_MATCH_MAC_ENA] = \"SECUR_MATCH_MAC_ENA\",\n+\t[VCAP_AF_SECUR_MATCH_VMID_ENA] = \"SECUR_MATCH_VMID_ENA\",\n+\t[VCAP_AF_SIP_RPF_ENA] = \"SIP_RPF_ENA\",\n \t[VCAP_AF_SWAP_MACS_ENA] = \"SWAP_MACS_ENA\",\n \t[VCAP_AF_TAG_A_DEI_SEL] = \"TAG_A_DEI_SEL\",\n \t[VCAP_AF_TAG_A_PCP_SEL] = \"TAG_A_PCP_SEL\",\n@@ -3774,6 +4117,7 @@ static const char * const vcap_actionfield_names[] = {\n \t[VCAP_AF_VID_B_VAL] = \"VID_B_VAL\",\n \t[VCAP_AF_VID_C_VAL] = \"VID_C_VAL\",\n \t[VCAP_AF_VID_VAL] = \"VID_VAL\",\n+\t[VCAP_AF_ZERO_DMAC_CPU_QU] = \"ZERO_DMAC_CPU_QU\",\n };\n \n /* VCAPs */\n@@ -3841,6 +4185,27 @@ const struct vcap_info sparx5_vcaps[] = {\n \t\t.keyfield_set_typegroups = es0_keyfield_set_typegroups,\n \t\t.actionfield_set_typegroups = es0_actionfield_set_typegroups,\n \t},\n+\t[VCAP_TYPE_LPM] = {\n+\t\t.name = \"lpm\",\n+\t\t.rows = 512,\n+\t\t.sw_count = 12,\n+\t\t.sw_width = 52,\n+\t\t.sticky_width = 1,\n+\t\t.act_width = 89,\n+\t\t.default_cnt = 0,\n+\t\t.require_cnt_dis = 0,\n+\t\t.version = 1,\n+\t\t.keyfield_set = lpm_keyfield_set,\n+\t\t.keyfield_set_size = ARRAY_SIZE(lpm_keyfield_set),\n+\t\t.actionfield_set = lpm_actionfield_set,\n+\t\t.actionfield_set_size = ARRAY_SIZE(lpm_actionfield_set),\n+\t\t.keyfield_set_map = lpm_keyfield_set_map,\n+\t\t.keyfield_set_map_size = lpm_keyfield_set_map_size,\n+\t\t.actionfield_set_map = lpm_actionfield_set_map,\n+\t\t.actionfield_set_map_size = lpm_actionfield_set_map_size,\n+\t\t.keyfield_set_typegroups = lpm_keyfield_set_typegroups,\n+\t\t.actionfield_set_typegroups = lpm_actionfield_set_typegroups,\n+\t},\n \t[VCAP_TYPE_ES2] = {\n \t\t.name = \"es2\",\n \t\t.rows = 1024,\n@@ -3866,7 +4231,7 @@ const struct vcap_info sparx5_vcaps[] = {\n \n const struct vcap_statistics sparx5_vcap_stats = {\n \t.name = \"sparx5\",\n-\t.count = 4,\n+\t.count = 5,\n \t.keyfield_set_names = vcap_keyfield_set_names,\n \t.actionfield_set_names = vcap_actionfield_set_names,\n \t.keyfield_names = vcap_keyfield_names,\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h\nindex e68f5639a40a5..22bb416f433d8 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h\n@@ -1,10 +1,10 @@\n /* SPDX-License-Identifier: BSD-3-Clause */\n-/* Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries.\n+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.\n * Microchip VCAP API\n */\n \n-/* This file is autogenerated by cml-utils 2022-10-13 10:04:41 +0200.\n- * Commit ID: fd7cafd175899f0672c73afb3a30fc872500ae86\n+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.\n+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a\n */\n \n #ifndef __SPARX5_VCAP_AG_API_H__\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c\nindex cf332de6bf736..20450462e2d80 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c\n@@ -54,8 +54,8 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {\n \t\t.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,\n \t\t.first_cid = SPARX5_VCAP_CID_IS0_L0,\n \t\t.last_cid = SPARX5_VCAP_CID_IS0_L2 - 1,\n-\t\t.blockno = 8, /* Maps block 8-9 */\n-\t\t.blocks = 2,\n+\t\t.blockno = 8, /* Maps block 8 */\n+\t\t.blocks = 1,\n \t\t.ingress = true,\n \t},\n \t{\n@@ -124,6 +124,18 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {\n \t\t.count = 12288, /* Addresses according to datasheet */\n \t\t.ingress = false,\n \t},\n+\t{\n+\t\t.vtype = VCAP_TYPE_LPM,\n+\t\t.vinst = 0,\n+\t\t.map_id = 6,\n+\t\t.lookups = SPARX5_LPM_LOOKUPS,\n+\t\t.lookups_per_instance = SPARX5_LPM_LOOKUPS,\n+\t\t.first_cid = SPARX5_VCAP_CID_LPM_L0,\n+\t\t.last_cid = SPARX5_VCAP_CID_LPM_MAX,\n+\t\t.blockno = 9,\n+\t\t.blocks = 1,\n+\t\t.ingress = true,\n+\t},\n };\n \n /* These protocols have dedicated keysets in IS0 and a TC dissector */\n@@ -149,6 +161,12 @@ static u16 sparx5_vcap_es2_known_etypes[] = {\n \tETH_P_IPV6,\n };\n \n+static u16 sparx5_vcap_lpm_known_etypes[] = {\n+\tETH_P_ALL,\n+\tETH_P_IP,\n+\tETH_P_IPV6,\n+};\n+\n static void sparx5_vcap_type_err(struct sparx5 *sparx5,\n \t\t\t\t struct vcap_admin *admin,\n \t\t\t\t const char *fname)\n@@ -195,6 +213,7 @@ static void _sparx5_vcap_range_init(struct sparx5 *sparx5,\n \tu32 size = count - 1;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n \tcase VCAP_TYPE_IS0:\n \tcase VCAP_TYPE_IS2:\n \t\tspx5_wr(VCAP_SUPER_CFG_MV_NUM_POS_SET(0) |\n@@ -386,6 +405,11 @@ static int sparx5_vcap_es2_cid_to_lookup(int cid)\n \treturn lookup;\n }\n \n+static int sparx5_vcap_lpm_cid_to_lookup(int cid)\n+{\n+\treturn 0;\n+}\n+\n /* Add ethernet type IS0 keyset to a list */\n static void\n sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,\n@@ -401,6 +425,28 @@ sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,\n \t}\n }\n \n+static int sparx5_vcap_lpm_get_port_keysets(struct net_device *ndev,\n+\t\t\t\t\t int lookup,\n+\t\t\t\t\t struct vcap_keyset_list *keysetlist,\n+\t\t\t\t\t u16 l3_proto)\n+{\n+\t/* LPM keysets are static. */\n+\tif (l3_proto == ETH_P_ALL) {\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);\n+\t} else if (l3_proto == ETH_P_IP) {\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);\n+\t} else if (l3_proto == ETH_P_IPV6) {\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);\n+\t\tvcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);\n+\t}\n+\n+\treturn 0;\n+}\n+\n /* Return the list of keysets for the vcap port configuration */\n static int sparx5_vcap_is0_get_port_keysets(struct net_device *ndev,\n \t\t\t\t\t int lookup,\n@@ -683,6 +729,11 @@ int sparx5_vcap_get_port_keyset(struct net_device *ndev,\n \tstruct sparx5_port *port;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tlookup = sparx5_vcap_lpm_cid_to_lookup(cid);\n+\t\terr = sparx5_vcap_lpm_get_port_keysets(ndev, lookup, kslist,\n+\t\t\t\t\t\t l3_proto);\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tlookup = sparx5_vcap_is0_cid_to_lookup(cid);\n \t\terr = sparx5_vcap_is0_get_port_keysets(ndev, lookup, kslist,\n@@ -716,6 +767,10 @@ bool sparx5_vcap_is_known_etype(struct vcap_admin *admin, u16 etype)\n \tint size, idx;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tknown_etypes = sparx5_vcap_lpm_known_etypes;\n+\t\tsize = ARRAY_SIZE(sparx5_vcap_lpm_known_etypes);\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tknown_etypes = sparx5_vcap_is0_known_etypes;\n \t\tsize = ARRAY_SIZE(sparx5_vcap_is0_known_etypes);\n@@ -760,6 +815,11 @@ sparx5_vcap_validate_keyset(struct net_device *ndev,\n \n \t/* Get a list of currently configured keysets in the lookups */\n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tlookup = sparx5_vcap_lpm_cid_to_lookup(rule-\u003evcap_chain_id);\n+\t\tsparx5_vcap_lpm_get_port_keysets(ndev, lookup, \u0026keysetlist,\n+\t\t\t\t\t\t l3_proto);\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tlookup = sparx5_vcap_is0_cid_to_lookup(rule-\u003evcap_chain_id);\n \t\tsparx5_vcap_is0_get_port_keysets(ndev, lookup, \u0026keysetlist,\n@@ -873,6 +933,9 @@ static void sparx5_vcap_add_default_fields(struct net_device *ndev,\n \n \t/* add the lookup bit */\n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\t/* LPM VCAP has no default fields */\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \tcase VCAP_TYPE_IS2:\n \t\tsparx5_vcap_ingress_add_default_fields(ndev, admin, rule);\n@@ -1083,6 +1146,46 @@ static void sparx5_vcap_es2_cache_write(struct sparx5 *sparx5,\n \t}\n }\n \n+static void sparx5_vcap_lpm_cache_write(struct sparx5 *sparx5,\n+\t\t\t\t\tstruct vcap_admin *admin,\n+\t\t\t\t\tenum vcap_selection sel,\n+\t\t\t\t\tu32 start,\n+\t\t\t\t\tu32 count)\n+{\n+\tu32 *keystr, *mskstr, *actstr;\n+\tint idx;\n+\n+\tkeystr = \u0026admin-\u003ecache.keystream[start];\n+\tmskstr = \u0026admin-\u003ecache.maskstream[start];\n+\tactstr = \u0026admin-\u003ecache.actionstream[start];\n+\n+\tswitch (sel) {\n+\tcase VCAP_SEL_ENTRY:\n+\t\tfor (idx = 0; idx \u003c count; ++idx) {\n+\t\t\t/* Avoid 'match-off' by setting value \u0026 mask */\n+\t\t\tspx5_wr(keystr[idx] \u0026 mskstr[idx], sparx5,\n+\t\t\t\tVCAP_SUPER_VCAP_ENTRY_DAT(idx));\n+\t\t\tspx5_wr(~mskstr[idx], sparx5,\n+\t\t\t\tVCAP_SUPER_VCAP_MASK_DAT(idx));\n+\t\t}\n+\t\tbreak;\n+\tcase VCAP_SEL_ACTION:\n+\t\tfor (idx = 0; idx \u003c count; ++idx)\n+\t\t\tspx5_wr(actstr[idx], sparx5,\n+\t\t\t\tVCAP_SUPER_VCAP_ACTION_DAT(idx));\n+\t\tbreak;\n+\tcase VCAP_SEL_ALL:\n+\t\tpr_err(\"%s:%d: cannot write all streams at once\\n\", __func__,\n+\t\t __LINE__);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\tif (sel \u0026 VCAP_SEL_COUNTER)\n+\t\tspx5_wr(admin-\u003ecache.counter, sparx5,\n+\t\t\tVCAP_SUPER_VCAP_CNT_DAT(0));\n+}\n+\n /* API callback used for writing to the VCAP cache */\n static void sparx5_vcap_cache_write(struct net_device *ndev,\n \t\t\t\t struct vcap_admin *admin,\n@@ -1094,6 +1197,9 @@ static void sparx5_vcap_cache_write(struct net_device *ndev,\n \tstruct sparx5 *sparx5 = port-\u003esparx5;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tsparx5_vcap_lpm_cache_write(sparx5, admin, sel, start, count);\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tsparx5_vcap_is0_cache_write(sparx5, admin, sel, start, count);\n \t\tbreak;\n@@ -1273,6 +1379,41 @@ static void sparx5_vcap_es2_cache_read(struct sparx5 *sparx5,\n \t}\n }\n \n+static void sparx5_vcap_lpm_cache_read(struct sparx5 *sparx5,\n+\t\t\t\t struct vcap_admin *admin,\n+\t\t\t\t enum vcap_selection sel,\n+\t\t\t\t u32 start,\n+\t\t\t\t u32 count)\n+{\n+\tu32 *keystr, *mskstr, *actstr;\n+\tint idx;\n+\n+\tkeystr = \u0026admin-\u003ecache.keystream[start];\n+\tmskstr = \u0026admin-\u003ecache.maskstream[start];\n+\tactstr = \u0026admin-\u003ecache.actionstream[start];\n+\n+\tif (sel \u0026 VCAP_SEL_ENTRY) {\n+\t\tfor (idx = 0; idx \u003c count; ++idx) {\n+\t\t\tkeystr[idx] = spx5_rd(sparx5,\n+\t\t\t\t\t VCAP_SUPER_VCAP_ENTRY_DAT(idx));\n+\t\t\tmskstr[idx] = ~spx5_rd(sparx5,\n+\t\t\t\t\t VCAP_SUPER_VCAP_MASK_DAT(idx));\n+\t\t}\n+\t}\n+\n+\tif (sel \u0026 VCAP_SEL_ACTION)\n+\t\tfor (idx = 0; idx \u003c count; ++idx)\n+\t\t\tactstr[idx] = spx5_rd(sparx5,\n+\t\t\t\t\t VCAP_SUPER_VCAP_ACTION_DAT(idx));\n+\n+\tif (sel \u0026 VCAP_SEL_COUNTER) {\n+\t\tu32 val = spx5_rd(sparx5, VCAP_SUPER_VCAP_CNT_DAT(0));\n+\n+\t\tadmin-\u003ecache.counter = val;\n+\t\tadmin-\u003ecache.sticky = !!val;\n+\t}\n+}\n+\n /* API callback used for reading from the VCAP into the VCAP cache */\n static void sparx5_vcap_cache_read(struct net_device *ndev,\n \t\t\t\t struct vcap_admin *admin,\n@@ -1284,6 +1425,9 @@ static void sparx5_vcap_cache_read(struct net_device *ndev,\n \tstruct sparx5 *sparx5 = port-\u003esparx5;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tsparx5_vcap_lpm_cache_read(sparx5, admin, sel, start, count);\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tsparx5_vcap_is0_cache_read(sparx5, admin, sel, start, count);\n \t\tbreak;\n@@ -1379,6 +1523,7 @@ static void sparx5_vcap_update(struct net_device *ndev,\n \tstruct sparx5 *sparx5 = port-\u003esparx5;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n \tcase VCAP_TYPE_IS0:\n \tcase VCAP_TYPE_IS2:\n \t\tsparx5_vcap_super_update(sparx5, cmd, sel, addr);\n@@ -1475,6 +1620,7 @@ static void sparx5_vcap_move(struct net_device *ndev, struct vcap_admin *admin,\n \t}\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n \tcase VCAP_TYPE_IS0:\n \tcase VCAP_TYPE_IS2:\n \t\tsparx5_vcap_super_move(sparx5, addr, cmd, mv_num_pos, mv_size);\n@@ -1743,6 +1889,8 @@ void sparx5_vcap_set_port_keyset(struct net_device *ndev,\n \tint lookup;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n+\t\tbreak;\n \tcase VCAP_TYPE_IS0:\n \t\tlookup = sparx5_vcap_is0_cid_to_lookup(cid);\n \t\tif (orig)\n@@ -1879,6 +2027,9 @@ static void sparx5_vcap_port_key_selection(struct sparx5 *sparx5,\n \tcase VCAP_TYPE_ES2:\n \t\tsparx5_vcap_es2_port_key_selection(sparx5, admin);\n \t\tbreak;\n+\tcase VCAP_TYPE_LPM:\n+\t\t/* VCAP LPM key selection is static */\n+\t\tbreak;\n \tdefault:\n \t\tsparx5_vcap_type_err(sparx5, admin, __func__);\n \t\tbreak;\n@@ -1920,6 +2071,9 @@ static void sparx5_vcap_port_key_deselection(struct sparx5 *sparx5,\n \t\t\t\t\t sparx5,\n \t\t\t\t\t EACL_VCAP_ES2_KEY_SEL(portno, lookup));\n \t\tbreak;\n+\tcase VCAP_TYPE_LPM:\n+\t\t/* LPM key selection is static */\n+\t\tbreak;\n \tdefault:\n \t\tsparx5_vcap_type_err(sparx5, admin, __func__);\n \t\tbreak;\n@@ -1979,6 +2133,7 @@ static void sparx5_vcap_block_alloc(struct sparx5 *sparx5,\n \tint idx, cores;\n \n \tswitch (admin-\u003evtype) {\n+\tcase VCAP_TYPE_LPM:\n \tcase VCAP_TYPE_IS0:\n \tcase VCAP_TYPE_IS2:\n \t\t/* Super VCAP block mapping and address configuration. Block 0\ndiff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h\nindex d0a42406bf26c..94c77a60e4f84 100644\n--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h\n+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h\n@@ -20,6 +20,7 @@\n #define SPARX5_IS0_LOOKUPS 6\n #define SPARX5_ES0_LOOKUPS 1\n #define SPARX5_ES2_LOOKUPS 2\n+#define SPARX5_LPM_LOOKUPS 1\n \n #define SPARX5_VCAP_CID_IS0_L0 VCAP_CID_INGRESS_L0 /* IS0/CLM lookup 0 */\n #define SPARX5_VCAP_CID_IS0_L1 VCAP_CID_INGRESS_L1 /* IS0/CLM lookup 1 */\n@@ -30,6 +31,10 @@\n #define SPARX5_VCAP_CID_IS0_MAX \\\n \t(VCAP_CID_INGRESS_L5 + VCAP_CID_LOOKUP_SIZE - 1) /* IS0/CLM Max */\n \n+#define SPARX5_VCAP_CID_LPM_L0 VCAP_CID_PREROUTING_L0 /* LPM lookup 0 */\n+#define SPARX5_VCAP_CID_LPM_MAX \\\n+\t(VCAP_CID_PREROUTING_L0 + VCAP_CID_LOOKUP_SIZE - 1) /* LPM Max */\n+\n #define SPARX5_VCAP_CID_IS2_L0 VCAP_CID_INGRESS_STAGE2_L0 /* IS2 lookup 0 */\n #define SPARX5_VCAP_CID_IS2_L1 VCAP_CID_INGRESS_STAGE2_L1 /* IS2 lookup 1 */\n #define SPARX5_VCAP_CID_IS2_L2 VCAP_CID_INGRESS_STAGE2_L2 /* IS2 lookup 2 */\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h b/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h\nindex 4735fad057081..487d564409365 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h\n@@ -1,10 +1,10 @@\n /* SPDX-License-Identifier: BSD-3-Clause */\n-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.\n+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.\n * Microchip VCAP API\n */\n \n-/* This file is autogenerated by cml-utils 2023-03-13 10:16:42 +0100.\n- * Commit ID: 259f0efd6d6d91bfbf62858de153cc757b6bffa3 (dirty)\n+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.\n+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a\n */\n \n #ifndef __VCAP_AG_API__\n@@ -16,6 +16,7 @@ enum vcap_type {\n \tVCAP_TYPE_IS0,\n \tVCAP_TYPE_IS1,\n \tVCAP_TYPE_IS2,\n+\tVCAP_TYPE_LPM,\n \tVCAP_TYPE_MAX\n };\n \n@@ -26,6 +27,8 @@ enum vcap_keyfield_set {\n \tVCAP_KFS_5TUPLE_IP6, /* lan966x is1 X4 */\n \tVCAP_KFS_7TUPLE, /* lan966x is1 X4 */\n \tVCAP_KFS_ARP, /* sparx5 is2 X6, sparx5 es2 X6, lan966x is2 X2 */\n+\tVCAP_KFS_DBL_IP4, /* sparx5 lpm X2 */\n+\tVCAP_KFS_DBL_IP6, /* sparx5 lpm X6 */\n \tVCAP_KFS_DBL_VID, /* lan966x is1 X1 */\n \tVCAP_KFS_DMAC_VID, /* lan966x is1 X1 */\n \tVCAP_KFS_ETAG, /* sparx5 is0 X2 */\n@@ -49,6 +52,8 @@ enum vcap_keyfield_set {\n \tVCAP_KFS_OAM, /* lan966x is2 X2 */\n \tVCAP_KFS_PURE_5TUPLE_IP4, /* sparx5 is0 X3 */\n \tVCAP_KFS_RT, /* lan966x is1 X1 */\n+\tVCAP_KFS_SGL_IP4, /* sparx5 lpm X1 */\n+\tVCAP_KFS_SGL_IP6, /* sparx5 lpm X3 */\n \tVCAP_KFS_SMAC_SIP4, /* lan966x is2 X1 */\n \tVCAP_KFS_SMAC_SIP6, /* lan966x is2 X2 */\n \tVCAP_KFS_VID, /* lan966x es0 X1 */\n@@ -117,6 +122,10 @@ enum vcap_keyfield_set {\n * tagged, 7: Triple tagged\n * VCAP_KF_ACL_GRP_ID: W8, sparx5: es2\n * Used in interface map table\n+ * VCAP_KF_AFFIX: W10, sparx5: lpm\n+ * LPM affix. Defaults to 0. Using the LPM affix, the VCAP LPM can be split into\n+ * multiple logically separate routing tables, e.g. to support features such as\n+ * VRF-lite.\n * VCAP_KF_ARP_ADDR_SPACE_OK_IS: W1, sparx5: is2/es2, lan966x: is2\n * Set if hardware address is Ethernet\n * VCAP_KF_ARP_LEN_OK_IS: W1, sparx5: is2/es2, lan966x: is2\n@@ -133,6 +142,9 @@ enum vcap_keyfield_set {\n * Target Hardware Address = SMAC (RARP)\n * VCAP_KF_COSID_CLS: W3, sparx5: es0/es2\n * Class of service\n+ * VCAP_KF_DST_FLAG: W1, sparx5: lpm\n+ * 0: IP4_XIP/IP6_XIP is only to be used for SIP matching, 1: IP4_XIP/IP6_XIP is\n+ * only to be used for DIP matching\n * VCAP_KF_ES0_ISDX_KEY_ENA: W1, sparx5: es2\n * The value taken from the IFH .FWD.ES0_ISDX_KEY_ENA\n * VCAP_KF_ETYPE: W16, sparx5: is0/is2/es2, lan966x: is1/is2\n@@ -167,8 +179,22 @@ enum vcap_keyfield_set {\n * Mapping: 0: DEFAULT 1: LOOPBACK 2: MASQUERADE 3: CPU_VD\n * VCAP_KF_IF_IGR_PORT_SEL: W1, sparx5: es2\n * Selector for IF_IGR_PORT: physical port number or ERLEG\n+ * VCAP_KF_IP4_DIP: W32, sparx5: lpm\n+ * IPv4 destination address. Used for IPv4 MC routing.\n * VCAP_KF_IP4_IS: W1, sparx5: is0/is2/es2, lan966x: is1/is2\n * Set if frame has EtherType = 0x800 and IP version = 4\n+ * VCAP_KF_IP4_SIP: W32, sparx5: lpm\n+ * IPv4 source address. Used for IPv4 MC routing.\n+ * VCAP_KF_IP4_XIP: W32, sparx5: lpm\n+ * IPv4 address. Used for IPv4 UC routing as well as IPv4 Source/Destination\n+ * Guard.\n+ * VCAP_KF_IP6_DIP: W128, sparx5: lpm\n+ * IPv6 destination address. Used for IPv6 MC routing.\n+ * VCAP_KF_IP6_SIP: W128, sparx5: lpm\n+ * IPv6 source address. Used for IPv6 MC routing.\n+ * VCAP_KF_IP6_XIP: W128, sparx5: lpm\n+ * IPv6 address. Used for IPv6 UC routing as well as IPv6 Source/Destination\n+ * Guard.\n * VCAP_KF_IP_MC_IS: W1, sparx5: is0, lan966x: is1\n * Set if frame is IPv4 frame and frame's destination MAC address is an IPv4\n * multicast address (0x01005E0 /25). Set if frame is IPv6 frame and frame's\n@@ -352,8 +378,8 @@ enum vcap_keyfield_set {\n * Set if frame is IPv4/IPv6 TCP or UDP frame (IP protocol/next header equals 6\n * or 17)\n * VCAP_KF_TYPE: sparx5 is0 W2, sparx5 is0 W1, sparx5 is2 W4, sparx5 is2 W2,\n- * sparx5 es0 W1, sparx5 es2 W3, lan966x is1 W1, lan966x is1 W2, lan966x is2 W4,\n- * lan966x is2 W2\n+ * sparx5 es0 W1, sparx5 lpm W1, sparx5 es2 W3, lan966x is1 W1, lan966x is1 W2,\n+ * lan966x is2 W4, lan966x is2 W2\n * Keyset type id - set by the API\n */\n \n@@ -387,6 +413,7 @@ enum vcap_key_field {\n \tVCAP_KF_8021Q_VLAN_TAGGED_IS,\n \tVCAP_KF_8021Q_VLAN_TAGS,\n \tVCAP_KF_ACL_GRP_ID,\n+\tVCAP_KF_AFFIX,\n \tVCAP_KF_ARP_ADDR_SPACE_OK_IS,\n \tVCAP_KF_ARP_LEN_OK_IS,\n \tVCAP_KF_ARP_OPCODE,\n@@ -395,6 +422,7 @@ enum vcap_key_field {\n \tVCAP_KF_ARP_SENDER_MATCH_IS,\n \tVCAP_KF_ARP_TGT_MATCH_IS,\n \tVCAP_KF_COSID_CLS,\n+\tVCAP_KF_DST_FLAG,\n \tVCAP_KF_ES0_ISDX_KEY_ENA,\n \tVCAP_KF_ETYPE,\n \tVCAP_KF_ETYPE_LEN_IS,\n@@ -408,7 +436,13 @@ enum vcap_key_field {\n \tVCAP_KF_IF_IGR_PORT_MASK_RNG,\n \tVCAP_KF_IF_IGR_PORT_MASK_SEL,\n \tVCAP_KF_IF_IGR_PORT_SEL,\n+\tVCAP_KF_IP4_DIP,\n \tVCAP_KF_IP4_IS,\n+\tVCAP_KF_IP4_SIP,\n+\tVCAP_KF_IP4_XIP,\n+\tVCAP_KF_IP6_DIP,\n+\tVCAP_KF_IP6_SIP,\n+\tVCAP_KF_IP6_XIP,\n \tVCAP_KF_IP_MC_IS,\n \tVCAP_KF_IP_PAYLOAD_5TUPLE,\n \tVCAP_KF_IP_PAYLOAD_S1_IP6,\n@@ -490,6 +524,8 @@ enum vcap_key_field {\n /* Actionset names with origin information */\n enum vcap_actionfield_set {\n \tVCAP_AFS_NO_VALUE, /* initial value */\n+\tVCAP_AFS_ARP_ENTRY, /* sparx5 lpm X1 */\n+\tVCAP_AFS_ARP_PTR, /* sparx5 lpm X1 */\n \tVCAP_AFS_BASE_TYPE, /* sparx5 is2 X3, sparx5 es2 X3, lan966x is2 X2 */\n \tVCAP_AFS_CLASSIFICATION, /* sparx5 is0 X2 */\n \tVCAP_AFS_CLASS_REDUCED, /* sparx5 is0 X1 */\n@@ -506,6 +542,15 @@ enum vcap_actionfield_set {\n * Logical ID for the entry. This ID is extracted together with the frame in the\n * CPU extraction header. Only applicable to actions with CPU_COPY_ENA or\n * HIT_ME_ONCE set.\n+ * VCAP_AF_ARP_ENA: W1, sparx5: lpm\n+ * Enable entry for address resolution usage.\n+ * VCAP_AF_ARP_PTR: W11, sparx5: lpm\n+ * Pointer to entry in ARP Table.\n+ * VCAP_AF_ARP_PTR_REMAP_ENA: W1, sparx5: lpm\n+ * If this bit is set, ARP_PTR is used to point to an entry in the ARP pointer\n+ * remap table.\n+ * VCAP_AF_ARP_VMID: W9, sparx5: lpm\n+ * Routing lookup. Egress router leg (EVMID).\n * VCAP_AF_CLS_VID_SEL: W3, sparx5: is0\n * Controls the classified VID: 0: VID_NONE: No action. 1: VID_ADD: New VID =\n * old VID + VID_VAL. 2: VID_REPLACE: New VID = VID_VAL. 3: VID_FIRST_TAG: New\n@@ -562,6 +607,11 @@ enum vcap_actionfield_set {\n * 7: Mapped using mapping table 3, otherwise use mapping table 2\n * VCAP_AF_DSCP_VAL: W6, sparx5: is0/es0, lan966x: is1\n * See DSCP_ENA.\n+ * VCAP_AF_ECMP_CNT: W4, sparx5: lpm\n+ * Number of equal cost, multiple paths routes to DIP.\n+ * VCAP_AF_ENCAP_ID: W10, sparx5: lpm\n+ * Index into REW:ENCAP_IP4 when encapsulating IP packet in IPv4. Disabled when\n+ * set to 0.\n * VCAP_AF_ES2_REW_CMD: W3, sparx5: es2\n * Command forwarded to REW: 0: No action. 1: SWAP MAC addresses. 2: Do L2CP\n * DMAC translation when entering or leaving a tunnel.\n@@ -610,6 +660,12 @@ enum vcap_actionfield_set {\n * 0: Forward based on PIPELINE_PT and FWD_SEL\n * VCAP_AF_LRN_DIS: W1, sparx5: is2, lan966x: is2\n * Setting this bit to 1 disables learning of frames hitting this action.\n+ * VCAP_AF_MAC_LSB: W32, sparx5: lpm\n+ * 32 least significant bits of MAC address. Used for ARP entry and/or\n+ * (SMAC,SIP)/(DMAC,DIP) check.\n+ * VCAP_AF_MAC_MSB: W16, sparx5: lpm\n+ * 16 most significant bits of MAC address. Used for ARP entry and/or\n+ * (SMAC,SIP)/(DMAC,DIP) check.\n * VCAP_AF_MAP_IDX: W9, sparx5: is0\n * Index for QoS mapping table lookup\n * VCAP_AF_MAP_KEY: W3, sparx5: is0\n@@ -715,9 +771,17 @@ enum vcap_actionfield_set {\n * See QOS_ENA.\n * VCAP_AF_REW_OP: W16, lan966x: is2\n * Rewriter operation command.\n+ * VCAP_AF_RGID: W3, sparx5: lpm\n+ * Route Group ID. Used for SIP RPF check.\n+ * VCAP_AF_RSDX: W12, sparx5: lpm\n+ * Router Leg Service Index for ARP table entry.\n * VCAP_AF_RT_DIS: W1, sparx5: is2\n * If set, routing is disallowed. Only applies when IS_INNER_ACL is 0. See also\n * IGR_ACL_ENA, EGR_ACL_ENA, and RLEG_STAT_IDX.\n+ * VCAP_AF_SECUR_MATCH_MAC_ENA: W1, sparx5: lpm\n+ * Enable Security MAC check.\n+ * VCAP_AF_SECUR_MATCH_VMID_ENA: W1, sparx5: lpm\n+ * Enable Security VMID check.\n * VCAP_AF_SFID_ENA: W1, lan966x: is1\n * If set, SFID_VAL is used to lookup ANA::SFID.\n * VCAP_AF_SFID_VAL: W8, lan966x: is1\n@@ -726,6 +790,8 @@ enum vcap_actionfield_set {\n * If set, SGID_VAL is used to lookup ANA::SGID.\n * VCAP_AF_SGID_VAL: W8, lan966x: is1\n * Stream gate identifier.\n+ * VCAP_AF_SIP_RPF_ENA: W1, sparx5: lpm\n+ * Enable use for SIP RPF check.\n * VCAP_AF_SWAP_MACS_ENA: W1, sparx5: es0\n * This setting is only active when FWD_SEL = 1 or FWD_SEL = 2 and PIPELINE_ACT\n * = LBK_ASM. 0: No action. 1: Swap MACs and clear bit 40 in new SMAC.\n@@ -774,7 +840,7 @@ enum vcap_actionfield_set {\n * VCAP_AF_TAG_C_VID_SEL: W2, sparx5: es0\n * Selects VID for ES0 tag C. The resulting VID is termed C-TAG.VID. 0:\n * Classified VID. 1: VID_C_VAL. 2: IFH.ENCAP.GVID. 3: Reserved.\n- * VCAP_AF_TYPE: W1, sparx5: is0, lan966x: is1\n+ * VCAP_AF_TYPE: sparx5 is0 W1, sparx5 lpm W2, lan966x is1 W1\n * Actionset type id - Set by the API\n * VCAP_AF_UNTAG_VID_ENA: W1, sparx5: es0\n * Controls insertion of tag C. Untag or insert mode can be selected. See\n@@ -798,12 +864,18 @@ enum vcap_actionfield_set {\n * If set, use VLAN_POP_CNT as the number of VLAN tags to pop from the incoming\n * frame. This number is used by the Rewriter. Otherwise, VLAN_POP_CNT from\n * ANA:PORT:VLAN_CFG.VLAN_POP_CNT is used\n+ * VCAP_AF_ZERO_DMAC_CPU_QU: W3, sparx5: lpm\n+ * CPU queue used for CPU redirect if MAC address in ARP entry is all-zeros.\n */\n \n /* Actionfield names */\n enum vcap_action_field {\n \tVCAP_AF_NO_VALUE, /* initial value */\n \tVCAP_AF_ACL_ID,\n+\tVCAP_AF_ARP_ENA,\n+\tVCAP_AF_ARP_PTR,\n+\tVCAP_AF_ARP_PTR_REMAP_ENA,\n+\tVCAP_AF_ARP_VMID,\n \tVCAP_AF_CLS_VID_SEL,\n \tVCAP_AF_CNT_ID,\n \tVCAP_AF_COPY_PORT_NUM,\n@@ -823,6 +895,8 @@ enum vcap_action_field {\n \tVCAP_AF_DSCP_ENA,\n \tVCAP_AF_DSCP_SEL,\n \tVCAP_AF_DSCP_VAL,\n+\tVCAP_AF_ECMP_CNT,\n+\tVCAP_AF_ENCAP_ID,\n \tVCAP_AF_ES2_REW_CMD,\n \tVCAP_AF_ESDX,\n \tVCAP_AF_FWD_KILL_ENA,\n@@ -839,6 +913,8 @@ enum vcap_action_field {\n \tVCAP_AF_ISDX_VAL,\n \tVCAP_AF_LOOP_ENA,\n \tVCAP_AF_LRN_DIS,\n+\tVCAP_AF_MAC_LSB,\n+\tVCAP_AF_MAC_MSB,\n \tVCAP_AF_MAP_IDX,\n \tVCAP_AF_MAP_KEY,\n \tVCAP_AF_MAP_LOOKUP_SEL,\n@@ -874,11 +950,16 @@ enum vcap_action_field {\n \tVCAP_AF_QOS_ENA,\n \tVCAP_AF_QOS_VAL,\n \tVCAP_AF_REW_OP,\n+\tVCAP_AF_RGID,\n+\tVCAP_AF_RSDX,\n \tVCAP_AF_RT_DIS,\n+\tVCAP_AF_SECUR_MATCH_MAC_ENA,\n+\tVCAP_AF_SECUR_MATCH_VMID_ENA,\n \tVCAP_AF_SFID_ENA,\n \tVCAP_AF_SFID_VAL,\n \tVCAP_AF_SGID_ENA,\n \tVCAP_AF_SGID_VAL,\n+\tVCAP_AF_SIP_RPF_ENA,\n \tVCAP_AF_SWAP_MACS_ENA,\n \tVCAP_AF_TAG_A_DEI_SEL,\n \tVCAP_AF_TAG_A_PCP_SEL,\n@@ -901,6 +982,7 @@ enum vcap_action_field {\n \tVCAP_AF_VID_VAL,\n \tVCAP_AF_VLAN_POP_CNT,\n \tVCAP_AF_VLAN_POP_CNT_ENA,\n+\tVCAP_AF_ZERO_DMAC_CPU_QU,\n };\n \n #endif /* __VCAP_AG_API__ */\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c\nindex 788c0728d7636..1ad2c44da8bd7 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_api.c\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c\n@@ -4,7 +4,9 @@\n * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.\n */\n \n+#include \u003clinux/lcm.h\u003e\n #include \u003clinux/types.h\u003e\n+#include \u003clinux/minmax.h\u003e\n \n #include \"vcap_api_private.h\"\n \n@@ -214,6 +216,13 @@ static void vcap_decode_field(u32 *stream, struct vcap_stream_iter *itr,\n \t}\n }\n \n+/* The type_id field is always right after the typegroup bits, if it exists */\n+static u8 vcap_find_stream_type_id(u32 *stream, u16 tg_width,\n+\t\t\t\t u16 typefld_width)\n+{\n+\treturn (stream[0] \u003e\u003e tg_width) \u0026 GENMASK(typefld_width - 1, 0);\n+}\n+\n /* Verify that the type id in the stream matches the type id of the keyset */\n static bool vcap_verify_keystream_keyset(struct vcap_control *vctrl,\n \t\t\t\t\t enum vcap_type vt,\n@@ -1339,8 +1348,10 @@ vcap_verify_actionstream_actionset(struct vcap_control *vctrl,\n \t\t\t\t enum vcap_actionfield_set actionset)\n {\n \tconst struct vcap_typegroup *tgt;\n+\tconst struct vcap_field *typefld;\n \tconst struct vcap_field *fields;\n \tconst struct vcap_set *info;\n+\tu8 value = 0;\n \n \tif (vcap_actionfield_count(vctrl, vt, actionset) == 0)\n \t\treturn false;\n@@ -1363,8 +1374,11 @@ vcap_verify_actionstream_actionset(struct vcap_control *vctrl,\n \tif (!fields)\n \t\treturn false;\n \n-\t/* Later this will be expanded with a check of the type id */\n-\treturn true;\n+\ttypefld = \u0026fields[VCAP_AF_TYPE];\n+\tvalue = vcap_find_stream_type_id(actionstream,\n+\t\t\t\t\t tgt-\u003ewidth, typefld-\u003ewidth);\n+\n+\treturn value == info-\u003etype_id;\n }\n \n /* Find the subword width of the action typegroup that matches the stream data\n@@ -2106,12 +2120,63 @@ static u32 vcap_set_rule_id(struct vcap_rule_internal *ri)\n \treturn ri-\u003edata.id;\n }\n \n+static void vcap_move_from_block(u32 base_addr,\n+\t\t\t\t struct vcap_rule_internal *block_first,\n+\t\t\t\t struct vcap_rule_internal *block_last,\n+\t\t\t\t int block_lcm, struct vcap_rule_move *move)\n+{\n+\tint unaligned_offset;\n+\n+\tif (!block_first || !block_last)\n+\t\treturn;\n+\n+\tmove-\u003eaddr = block_last-\u003eaddr;\n+\tmove-\u003ecount = block_first-\u003eaddr + block_first-\u003esize - block_last-\u003eaddr;\n+\t/* Integer division rounds toward zero. We want to round toward +inf\n+\t * for the positive case (insertion) to ensure enough room.\n+\t */\n+\tunaligned_offset = (block_first-\u003eaddr + block_first-\u003esize) - base_addr;\n+\tif (unaligned_offset \u003e 0)\n+\t\tmove-\u003eoffset =\n+\t\t\t((unaligned_offset + (block_lcm - 1)) / block_lcm) *\n+\t\t\tblock_lcm;\n+\telse\n+\t\tmove-\u003eoffset = (unaligned_offset / block_lcm) * block_lcm;\n+}\n+\n+static void vcap_move_rules_sw(struct vcap_admin *admin,\n+\t\t\t struct vcap_rule_internal *pos,\n+\t\t\t struct vcap_rule_move const *move)\n+{\n+\tif (move-\u003ecount == 0 || move-\u003eoffset == 0)\n+\t\treturn;\n+\n+\tlist_for_each_entry_continue(pos, \u0026admin-\u003erules, list)\n+\t\tpos-\u003eaddr -= move-\u003eoffset;\n+}\n+\n+static int vcap_find_move_block_lcm(struct vcap_admin *admin,\n+\t\t\t\t struct vcap_rule_internal *pos)\n+{\n+\tint block_lcm = 1;\n+\n+\tlist_for_each_entry_continue(pos, \u0026admin-\u003erules, list) {\n+\t\tblock_lcm = lcm(block_lcm, pos-\u003esize);\n+\t\tif (pos-\u003esize \u003c= 2)\n+\t\t\tbreak;\n+\t}\n+\n+\treturn block_lcm;\n+}\n+\n static int vcap_insert_rule(struct vcap_rule_internal *ri,\n \t\t\t struct vcap_rule_move *move)\n {\n+\tstruct vcap_rule_internal *duprule, *iter, *block_first = NULL,\n+\t\t\t\t\t\t *block_last;\n \tint sw_count = ri-\u003evctrl-\u003evcaps[ri-\u003eadmin-\u003evtype].sw_count;\n-\tstruct vcap_rule_internal *duprule, *iter, *elem = NULL;\n \tstruct vcap_admin *admin = ri-\u003eadmin;\n+\tint block_lcm;\n \tu32 addr;\n \n \tri-\u003esort_key = vcap_sort_key(sw_count, ri-\u003esize, ri-\u003edata.user,\n@@ -2124,12 +2189,12 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,\n \t */\n \tlist_for_each_entry(iter, \u0026admin-\u003erules, list) {\n \t\tif (ri-\u003esort_key \u003c iter-\u003esort_key) {\n-\t\t\telem = iter;\n+\t\t\tblock_first = iter;\n \t\t\tbreak;\n \t\t}\n \t}\n \n-\tif (!elem) {\n+\tif (!block_first) {\n \t\tri-\u003eaddr = vcap_next_rule_addr(admin-\u003elast_used_addr, ri);\n \t\tadmin-\u003elast_used_addr = ri-\u003eaddr;\n \n@@ -2142,10 +2207,13 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,\n \t\treturn 0;\n \t}\n \n-\t/* Reuse the space of the current rule */\n-\taddr = elem-\u003eaddr + elem-\u003esize;\n+\tblock_last = list_last_entry(\u0026admin-\u003erules, typeof(*ri), list);\n+\n+\t/* Reuse the space and padding of the current rule */\n+\taddr = admin-\u003elast_valid_addr + 1;\n+\tif (!list_is_first(\u0026block_first-\u003elist, \u0026admin-\u003erules))\n+\t\taddr = list_prev_entry(block_first, list)-\u003eaddr;\n \tri-\u003eaddr = vcap_next_rule_addr(addr, ri);\n-\taddr = ri-\u003eaddr;\n \n \t/* Add a copy of the rule to the VCAP list */\n \tduprule = vcap_dup_rule(ri, ri-\u003estate == VCAP_RS_DISABLED);\n@@ -2153,29 +2221,34 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,\n \t\treturn PTR_ERR(duprule);\n \n \t/* Add before the current entry */\n-\tlist_add_tail(\u0026duprule-\u003elist, \u0026elem-\u003elist);\n-\n-\t/* Update the current rule */\n-\telem-\u003eaddr = vcap_next_rule_addr(addr, elem);\n-\taddr = elem-\u003eaddr;\n-\n-\t/* Update the address in the remaining rules in the list */\n-\tlist_for_each_entry_continue(elem, \u0026admin-\u003erules, list) {\n-\t\telem-\u003eaddr = vcap_next_rule_addr(addr, elem);\n-\t\taddr = elem-\u003eaddr;\n+\tlist_add_tail(\u0026duprule-\u003elist, \u0026block_first-\u003elist);\n+\n+\t/* Collect block move info in struct move, to update VCAP HW later */\n+\tblock_lcm = vcap_find_move_block_lcm(admin, duprule);\n+\tvcap_move_from_block(ri-\u003eaddr, block_first, block_last, block_lcm,\n+\t\t\t move);\n+\n+\tif ((int)block_last-\u003eaddr - move-\u003eoffset \u003c admin-\u003efirst_valid_addr) {\n+\t\tpr_err(\"%s:%d: No room for rule size: %u, %u\\n\", __func__,\n+\t\t __LINE__, duprule-\u003esize, admin-\u003efirst_valid_addr);\n+\t\tlist_del(\u0026duprule-\u003elist);\n+\t\tvcap_free_rule(\u0026duprule-\u003edata);\n+\t\treturn -ENOSPC;\n \t}\n \n-\t/* Update the move info */\n-\tmove-\u003eaddr = admin-\u003elast_used_addr;\n-\tmove-\u003ecount = ri-\u003eaddr - addr;\n-\tmove-\u003eoffset = admin-\u003elast_used_addr - addr;\n-\tadmin-\u003elast_used_addr = addr;\n+\t/* Apply move to SW */\n+\tvcap_move_rules_sw(admin, duprule, move);\n+\tadmin-\u003elast_used_addr =\n+\t\tlist_last_entry(\u0026admin-\u003erules, typeof(*ri), list)-\u003eaddr;\n \treturn 0;\n }\n \n static void vcap_move_rules(struct vcap_rule_internal *ri,\n \t\t\t struct vcap_rule_move *move)\n {\n+\tif (move-\u003ecount == 0 || move-\u003eoffset == 0)\n+\t\treturn;\n+\n \tri-\u003evctrl-\u003eops-\u003emove(ri-\u003endev, ri-\u003eadmin, move-\u003eaddr,\n \t\t\t move-\u003eoffset, move-\u003ecount);\n }\n@@ -2285,8 +2358,7 @@ int vcap_add_rule(struct vcap_rule *rule)\n \t\t __func__, __LINE__, ret);\n \t\tgoto out;\n \t}\n-\tif (move.count \u003e 0)\n-\t\tvcap_move_rules(ri, \u0026move);\n+\tvcap_move_rules(ri, \u0026move);\n \n \t/* Set the counter to zero */\n \tret = vcap_write_counter(ri, \u0026ctr);\n@@ -2317,6 +2389,19 @@ int vcap_add_rule(struct vcap_rule *rule)\n }\n EXPORT_SYMBOL_GPL(vcap_add_rule);\n \n+/* Validate and add rule to a VCAP instance */\n+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto)\n+{\n+\tint err;\n+\n+\terr = vcap_val_rule(rule, l3_proto);\n+\tif (err)\n+\t\treturn err;\n+\n+\treturn vcap_add_rule(rule);\n+}\n+EXPORT_SYMBOL_GPL(vcap_val_add_rule);\n+\n /* Allocate a new rule with the provided arguments */\n struct vcap_rule *vcap_alloc_rule(struct vcap_control *vctrl,\n \t\t\t\t struct net_device *ndev, int vcap_chain_id,\n@@ -2501,59 +2586,52 @@ int vcap_mod_rule(struct vcap_rule *rule)\n }\n EXPORT_SYMBOL_GPL(vcap_mod_rule);\n \n-/* Return the alignment offset for a new rule address */\n-static int vcap_valid_rule_move(struct vcap_rule_internal *el, int offset)\n-{\n-\treturn (el-\u003eaddr + offset) % el-\u003esize;\n-}\n-\n-/* Update the rule address with an offset */\n-static void vcap_adjust_rule_addr(struct vcap_rule_internal *el, int offset)\n-{\n-\tel-\u003eaddr += offset;\n-}\n-\n /* Rules needs to be moved to fill the gap of the deleted rule */\n static int vcap_fill_rule_gap(struct vcap_rule_internal *ri)\n {\n+\tstruct vcap_rule_internal *block_first = NULL, *block_last = NULL;\n \tstruct vcap_admin *admin = ri-\u003eadmin;\n-\tstruct vcap_rule_internal *elem;\n-\tstruct vcap_rule_move move;\n-\tint gap = 0, offset = 0;\n+\tstruct net_device *ndev = ri-\u003endev;\n+\tstruct vcap_rule_move move = {};\n+\tint block_lcm, addr;\n \n-\t/* If the first rule is deleted: Move other rules to the top */\n-\tif (list_is_first(\u0026ri-\u003elist, \u0026admin-\u003erules))\n-\t\toffset = admin-\u003elast_valid_addr + 1 - ri-\u003eaddr - ri-\u003esize;\n+\tif (!list_is_last(\u0026ri-\u003elist, \u0026admin-\u003erules)) {\n+\t\tblock_first = list_next_entry(ri, list);\n+\t\tblock_last = list_last_entry(\u0026admin-\u003erules, typeof(*ri), list);\n+\t}\n+\n+\taddr = admin-\u003elast_valid_addr + 1;\n+\tif (!list_is_first(\u0026ri-\u003elist, \u0026admin-\u003erules))\n+\t\taddr = list_prev_entry(ri, list)-\u003eaddr;\n \n-\t/* Locate gaps between odd size rules and adjust the move */\n-\telem = ri;\n-\tlist_for_each_entry_continue(elem, \u0026admin-\u003erules, list)\n-\t\tgap += vcap_valid_rule_move(elem, ri-\u003esize);\n+\tblock_lcm = vcap_find_move_block_lcm(admin, ri);\n+\tvcap_move_from_block(addr, block_first, block_last, block_lcm, \u0026move);\n \n-\t/* Update the address in the remaining rules in the list */\n-\telem = ri;\n-\tlist_for_each_entry_continue(elem, \u0026admin-\u003erules, list)\n-\t\tvcap_adjust_rule_addr(elem, ri-\u003esize + gap + offset);\n+\t/* Apply move to SW representation */\n+\tvcap_move_rules_sw(admin, ri, \u0026move);\n \n-\t/* Update the move info */\n-\tmove.addr = admin-\u003elast_used_addr;\n-\tmove.count = ri-\u003eaddr - admin-\u003elast_used_addr - gap;\n-\tmove.offset = -(ri-\u003esize + gap + offset);\n+\t/* Initialize space used by ri, as it may not be overwritten by move */\n+\tri-\u003evctrl-\u003eops-\u003einit(ndev, admin, ri-\u003eaddr, ri-\u003esize);\n \n-\t/* Do the actual move operation */\n+\t/* Apply move to HW. The VCAP move command automatically disables\n+\t * (initializes) the source addresses, so no separate init is needed\n+\t * for the vacated positions.\n+\t */\n \tvcap_move_rules(ri, \u0026move);\n \n-\treturn gap + offset;\n+\tif (block_last)\n+\t\treturn block_last-\u003eaddr;\n+\n+\treturn addr;\n }\n \n /* Delete rule in a VCAP instance */\n int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)\n {\n-\tstruct vcap_rule_internal *ri, *elem;\n+\tstruct vcap_rule_internal *ri;\n \tstruct vcap_admin *admin;\n-\tint gap = 0, err;\n+\tint err;\n \n-\t/* This will later also handle rule moving */\n \tif (!ndev)\n \t\treturn -ENODEV;\n \terr = vcap_api_check(vctrl);\n@@ -2566,23 +2644,12 @@ int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)\n \n \tadmin = ri-\u003eadmin;\n \n-\tif (ri-\u003eaddr \u003e admin-\u003elast_used_addr)\n-\t\tgap = vcap_fill_rule_gap(ri);\n+\tadmin-\u003elast_used_addr = vcap_fill_rule_gap(ri);\n \n \t/* Delete the rule from the list of rules and the cache */\n \tlist_del(\u0026ri-\u003elist);\n-\tvctrl-\u003eops-\u003einit(ndev, admin, admin-\u003elast_used_addr, ri-\u003esize + gap);\n \tvcap_free_rule(\u0026ri-\u003edata);\n \n-\t/* Update the last used address, set to default when no rules */\n-\tif (list_empty(\u0026admin-\u003erules)) {\n-\t\tadmin-\u003elast_used_addr = admin-\u003elast_valid_addr + 1;\n-\t} else {\n-\t\telem = list_last_entry(\u0026admin-\u003erules, struct vcap_rule_internal,\n-\t\t\t\t list);\n-\t\tadmin-\u003elast_used_addr = elem-\u003eaddr;\n-\t}\n-\n \tvcap_unlock(admin);\n \treturn err;\n }\n@@ -3506,6 +3573,18 @@ int vcap_rule_mod_action_u32(struct vcap_rule *rule,\n }\n EXPORT_SYMBOL_GPL(vcap_rule_mod_action_u32);\n \n+/* Modify a bit action with value in the rule */\n+int vcap_rule_mod_action_bit(struct vcap_rule *rule,\n+\t\t\t enum vcap_action_field action,\n+\t\t\t enum vcap_bit val)\n+{\n+\tstruct vcap_client_actionfield_data data = {};\n+\n+\tvcap_rule_set_action_bitsize(\u0026data.u1, val);\n+\treturn vcap_rule_mod_action(rule, action, VCAP_FIELD_BIT, \u0026data);\n+}\n+EXPORT_SYMBOL_GPL(vcap_rule_mod_action_bit);\n+\n /* Drop keys in a keylist and any keys that are not supported by the keyset */\n int vcap_filter_rule_keys(struct vcap_rule *rule,\n \t\t\t enum vcap_key_field keylist[], int length,\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.h b/drivers/net/ethernet/microchip/vcap/vcap_api.h\nindex 05b4b02e59ef5..011d1cb7b594c 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_api.h\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.h\n@@ -22,7 +22,7 @@\n #define VCAP_CID_INGRESS_L5 1500000 /* Ingress Stage 1 Lookup 5 */\n \n #define VCAP_CID_PREROUTING_IPV6 3000000 /* Prerouting Stage */\n-#define VCAP_CID_PREROUTING 6000000 /* Prerouting Stage */\n+#define VCAP_CID_PREROUTING_L0 6000000 /* Prerouting Stage Lookup 0 */\n \n #define VCAP_CID_INGRESS_STAGE2_L0 8000000 /* Ingress Stage 2 Lookup 0 */\n #define VCAP_CID_INGRESS_STAGE2_L1 8100000 /* Ingress Stage 2 Lookup 1 */\n@@ -41,7 +41,9 @@ enum vcap_user {\n \tVCAP_USER_MRP,\n \tVCAP_USER_CFM,\n \tVCAP_USER_VLAN,\n+\tVCAP_USER_L3,\n \tVCAP_USER_QOS,\n+\t/* permanent enabled users above here */\n \tVCAP_USER_VCAP_UTIL,\n \tVCAP_USER_TC,\n \tVCAP_USER_TC_EXTRA,\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h\nindex cdf79e17ca547..3f17e1e76b7de 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h\n@@ -167,6 +167,8 @@ void vcap_free_rule(struct vcap_rule *rule);\n int vcap_val_rule(struct vcap_rule *rule, u16 l3_proto);\n /* Add rule to a VCAP instance */\n int vcap_add_rule(struct vcap_rule *rule);\n+/* Validate and add rule to a VCAP instance */\n+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto);\n /* Delete rule in a VCAP instance */\n int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id);\n /* Make a full copy of an existing rule with a new rule id */\n@@ -266,6 +268,10 @@ int vcap_rule_mod_key_u32(struct vcap_rule *rule, enum vcap_key_field key,\n int vcap_rule_mod_action_u32(struct vcap_rule *rule,\n \t\t\t enum vcap_action_field action,\n \t\t\t u32 value);\n+/* Modify a bit action with value in the rule */\n+int vcap_rule_mod_action_bit(struct vcap_rule *rule,\n+\t\t\t enum vcap_action_field action,\n+\t\t\t enum vcap_bit val);\n \n /* Get a 32 bit key field value and mask from the rule */\n int vcap_rule_get_key_u32(struct vcap_rule *rule, enum vcap_key_field key,\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c\nindex e0c65c7ab23e1..36150822ce153 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c\n@@ -40,7 +40,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,\n \t\tvalue = (u8 *)(\u0026data-\u003eu32.value);\n \t\tmask = (u8 *)(\u0026data-\u003eu32.mask);\n \n-\t\tif (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP) {\n+\t\tif (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP ||\n+\t\t key == VCAP_KF_IP4_XIP) {\n \t\t\tout-\u003eprf(out-\u003edst, \"%pI4h/%pI4h\", \u0026data-\u003eu32.value,\n \t\t\t\t \u0026data-\u003eu32.mask);\n \t\t} else if (key == VCAP_KF_ETYPE ||\n@@ -88,7 +89,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,\n \tcase VCAP_FIELD_U128:\n \t\tvalue = data-\u003eu128.value;\n \t\tmask = data-\u003eu128.mask;\n-\t\tif (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP) {\n+\t\tif (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP ||\n+\t\t key == VCAP_KF_IP6_XIP) {\n \t\t\tu8 nvalue[16], nmask[16];\n \n \t\t\tvcap_netbytes_copy(nvalue, data-\u003eu128.value,\n@@ -133,7 +135,12 @@ vcap_debugfs_show_rule_actionfield(struct vcap_control *vctrl,\n \t\tout-\u003eprf(out-\u003edst, \"%d\", value[0]);\n \t\tbreak;\n \tcase VCAP_FIELD_U32:\n-\t\tfmsk = (1 \u003c\u003c actionfield[action].width) - 1;\n+\t\tif (action == VCAP_AF_MAC_LSB || action == VCAP_AF_MAC_MSB) {\n+\t\t\thex = true;\n+\t\t\tbreak;\n+\t\t}\n+\t\tfmsk = actionfield[action].width ?\n+\t\t GENMASK(actionfield[action].width - 1, 0) : 0;\n \t\tval = *(u32 *)value;\n \t\tout-\u003eprf(out-\u003edst, \"%u\", val \u0026 fmsk);\n \t\tbreak;\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c b/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c\nindex 83de384d3e3bd..675289001a001 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c\n@@ -1576,7 +1576,7 @@ static void vcap_api_rule_insert_in_order_test(struct kunit *test)\n \t\t.lookups = 4,\n \t\t.last_valid_addr = 3071,\n \t\t.first_valid_addr = 0,\n-\t\t.last_used_addr = 800,\n+\t\t.last_used_addr = 804,\n \t\t.cache = {\n \t\t\t.keystream = keydata,\n \t\t\t.maskstream = mskdata,\n@@ -1584,15 +1584,28 @@ static void vcap_api_rule_insert_in_order_test(struct kunit *test)\n \t\t},\n \t};\n \n+\tstruct vcap_rule_internal ri0 = {\n+\t\t.data = {\n+\t\t\t.id = 2001,\n+\t\t},\n+\t\t.addr = 804,\n+\t\t.size = 12,\n+\t\t.sort_key = vcap_sort_key(12, 12, 0, 0),\n+\t\t.admin = \u0026admin,\n+\t\t.counter_id = 2002,\n+\t\t.vctrl = \u0026test_vctrl,\n+\t};\n+\n \tvcap_test_api_init(\u0026admin);\n+\tlist_add_tail(\u0026ri0.list, \u0026admin.rules);\n \n \t/* Create rules with different sizes and check that they are placed\n \t * at the correct address in the VCAP according to size\n \t */\n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);\n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 774);\n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 771);\n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 768);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 792);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 786);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 783);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 780);\n \n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 200);\n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 300);\n@@ -1614,7 +1627,7 @@ static void vcap_api_rule_insert_reverse_order_test(struct kunit *test)\n \t\t.lookups = 4,\n \t\t.last_valid_addr = 3071,\n \t\t.first_valid_addr = 0,\n-\t\t.last_used_addr = 800,\n+\t\t.last_used_addr = 804,\n \t\t.cache = {\n \t\t\t.keystream = keydata,\n \t\t\t.maskstream = mskdata,\n@@ -1622,40 +1635,54 @@ static void vcap_api_rule_insert_reverse_order_test(struct kunit *test)\n \t\t},\n \t};\n \tstruct vcap_rule_internal *elem;\n-\tu32 exp_addr[] = {780, 774, 771, 768, 767};\n+\tu32 exp_addr[] = {804, 792, 786, 783, 780};\n \tint idx;\n \n+\t/* Existing X12 rule at last_used_addr */\n+\tstruct vcap_rule_internal ri0 = {\n+\t\t.data = {\n+\t\t\t.id = 2001,\n+\t\t},\n+\t\t.addr = 804,\n+\t\t.size = 12,\n+\t\t.sort_key = vcap_sort_key(12, 12, 0, 0),\n+\t\t.admin = \u0026admin,\n+\t\t.counter_id = 2002,\n+\t\t.vctrl = \u0026test_vctrl,\n+\t};\n+\n \tvcap_test_api_init(\u0026admin);\n+\tlist_add_tail(\u0026ri0.list, \u0026admin.rules);\n \n \t/* Create rules with different sizes and check that they are placed\n \t * at the correct address in the VCAP according to size\n \t */\n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 200, 2, 798);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 200, 2, 802);\n \tKUNIT_EXPECT_EQ(test, 0, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 0, test_move_count);\n \tKUNIT_EXPECT_EQ(test, 0, test_move_addr);\n \n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 795);\n-\tKUNIT_EXPECT_EQ(test, 6, test_move_offset);\n-\tKUNIT_EXPECT_EQ(test, 3, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 798, test_move_addr);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 801);\n+\tKUNIT_EXPECT_EQ(test, 4, test_move_offset);\n+\tKUNIT_EXPECT_EQ(test, 2, test_move_count);\n+\tKUNIT_EXPECT_EQ(test, 802, test_move_addr);\n \n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 400, 6, 792);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 400, 6, 798);\n \tKUNIT_EXPECT_EQ(test, 6, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 6, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 792, test_move_addr);\n+\tKUNIT_EXPECT_EQ(test, 798, test_move_addr);\n \n-\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 50, 500, 12, 780);\n-\tKUNIT_EXPECT_EQ(test, 18, test_move_offset);\n+\ttest_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 50, 500, 12, 792);\n+\tKUNIT_EXPECT_EQ(test, 12, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 12, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 786, test_move_addr);\n+\tKUNIT_EXPECT_EQ(test, 792, test_move_addr);\n \n \tidx = 0;\n \tlist_for_each_entry(elem, \u0026admin.rules, list) {\n \t\tKUNIT_EXPECT_EQ(test, exp_addr[idx], elem-\u003eaddr);\n \t\t++idx;\n \t}\n-\tKUNIT_EXPECT_EQ(test, 768, admin.last_used_addr);\n+\tKUNIT_EXPECT_EQ(test, 780, admin.last_used_addr);\n \n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 500);\n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 400);\n@@ -1775,7 +1802,7 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)\n \tKUNIT_EXPECT_EQ(test, 768, test_move_addr);\n \tKUNIT_EXPECT_EQ(test, -6, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 6, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 768, test_init_start);\n+\tKUNIT_EXPECT_EQ(test, 774, test_init_start);\n \tKUNIT_EXPECT_EQ(test, 6, test_init_count);\n \tKUNIT_EXPECT_EQ(test, 774, admin.last_used_addr);\n \n@@ -1785,8 +1812,8 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)\n \tKUNIT_EXPECT_EQ(test, 774, test_move_addr);\n \tKUNIT_EXPECT_EQ(test, -4, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 2, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 774, test_init_start);\n-\tKUNIT_EXPECT_EQ(test, 4, test_init_count);\n+\tKUNIT_EXPECT_EQ(test, 777, test_init_start);\n+\tKUNIT_EXPECT_EQ(test, 3, test_init_count);\n \tKUNIT_EXPECT_EQ(test, 778, admin.last_used_addr);\n \n \ttest_init_rule_deletion();\n@@ -1795,8 +1822,8 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)\n \tKUNIT_EXPECT_EQ(test, 778, test_move_addr);\n \tKUNIT_EXPECT_EQ(test, -20, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 2, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 778, test_init_start);\n-\tKUNIT_EXPECT_EQ(test, 20, test_init_count);\n+\tKUNIT_EXPECT_EQ(test, 780, test_init_start);\n+\tKUNIT_EXPECT_EQ(test, 12, test_init_count);\n \tKUNIT_EXPECT_EQ(test, 798, admin.last_used_addr);\n \n \ttest_init_rule_deletion();\n@@ -1856,11 +1883,11 @@ static void vcap_api_rule_remove_in_front_test(struct kunit *test)\n \tret = vcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 400);\n \tKUNIT_EXPECT_EQ(test, 0, ret);\n \tKUNIT_EXPECT_EQ(test, 786, test_move_addr);\n-\tKUNIT_EXPECT_EQ(test, -8, test_move_offset);\n+\tKUNIT_EXPECT_EQ(test, -6, test_move_offset);\n \tKUNIT_EXPECT_EQ(test, 6, test_move_count);\n-\tKUNIT_EXPECT_EQ(test, 786, test_init_start);\n-\tKUNIT_EXPECT_EQ(test, 8, test_init_count);\n-\tKUNIT_EXPECT_EQ(test, 794, admin.last_used_addr);\n+\tKUNIT_EXPECT_EQ(test, 792, test_init_start);\n+\tKUNIT_EXPECT_EQ(test, 6, test_init_count);\n+\tKUNIT_EXPECT_EQ(test, 792, admin.last_used_addr);\n \n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 200);\n \tvcap_del_rule(\u0026test_vctrl, \u0026test_netdev, 300);\ndiff --git a/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c b/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c\nindex 5dbfc0d0c369b..75819b49e1106 100644\n--- a/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c\n+++ b/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c\n@@ -1,10 +1,10 @@\n // SPDX-License-Identifier: BSD-3-Clause\n-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.\n+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.\n * Microchip VCAP test model interface for kunit testing\n */\n \n-/* This file is autogenerated by cml-utils 2023-02-10 11:16:00 +0100.\n- * Commit ID: c30fb4bf0281cd4a7133bdab6682f9e43c872ada\n+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.\n+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a\n */\n \n #include \u003clinux/types.h\u003e\n"
} |
| 2/1 | 2026/08/10 11:33 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds L3 routing offload support for the Microchip Sparx5 switch driver. This is a vendor-specific hardware switch driver that requires physical hardware (Sparx5 SoC) and is not emulated in standard QEMU. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit cfb847c5db0faa51a40d3da33d5ce37279302d47
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 10 11:33:04 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile
index d447f9e84d92a..d917734d9f12e 100644
--- a/drivers/net/ethernet/microchip/sparx5/Makefile
+++ b/drivers/net/ethernet/microchip/sparx5/Makefile
@@ -11,7 +11,7 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \
sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \
sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \
sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \
- sparx5_psfp.o sparx5_mirror.o sparx5_regs.o
+ sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_router.o
sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
index f3a9c71bea36a..f8d1741c264f0 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
@@ -322,6 +322,8 @@ static const struct sparx5_consts lan969x_consts = {
.qres_max_prio_idx = 315,
.qres_max_colour_idx = 323,
.tod_pin = 4,
+ .vmid_cnt = 127,
+ .arp_tbl_cnt = 1024,
.vcaps = lan969x_vcaps,
.vcap_stats = &lan969x_vcap_stats,
.vcaps_cfg = lan969x_vcap_inst_cfg,
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
index 3fc2c006ba129..00fd03f87bdd8 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#include "lan969x.h"
@@ -77,12 +77,16 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_ANA_CL_COMMON] = 87040,
[GA_ANA_L2_COMMON] = 561928,
[GA_ANA_L3_COMMON] = 370752,
+ [GA_ANA_L3_VMID] = 360448,
+ [GA_ANA_L3_ARP_PTR_REMAP] = 370944,
+ [GA_ANA_L3_ARP] = 294912,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 368580,
[GA_ASM_CFG] = 18304,
[GA_ASM_PFC_TIMER_CFG] = 15568,
[GA_ASM_LBK_WM_CFG] = 15596,
[GA_ASM_LBK_MISC_CFG] = 15608,
[GA_ASM_RAM_CTRL] = 15684,
+ [GA_EACL_COMMON] = 37408,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 36864,
[GA_EACL_CNT_TBL] = 30720,
[GA_EACL_POL_CFG] = 38400,
@@ -103,6 +107,7 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_QSYS_RAM_CTRL] = 2204,
[GA_REW_COMMON] = 98304,
[GA_REW_PORT] = 49152,
+ [GA_REW_VMID] = 96768,
[GA_REW_VOE_PORT_LM_CNT] = 90112,
[GA_REW_RAM_CTRL] = 93992,
[GA_VOP_RAM_CTRL] = 16368,
@@ -124,6 +129,8 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {
[GC_ANA_L2_ISDX_LIMIT] = 256,
[GC_ANA_L2_ISDX] = 1024,
[GC_ANA_L3_VLAN] = 4608,
+ [GC_ANA_L3_VMID] = 127,
+ [GC_ANA_L3_ARP] = 1024,
[GC_ASM_DEV_STATISTICS] = 30,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 68,
[GC_EACL_CNT_TBL] = 512,
@@ -134,6 +141,7 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = {
[GC_PTP_PTP_PINS] = 8,
[GC_PTP_PHASE_DETECTOR_CTRL] = 8,
[GC_REW_PORT] = 35,
+ [GC_REW_VMID] = 127,
[GC_REW_VOE_PORT_LM_CNT] = 240,
};
@@ -191,7 +199,12 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 30,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 9,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 8,
+ [FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 30,
+ [FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 3,
+ [FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 30,
+ [FW_ANA_L3_VMID_CFG_VMID] = 7,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 30,
+ [FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 7,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 2,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 7,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 5,
@@ -217,6 +230,7 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_QSYS_ATOP_ATOP] = 11,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 11,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 6,
+ [FW_REW_RLEG_CTRL_DECAP_IRLEG] = 7,
[FW_XQS_STAT_CFG_STAT_VIEW] = 10,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 14,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 14,
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c
index 7acc5bcf337a2..e623b1dbe9cb9 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_ag_api.c
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: BSD-3-Clause
-/* Copyright (C) 2024 Microchip Technology Inc. and its subsidiaries.
+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP API
*/
-/* This file is autogenerated by cml-utils 2024-10-07 11:10:56 +0200.
- * Commit ID: b5ddc8e244eb2481a9524f1ddc630a8b41e7c391
+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.
+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a
*/
#include <linux/types.h>
@@ -1380,6 +1380,83 @@ static const struct vcap_field es0_isdx_keyfield[] = {
},
};
+static const struct vcap_field lpm_sgl_ip4_keyfield[] = {
+ [VCAP_KF_DST_FLAG] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP4_XIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 11,
+ .width = 32,
+ },
+};
+
+static const struct vcap_field lpm_dbl_ip4_keyfield[] = {
+ [VCAP_KF_TYPE] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP4_SIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 11,
+ .width = 32,
+ },
+ [VCAP_KF_IP4_DIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 43,
+ .width = 32,
+ },
+};
+
+static const struct vcap_field lpm_sgl_ip6_keyfield[] = {
+ [VCAP_KF_DST_FLAG] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP6_XIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 11,
+ .width = 128,
+ },
+};
+
+static const struct vcap_field lpm_dbl_ip6_keyfield[] = {
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 10,
+ },
+ [VCAP_KF_IP6_SIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 10,
+ .width = 128,
+ },
+ [VCAP_KF_IP6_DIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 138,
+ .width = 128,
+ },
+};
+
static const struct vcap_field es2_mac_etype_keyfield[] = {
[VCAP_KF_TYPE] = {
.type = VCAP_FIELD_U32,
@@ -2338,6 +2415,29 @@ static const struct vcap_set es0_keyfield_set[] = {
},
};
+static const struct vcap_set lpm_keyfield_set[] = {
+ [VCAP_KFS_SGL_IP4] = {
+ .type_id = -1,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+ [VCAP_KFS_DBL_IP4] = {
+ .type_id = 0,
+ .sw_per_item = 2,
+ .sw_cnt = 6,
+ },
+ [VCAP_KFS_SGL_IP6] = {
+ .type_id = -1,
+ .sw_per_item = 3,
+ .sw_cnt = 4,
+ },
+ [VCAP_KFS_DBL_IP6] = {
+ .type_id = -1,
+ .sw_per_item = 6,
+ .sw_cnt = 2,
+ },
+};
+
static const struct vcap_set es2_keyfield_set[] = {
[VCAP_KFS_MAC_ETYPE] = {
.type_id = 0,
@@ -2390,6 +2490,13 @@ static const struct vcap_field *es0_keyfield_set_map[] = {
[VCAP_KFS_ISDX] = es0_isdx_keyfield,
};
+static const struct vcap_field *lpm_keyfield_set_map[] = {
+ [VCAP_KFS_SGL_IP4] = lpm_sgl_ip4_keyfield,
+ [VCAP_KFS_DBL_IP4] = lpm_dbl_ip4_keyfield,
+ [VCAP_KFS_SGL_IP6] = lpm_sgl_ip6_keyfield,
+ [VCAP_KFS_DBL_IP6] = lpm_dbl_ip6_keyfield,
+};
+
static const struct vcap_field *es2_keyfield_set_map[] = {
[VCAP_KFS_MAC_ETYPE] = es2_mac_etype_keyfield,
[VCAP_KFS_ARP] = es2_arp_keyfield,
@@ -2418,6 +2525,13 @@ static int es0_keyfield_set_map_size[] = {
[VCAP_KFS_ISDX] = ARRAY_SIZE(es0_isdx_keyfield),
};
+static int lpm_keyfield_set_map_size[] = {
+ [VCAP_KFS_SGL_IP4] = ARRAY_SIZE(lpm_sgl_ip4_keyfield),
+ [VCAP_KFS_DBL_IP4] = ARRAY_SIZE(lpm_dbl_ip4_keyfield),
+ [VCAP_KFS_SGL_IP6] = ARRAY_SIZE(lpm_sgl_ip6_keyfield),
+ [VCAP_KFS_DBL_IP6] = ARRAY_SIZE(lpm_dbl_ip6_keyfield),
+};
+
static int es2_keyfield_set_map_size[] = {
[VCAP_KFS_MAC_ETYPE] = ARRAY_SIZE(es2_mac_etype_keyfield),
[VCAP_KFS_ARP] = ARRAY_SIZE(es2_arp_keyfield),
@@ -2993,6 +3107,92 @@ static const struct vcap_field es0_es0_actionfield[] = {
},
};
+static const struct vcap_field lpm_arp_ptr_actionfield[] = {
+ [VCAP_AF_TYPE] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 2,
+ },
+ [VCAP_AF_ARP_PTR] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 2,
+ .width = 10,
+ },
+ [VCAP_AF_ARP_PTR_REMAP_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 12,
+ .width = 1,
+ },
+ [VCAP_AF_ECMP_CNT] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 13,
+ .width = 4,
+ },
+ [VCAP_AF_RGID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 17,
+ .width = 3,
+ },
+};
+
+static const struct vcap_field lpm_arp_entry_actionfield[] = {
+ [VCAP_AF_TYPE] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 2,
+ },
+ [VCAP_AF_MAC_MSB] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 2,
+ .width = 16,
+ },
+ [VCAP_AF_MAC_LSB] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 18,
+ .width = 32,
+ },
+ [VCAP_AF_ARP_VMID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 50,
+ .width = 7,
+ },
+ [VCAP_AF_ZERO_DMAC_CPU_QU] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 57,
+ .width = 3,
+ },
+ [VCAP_AF_SIP_RPF_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 60,
+ .width = 1,
+ },
+ [VCAP_AF_SECUR_MATCH_VMID_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 61,
+ .width = 1,
+ },
+ [VCAP_AF_SECUR_MATCH_MAC_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 62,
+ .width = 1,
+ },
+ [VCAP_AF_ARP_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 63,
+ .width = 1,
+ },
+ [VCAP_AF_ENCAP_ID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 64,
+ .width = 8,
+ },
+ [VCAP_AF_RSDX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 72,
+ .width = 10,
+ },
+};
+
static const struct vcap_field es2_base_type_actionfield[] = {
[VCAP_AF_HIT_ME_ONCE] = {
.type = VCAP_FIELD_BIT,
@@ -3101,6 +3301,19 @@ static const struct vcap_set es0_actionfield_set[] = {
},
};
+static const struct vcap_set lpm_actionfield_set[] = {
+ [VCAP_AFS_ARP_PTR] = {
+ .type_id = 0,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+ [VCAP_AFS_ARP_ENTRY] = {
+ .type_id = 2,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+};
+
static const struct vcap_set es2_actionfield_set[] = {
[VCAP_AFS_BASE_TYPE] = {
.type_id = -1,
@@ -3124,6 +3337,11 @@ static const struct vcap_field *es0_actionfield_set_map[] = {
[VCAP_AFS_ES0] = es0_es0_actionfield,
};
+static const struct vcap_field *lpm_actionfield_set_map[] = {
+ [VCAP_AFS_ARP_PTR] = lpm_arp_ptr_actionfield,
+ [VCAP_AFS_ARP_ENTRY] = lpm_arp_entry_actionfield,
+};
+
static const struct vcap_field *es2_actionfield_set_map[] = {
[VCAP_AFS_BASE_TYPE] = es2_base_type_actionfield,
};
@@ -3143,6 +3361,11 @@ static int es0_actionfield_set_map_size[] = {
[VCAP_AFS_ES0] = ARRAY_SIZE(es0_es0_actionfield),
};
+static int lpm_actionfield_set_map_size[] = {
+ [VCAP_AFS_ARP_PTR] = ARRAY_SIZE(lpm_arp_ptr_actionfield),
+ [VCAP_AFS_ARP_ENTRY] = ARRAY_SIZE(lpm_arp_entry_actionfield),
+};
+
static int es2_actionfield_set_map_size[] = {
[VCAP_AFS_BASE_TYPE] = ARRAY_SIZE(es2_base_type_actionfield),
};
@@ -3308,6 +3531,82 @@ static const struct vcap_typegroup es0_x1_keyfield_set_typegroups[] = {
{}
};
+static const struct vcap_typegroup lpm_x6_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 4,
+ .value = 8,
+ },
+ {
+ .offset = 52,
+ .width = 1,
+ .value = 0,
+ },
+ {
+ .offset = 104,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 156,
+ .width = 3,
+ .value = 0,
+ },
+ {
+ .offset = 208,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 260,
+ .width = 1,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x3_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 3,
+ .value = 4,
+ },
+ {
+ .offset = 52,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 104,
+ .width = 2,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x2_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 2,
+ .value = 2,
+ },
+ {
+ .offset = 52,
+ .width = 1,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x1_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 1,
+ .value = 1,
+ },
+ {}
+};
+
static const struct vcap_typegroup es2_x12_keyfield_set_typegroups[] = {
{
.offset = 0,
@@ -3376,6 +3675,14 @@ static const struct vcap_typegroup *es0_keyfield_set_typegroups[] = {
[2] = NULL,
};
+static const struct vcap_typegroup *lpm_keyfield_set_typegroups[] = {
+ [6] = lpm_x6_keyfield_set_typegroups,
+ [3] = lpm_x3_keyfield_set_typegroups,
+ [2] = lpm_x2_keyfield_set_typegroups,
+ [1] = lpm_x1_keyfield_set_typegroups,
+ [13] = NULL,
+};
+
static const struct vcap_typegroup *es2_keyfield_set_typegroups[] = {
[12] = es2_x12_keyfield_set_typegroups,
[6] = es2_x6_keyfield_set_typegroups,
@@ -3453,6 +3760,10 @@ static const struct vcap_typegroup es0_x1_actionfield_set_typegroups[] = {
{}
};
+static const struct vcap_typegroup lpm_x1_actionfield_set_typegroups[] = {
+ {}
+};
+
static const struct vcap_typegroup es2_x3_actionfield_set_typegroups[] = {
{
.offset = 0,
@@ -3494,6 +3805,11 @@ static const struct vcap_typegroup *es0_actionfield_set_typegroups[] = {
[2] = NULL,
};
+static const struct vcap_typegroup *lpm_actionfield_set_typegroups[] = {
+ [1] = lpm_x1_actionfield_set_typegroups,
+ [13] = NULL,
+};
+
static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {
[3] = es2_x3_actionfield_set_typegroups,
[1] = es2_x1_actionfield_set_typegroups,
@@ -3504,6 +3820,8 @@ static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {
static const char * const vcap_keyfield_set_names[] = {
[VCAP_KFS_NO_VALUE] = "(None)",
[VCAP_KFS_ARP] = "VCAP_KFS_ARP",
+ [VCAP_KFS_DBL_IP4] = "VCAP_KFS_DBL_IP4",
+ [VCAP_KFS_DBL_IP6] = "VCAP_KFS_DBL_IP6",
[VCAP_KFS_ETAG] = "VCAP_KFS_ETAG",
[VCAP_KFS_IP4_OTHER] = "VCAP_KFS_IP4_OTHER",
[VCAP_KFS_IP4_TCP_UDP] = "VCAP_KFS_IP4_TCP_UDP",
@@ -3522,6 +3840,8 @@ static const char * const vcap_keyfield_set_names[] = {
[VCAP_KFS_NORMAL_7TUPLE] = "VCAP_KFS_NORMAL_7TUPLE",
[VCAP_KFS_OAM] = "VCAP_KFS_OAM",
[VCAP_KFS_PURE_5TUPLE_IP4] = "VCAP_KFS_PURE_5TUPLE_IP4",
+ [VCAP_KFS_SGL_IP4] = "VCAP_KFS_SGL_IP4",
+ [VCAP_KFS_SGL_IP6] = "VCAP_KFS_SGL_IP6",
[VCAP_KFS_SMAC_SIP4] = "VCAP_KFS_SMAC_SIP4",
[VCAP_KFS_SMAC_SIP6] = "VCAP_KFS_SMAC_SIP6",
};
@@ -3529,6 +3849,8 @@ static const char * const vcap_keyfield_set_names[] = {
/* Actionfieldset names */
static const char * const vcap_actionfield_set_names[] = {
[VCAP_AFS_NO_VALUE] = "(None)",
+ [VCAP_AFS_ARP_ENTRY] = "VCAP_AFS_ARP_ENTRY",
+ [VCAP_AFS_ARP_PTR] = "VCAP_AFS_ARP_PTR",
[VCAP_AFS_BASE_TYPE] = "VCAP_AFS_BASE_TYPE",
[VCAP_AFS_CLASSIFICATION] = "VCAP_AFS_CLASSIFICATION",
[VCAP_AFS_CLASS_REDUCED] = "VCAP_AFS_CLASS_REDUCED",
@@ -3565,6 +3887,7 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_8021Q_VLAN_TAGGED_IS] = "8021Q_VLAN_TAGGED_IS",
[VCAP_KF_8021Q_VLAN_TAGS] = "8021Q_VLAN_TAGS",
[VCAP_KF_ACL_GRP_ID] = "ACL_GRP_ID",
+ [VCAP_KF_AFFIX] = "AFFIX",
[VCAP_KF_ARP_ADDR_SPACE_OK_IS] = "ARP_ADDR_SPACE_OK_IS",
[VCAP_KF_ARP_LEN_OK_IS] = "ARP_LEN_OK_IS",
[VCAP_KF_ARP_OPCODE] = "ARP_OPCODE",
@@ -3573,6 +3896,7 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_ARP_SENDER_MATCH_IS] = "ARP_SENDER_MATCH_IS",
[VCAP_KF_ARP_TGT_MATCH_IS] = "ARP_TGT_MATCH_IS",
[VCAP_KF_COSID_CLS] = "COSID_CLS",
+ [VCAP_KF_DST_FLAG] = "DST_FLAG",
[VCAP_KF_ES0_ISDX_KEY_ENA] = "ES0_ISDX_KEY_ENA",
[VCAP_KF_ETYPE] = "ETYPE",
[VCAP_KF_ETYPE_LEN_IS] = "ETYPE_LEN_IS",
@@ -3586,7 +3910,13 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_IF_IGR_PORT_MASK_RNG] = "IF_IGR_PORT_MASK_RNG",
[VCAP_KF_IF_IGR_PORT_MASK_SEL] = "IF_IGR_PORT_MASK_SEL",
[VCAP_KF_IF_IGR_PORT_SEL] = "IF_IGR_PORT_SEL",
+ [VCAP_KF_IP4_DIP] = "IP4_DIP",
[VCAP_KF_IP4_IS] = "IP4_IS",
+ [VCAP_KF_IP4_SIP] = "IP4_SIP",
+ [VCAP_KF_IP4_XIP] = "IP4_XIP",
+ [VCAP_KF_IP6_DIP] = "IP6_DIP",
+ [VCAP_KF_IP6_SIP] = "IP6_SIP",
+ [VCAP_KF_IP6_XIP] = "IP6_XIP",
[VCAP_KF_IP_MC_IS] = "IP_MC_IS",
[VCAP_KF_IP_PAYLOAD_5TUPLE] = "IP_PAYLOAD_5TUPLE",
[VCAP_KF_IP_SNAP_IS] = "IP_SNAP_IS",
@@ -3659,6 +3989,10 @@ static const char * const vcap_keyfield_names[] = {
static const char * const vcap_actionfield_names[] = {
[VCAP_AF_NO_VALUE] = "(None)",
[VCAP_AF_ACL_ID] = "ACL_ID",
+ [VCAP_AF_ARP_ENA] = "ARP_ENA",
+ [VCAP_AF_ARP_PTR] = "ARP_PTR",
+ [VCAP_AF_ARP_PTR_REMAP_ENA] = "ARP_PTR_REMAP_ENA",
+ [VCAP_AF_ARP_VMID] = "ARP_VMID",
[VCAP_AF_CLS_VID_SEL] = "CLS_VID_SEL",
[VCAP_AF_CNT_ID] = "CNT_ID",
[VCAP_AF_COPY_PORT_NUM] = "COPY_PORT_NUM",
@@ -3676,6 +4010,8 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_DSCP_ENA] = "DSCP_ENA",
[VCAP_AF_DSCP_SEL] = "DSCP_SEL",
[VCAP_AF_DSCP_VAL] = "DSCP_VAL",
+ [VCAP_AF_ECMP_CNT] = "ECMP_CNT",
+ [VCAP_AF_ENCAP_ID] = "ENCAP_ID",
[VCAP_AF_ES2_REW_CMD] = "ES2_REW_CMD",
[VCAP_AF_ESDX] = "ESDX",
[VCAP_AF_FWD_KILL_ENA] = "FWD_KILL_ENA",
@@ -3690,6 +4026,8 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_ISDX_VAL] = "ISDX_VAL",
[VCAP_AF_LOOP_ENA] = "LOOP_ENA",
[VCAP_AF_LRN_DIS] = "LRN_DIS",
+ [VCAP_AF_MAC_LSB] = "MAC_LSB",
+ [VCAP_AF_MAC_MSB] = "MAC_MSB",
[VCAP_AF_MAP_IDX] = "MAP_IDX",
[VCAP_AF_MAP_KEY] = "MAP_KEY",
[VCAP_AF_MAP_LOOKUP_SEL] = "MAP_LOOKUP_SEL",
@@ -3723,7 +4061,12 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_QOS_ENA] = "QOS_ENA",
[VCAP_AF_QOS_VAL] = "QOS_VAL",
[VCAP_AF_REW_OP] = "REW_OP",
+ [VCAP_AF_RGID] = "RGID",
+ [VCAP_AF_RSDX] = "RSDX",
[VCAP_AF_RT_DIS] = "RT_DIS",
+ [VCAP_AF_SECUR_MATCH_MAC_ENA] = "SECUR_MATCH_MAC_ENA",
+ [VCAP_AF_SECUR_MATCH_VMID_ENA] = "SECUR_MATCH_VMID_ENA",
+ [VCAP_AF_SIP_RPF_ENA] = "SIP_RPF_ENA",
[VCAP_AF_SWAP_MACS_ENA] = "SWAP_MACS_ENA",
[VCAP_AF_TAG_A_DEI_SEL] = "TAG_A_DEI_SEL",
[VCAP_AF_TAG_A_PCP_SEL] = "TAG_A_PCP_SEL",
@@ -3743,6 +4086,7 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_VID_B_VAL] = "VID_B_VAL",
[VCAP_AF_VID_C_VAL] = "VID_C_VAL",
[VCAP_AF_VID_VAL] = "VID_VAL",
+ [VCAP_AF_ZERO_DMAC_CPU_QU] = "ZERO_DMAC_CPU_QU",
};
/* VCAPs */
@@ -3810,6 +4154,27 @@ const struct vcap_info lan969x_vcaps[] = {
.keyfield_set_typegroups = es0_keyfield_set_typegroups,
.actionfield_set_typegroups = es0_actionfield_set_typegroups,
},
+ [VCAP_TYPE_LPM] = {
+ .name = "lpm",
+ .rows = 512,
+ .sw_count = 12,
+ .sw_width = 52,
+ .sticky_width = 1,
+ .act_width = 83,
+ .default_cnt = 0,
+ .require_cnt_dis = 0,
+ .version = 1,
+ .keyfield_set = lpm_keyfield_set,
+ .keyfield_set_size = ARRAY_SIZE(lpm_keyfield_set),
+ .actionfield_set = lpm_actionfield_set,
+ .actionfield_set_size = ARRAY_SIZE(lpm_actionfield_set),
+ .keyfield_set_map = lpm_keyfield_set_map,
+ .keyfield_set_map_size = lpm_keyfield_set_map_size,
+ .actionfield_set_map = lpm_actionfield_set_map,
+ .actionfield_set_map_size = lpm_actionfield_set_map_size,
+ .keyfield_set_typegroups = lpm_keyfield_set_typegroups,
+ .actionfield_set_typegroups = lpm_actionfield_set_typegroups,
+ },
[VCAP_TYPE_ES2] = {
.name = "es2",
.rows = 256,
@@ -3835,7 +4200,7 @@ const struct vcap_info lan969x_vcaps[] = {
const struct vcap_statistics lan969x_vcap_stats = {
.name = "lan969x",
- .count = 4,
+ .count = 5,
.keyfield_set_names = vcap_keyfield_set_names,
.actionfield_set_names = vcap_actionfield_set_names,
.keyfield_names = vcap_keyfield_names,
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
index 543a1f2bf6bd9..6e6a1f833deac 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c
@@ -82,4 +82,16 @@ const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[] = {
.count = 1024,
.ingress = false,
},
+ {
+ .vtype = VCAP_TYPE_LPM,
+ .vinst = 0,
+ .map_id = 6,
+ .lookups = SPARX5_LPM_LOOKUPS,
+ .lookups_per_instance = SPARX5_LPM_LOOKUPS,
+ .first_cid = SPARX5_VCAP_CID_LPM_L0,
+ .last_cid = SPARX5_VCAP_CID_LPM_MAX,
+ .blockno = 5,
+ .blocks = 1,
+ .ingress = true,
+ },
};
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
index dad713e9ddd5b..24acdbc20dc8e 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
@@ -994,14 +994,22 @@ static int mchp_sparx5_probe(struct platform_device *pdev)
goto cleanup_ptp;
}
+ err = sparx5_rr_router_init(sparx5);
+ if (err) {
+ dev_err(sparx5->dev, "Router initialization failed\n");
+ goto cleanup_netdevs;
+ }
+
err = sparx5_register_notifier_blocks(sparx5);
if (err) {
dev_err(sparx5->dev, "Failed to register notifier blocks\n");
- goto cleanup_netdevs;
+ goto cleanup_router;
}
goto cleanup_config;
+cleanup_router:
+ sparx5_rr_router_deinit(sparx5);
cleanup_netdevs:
sparx5_unregister_netdevs(sparx5);
cleanup_ptp:
@@ -1031,6 +1039,7 @@ static void mchp_sparx5_remove(struct platform_device *pdev)
sparx5_unregister_notifier_blocks(sparx5);
sparx5_unregister_netdevs(sparx5);
sparx5_ptp_deinit(sparx5);
+ sparx5_rr_router_deinit(sparx5);
sparx5_frame_io_deinit(sparx5);
sparx5_stats_deinit(sparx5);
sparx5_mact_deinit(sparx5);
@@ -1067,6 +1076,8 @@ static const struct sparx5_consts sparx5_consts = {
.qres_max_prio_idx = 630,
.qres_max_colour_idx = 638,
.tod_pin = 4,
+ .vmid_cnt = 511,
+ .arp_tbl_cnt = 2048,
.vcaps = sparx5_vcaps,
.vcaps_cfg = sparx5_vcap_inst_cfg,
.vcap_stats = &sparx5_vcap_stats,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index eb57b86fbe22f..2394ba5107e71 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -136,6 +136,10 @@ enum sparx5_feature {
#define SPARX5_MAX_PTP_ID 512
+/* Must be maximum values across all L3 enabled platforms */
+#define SPARX5_ROUTER_LEG_N_VMID 511
+#define SPARX5_ARP_TBL_SIZE 2048
+
struct sparx5;
struct sparx5_calendar_data {
@@ -320,6 +324,8 @@ struct sparx5_consts {
u32 qres_max_prio_idx; /* Maximum QRES prio index */
u32 qres_max_colour_idx; /* Maximum QRES colour index */
u32 tod_pin; /* PTP TOD pin */
+ u32 vmid_cnt; /* Number of router leg VMID's */
+ u32 arp_tbl_cnt; /* Number of ARP table entries */
const struct sparx5_vcap_inst *vcaps_cfg;
const struct vcap_info *vcaps;
const struct vcap_statistics *vcap_stats;
@@ -434,6 +440,8 @@ struct sparx5 {
/* Common root for debugfs */
struct dentry *debugfs_root;
const struct sparx5_match_data *data;
+ /* L3 Forwarding */
+ struct sparx5_router *router;
};
/* sparx5_main.c */
@@ -503,6 +511,42 @@ int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid);
void sparx5_vlan_port_apply(struct sparx5 *sparx5, struct sparx5_port *port);
+/* sparx5_router.c */
+int sparx5_rr_router_init(struct sparx5 *sparx5);
+void sparx5_rr_router_deinit(struct sparx5 *sparx5);
+
+struct sparx5_rr_hw_route {
+ u32 vrule_id;
+ bool vrule_id_valid;
+};
+
+struct sparx5_router {
+ struct sparx5 *sparx5;
+ struct notifier_block fib_nb;
+ struct notifier_block netevent_nb;
+ struct notifier_block inetaddr_nb;
+ struct notifier_block inetaddr_valid_nb;
+ struct notifier_block netdevice_nb;
+ struct notifier_block inet6addr_nb;
+ struct notifier_block inet6addr_valid_nb;
+ struct rhashtable neigh_ht;
+ struct rhashtable fib_ht;
+ struct sparx5_rr_hw_route link_local; /* Trap all link-local traffic. */
+ struct net_device *port_dev; /* For VCAP API. */
+
+ struct list_head fib_list; /* All fib entries, for teardown */
+ struct mutex lock; /* Global router lock for all shared data. */
+
+ struct workqueue_struct *sparx5_router_owq;
+
+ atomic_t legs_count;
+ struct list_head leg_list;
+ /* Track allocated router leg indices in hw */
+ DECLARE_BITMAP(vmid_mask, SPARX5_ROUTER_LEG_N_VMID);
+ /* Track allocated arp table indices in hw */
+ DECLARE_BITMAP(arp_tbl_mask, SPARX5_ARP_TBL_SIZE);
+};
+
/* sparx5_calendar.c */
int sparx5_calendar_init(struct sparx5 *sparx5);
int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
index d344675136484..6cf8c191a460d 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0+
* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-10-04 10:40:40 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#ifndef _SPARX5_MAIN_REGS_H_
@@ -711,6 +711,124 @@ extern const struct sparx5_regs *regs;
#define ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA_GET(x)\
FIELD_GET(ANA_ACL_SWAP_IP_CTRL_IP_SWAP_IP4_TTL_ENA, x)
+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL */
+#define ANA_ACL_VCAP_S2_MISC_CTRL \
+ __REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
+ 416, 0, 1, 4)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA GENMASK(27, 24)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MAPPED_PORT_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA GENMASK(23, 20)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_AFFIX_OVERLOAD_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA GENMASK(19, 16)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ISDX_OVERLOAD_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA GENMASK(15, 12)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_PAG_FORCE_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA BIT(11)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VLAN_PIPELINE_ACT_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE BIT(10)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_IGR_RLEG_STAT_MODE, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE BIT(9)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_EGR_RLEG_STAT_MODE, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA BIT(8)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_FORCE_ES0_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA BIT(7)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_UPDATE_CL_VID_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL GENMASK(6, 5)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA BIT(4)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_LBK_IGR_MASK_SEL3_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA BIT(3)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_MASQ_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA BIT(2)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_FP_VS2_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA BIT(1)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_VD_IGR_MASK_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA BIT(0)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL_CPU_IGR_MASK_ENA, x)
+
+/* ANA_ACL:COMMON:VCAP_S2_MISC_CTRL2 */
+#define ANA_ACL_VCAP_S2_MISC_CTRL2 \
+ __REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
+ 420, 0, 1, 4)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT GENMASK(12, 8)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_TWAMP_PIPELINE_PT, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA GENMASK(7, 4)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_ACTION_ENA, x)
+
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA GENMASK(3, 0)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_SET(x)\
+ FIELD_PREP(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)
+#define ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA_GET(x)\
+ FIELD_GET(ANA_ACL_VCAP_S2_MISC_CTRL2_EGR_KEY_ENA, x)
+
/* ANA_ACL:COMMON:VCAP_S2_RLEG_STAT */
#define ANA_ACL_VCAP_S2_RLEG_STAT(r) \
__REG(TARGET_ANA_ACL, 0, 1, regs->gaddr[GA_ANA_ACL_COMMON], 0, 1, 592, \
@@ -1812,6 +1930,338 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_CTRL_VLAN_ENA_GET(x)\
FIELD_GET(ANA_L3_VLAN_CTRL_VLAN_ENA, x)
+/* ANA_L3:COMMON:L3_UC_ENA */
+#define ANA_L3_L3_UC_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, 8,\
+ 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:L3_UC_ENA1 */
+#define ANA_L3_L3_UC_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 12, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:L3_UC_ENA2 */
+#define ANA_L3_L3_UC_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 16, 0, 1, 4)
+
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2 BIT(0)
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)
+#define ANA_L3_L3_UC_ENA2_L3_UC_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_L3_UC_ENA2_L3_UC_ENA2, x)
+
+/* ANA_L3:COMMON:ROUTING_CFG */
+#define ANA_L3_ROUTING_CFG \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 104, 0, 1, 4)
+
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE BIT(30)
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)
+#define ANA_L3_ROUTING_CFG_L3_ENA_MODE_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_L3_ENA_MODE, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA BIT(29)
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_MC_DIP_FWD_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA BIT(28)
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_MC_DIP_FWD_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA BIT(27)
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)
+#define ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE GENMASK(26, 25)
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)
+#define ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_NONIP_UC_REDIR_MODE, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR BIT(22)
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)
+#define ANA_L3_ROUTING_CFG_IP6_LEN_REDIR_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_LEN_REDIR, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR BIT(21)
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)
+#define ANA_L3_ROUTING_CFG_IP4_LEN_REDIR_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_LEN_REDIR, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA BIT(20)
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_L2_BC_COPY_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA BIT(19)
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_L2_BC_COPY_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA BIT(18)
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP6_SIP_RPF_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA BIT(17)
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_RLEG_IP4_SIP_RPF_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(16, 15)
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA GENMASK(13, 11)
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_DIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(10, 8)
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA GENMASK(7, 5)
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_SIP_ADDR_VIOLATION_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA BIT(4)
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA BIT(3)
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA BIT(2)
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA BIT(1)
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA, x)
+
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA BIT(0)
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA, x)
+
+/* ANA_L3:COMMON:ROUTING_CFG2 */
+#define ANA_L3_ROUTING_CFG2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 108, 0, 1, 4)
+
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA BIT(4)
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP6_SIP_LOOKUP_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA BIT(3)
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP4_SIP_LOOKUP_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA BIT(1)
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)
+#define ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_SIP_IP6PFX_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA BIT(0)
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)
+#define ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_DIP_IP6PFX_ENA, x)
+
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA BIT(2)
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)
+#define ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ROUTING_CFG2_IP4_DECAP_REDIR_ENA, x)
+
+/* ANA_L3:COMMON:RLEG_CFG_0 */
+#define ANA_L3_RLEG_CFG_0 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 112, 0, 1, 4)
+
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(31, 8)
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* ANA_L3:COMMON:RLEG_CFG_1 */
+#define ANA_L3_RLEG_CFG_1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 116, 0, 1, 4)
+
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
+/* ANA_L3:COMMON:CPU_QU_CFG */
+#define ANA_L3_CPU_QU_CFG \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 120, 0, 1, 4)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU GENMASK(30, 28)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU GENMASK(26, 24)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_OPT_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU GENMASK(22, 20)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_RLEG_IP_HDR_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU GENMASK(18, 16)
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_SIP_RPF_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU GENMASK(14, 12)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_LEN_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU GENMASK(10, 8)
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_MC_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU GENMASK(6, 4)
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_UC_FAIL_QU, x)
+
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU GENMASK(2, 0)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)
+#define ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG_CPU_IP_TTL_FAIL_QU, x)
+
+/* ANA_L3:COMMON:CPU_QU_CFG2 */
+#define ANA_L3_CPU_QU_CFG2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 124, 0, 1, 4)
+
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU GENMASK(2, 0)
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_SET(x)\
+ FIELD_PREP(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)
+#define ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU_GET(x)\
+ FIELD_GET(ANA_L3_CPU_QU_CFG2_CPU_IP_DECAP_QU, x)
+
+/* ANA_L3:COMMON:SIP_SECURE_ENA */
+#define ANA_L3_SIP_SECURE_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 144, 0, 1, 4)
+
+/* ANA_L3:COMMON:SIP_SECURE_ENA1 */
+#define ANA_L3_SIP_SECURE_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 148, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:SIP_SECURE_ENA2 */
+#define ANA_L3_SIP_SECURE_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 152, 0, 1, 4)
+
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2 GENMASK(5, 0)
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)
+#define ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_SIP_SECURE_ENA2_SIP_CMP_ENA2, x)
+
+/* ANA_L3:COMMON:DIP_SECURE_ENA */
+#define ANA_L3_DIP_SECURE_ENA \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 156, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:DIP_SECURE_ENA1 */
+#define ANA_L3_DIP_SECURE_ENA1 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 160, 0, 1, 4)
+
+/* SPARX5 ONLY */
+/* ANA_L3:COMMON:DIP_SECURE_ENA2 */
+#define ANA_L3_DIP_SECURE_ENA2 \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_COMMON], 0, 1, 184, \
+ 164, 0, 1, 4)
+
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2 BIT(0)
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_SET(x)\
+ FIELD_PREP(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)
+#define ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2_GET(x)\
+ FIELD_GET(ANA_L3_DIP_SECURE_ENA2_DIP_CMP_ENA2, x)
+
+/* ANA_L3:VLAN:VMID_CFG */
+#define ANA_L3_VMID_CFG(g) \
+ __REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 0, 0, \
+ 1, 4)
+
+#define ANA_L3_VMID_CFG_VMID\
+ GENMASK(regs->fsize[FW_ANA_L3_VMID_CFG_VMID] + 0 - 1, 0)
+#define ANA_L3_VMID_CFG_VMID_SET(x)\
+ spx5_field_prep(ANA_L3_VMID_CFG_VMID, x)
+#define ANA_L3_VMID_CFG_VMID_GET(x)\
+ spx5_field_get(ANA_L3_VMID_CFG_VMID, x)
+
/* ANA_L3:VLAN:VLAN_CFG */
#define ANA_L3_VLAN_CFG(g) \
__REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 8, 0, \
@@ -1871,6 +2321,13 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA_GET(x)\
FIELD_GET(ANA_L3_VLAN_CFG_VLAN_MIRROR_ENA, x)
+/* LAN969X ONLY */
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS BIT(31)
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)
+#define ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS_GET(x)\
+ FIELD_GET(ANA_L3_VLAN_CFG_VLAN_PGID_CPU_DIS, x)
+
/* ANA_L3:VLAN:VLAN_MASK_CFG */
#define ANA_L3_VLAN_MASK_CFG(g) \
__REG(TARGET_ANA_L3, 0, 1, 0, g, regs->gcnt[GC_ANA_L3_VLAN], 64, 16, 0,\
@@ -1894,6 +2351,154 @@ extern const struct sparx5_regs *regs;
#define ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2_GET(x)\
FIELD_GET(ANA_L3_VLAN_MASK_CFG2_VLAN_PORT_MASK2, x)
+/* ANA_L3:VMID:RLEG_CTRL */
+#define ANA_L3_RLEG_CTRL(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_VMID], g, \
+ regs->gcnt[GC_ANA_L3_VMID], 64, 0, 0, 1, 4)
+
+#define ANA_L3_RLEG_CTRL_RLEG_EVID GENMASK(31, 19)
+#define ANA_L3_RLEG_CTRL_RLEG_EVID_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_EVID, x)
+#define ANA_L3_RLEG_CTRL_RLEG_EVID_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_EVID, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA BIT(18)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_STAT_IP_ONLY_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA BIT(17)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_STAT_IP_ONLY_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE GENMASK(15, 14)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_SIP_RPF_MODE, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE GENMASK(13, 12)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_SIP_RPF_MODE, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS BIT(9)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_TTL_DECR_DIS, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS BIT(8)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_TTL_DECR_DIS, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA BIT(7)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA BIT(6)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA BIT(5)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_MC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA BIT(4)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_MC_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA BIT(3)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_ICMP_REDIR_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA BIT(2)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_ICMP_REDIR_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA BIT(1)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP6_VRID_ENA, x)
+
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA BIT(0)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)
+#define ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_RLEG_CTRL_RLEG_IP4_VRID_ENA, x)
+
+/* ANA_L3:ARP:ARP_CFG_0 */
+#define ANA_L3_ARP_CFG_0(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_ARP], g, \
+ regs->gcnt[GC_ANA_L3_ARP], 32, 0, 0, 1, 4)
+
+#define ANA_L3_ARP_CFG_0_MAC_MSB GENMASK(31, 16)
+#define ANA_L3_ARP_CFG_0_MAC_MSB_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_MAC_MSB, x)
+#define ANA_L3_ARP_CFG_0_MAC_MSB_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_MAC_MSB, x)
+
+#define ANA_L3_ARP_CFG_0_ARP_VMID\
+ GENMASK(regs->fsize[FW_ANA_L3_ARP_CFG_0_ARP_VMID] + 7 - 1, 7)
+#define ANA_L3_ARP_CFG_0_ARP_VMID_SET(x)\
+ spx5_field_prep(ANA_L3_ARP_CFG_0_ARP_VMID, x)
+#define ANA_L3_ARP_CFG_0_ARP_VMID_GET(x)\
+ spx5_field_get(ANA_L3_ARP_CFG_0_ARP_VMID, x)
+
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU GENMASK(6, 4)
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)
+#define ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_ZERO_DMAC_CPU_QU, x)
+
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA BIT(3)
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)
+#define ANA_L3_ARP_CFG_0_SIP_RPF_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SIP_RPF_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA BIT(2)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_VMID_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA BIT(1)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)
+#define ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_SECUR_MATCH_MAC_ENA, x)
+
+#define ANA_L3_ARP_CFG_0_ARP_ENA BIT(0)
+#define ANA_L3_ARP_CFG_0_ARP_ENA_SET(x)\
+ FIELD_PREP(ANA_L3_ARP_CFG_0_ARP_ENA, x)
+#define ANA_L3_ARP_CFG_0_ARP_ENA_GET(x)\
+ FIELD_GET(ANA_L3_ARP_CFG_0_ARP_ENA, x)
+
+/* ANA_L3:ARP:ARP_CFG_1 */
+#define ANA_L3_ARP_CFG_1(g) \
+ __REG(TARGET_ANA_L3, 0, 1, regs->gaddr[GA_ANA_L3_ARP], g, \
+ regs->gcnt[GC_ANA_L3_ARP], 32, 4, 0, 1, 4)
+
/* ASM:DEV_STATISTICS:RX_IN_BYTES_CNT */
#define ASM_RX_IN_BYTES_CNT(g) \
__REG(TARGET_ASM, 0, 1, 0, g, regs->gcnt[GC_ASM_DEV_STATISTICS], 512, \
@@ -4515,6 +5120,34 @@ extern const struct sparx5_regs *regs;
#define DSM_TAXI_CAL_CFG_CAL_PGM_SEL_GET(x)\
FIELD_GET(DSM_TAXI_CAL_CFG_CAL_PGM_SEL, x)
+/* EACL:COMMON:RLEG_CFG_0 */
+#define EACL_RLEG_CFG_0 \
+ __REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_COMMON], 0, 1, 216, 64, 0,\
+ 1, 4)
+
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define EACL_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* EACL:COMMON:RLEG_CFG_1 */
+#define EACL_RLEG_CFG_1 \
+ __REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_COMMON], 0, 1, 216, 68, 0,\
+ 1, 4)
+
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define EACL_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(EACL_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
/* EACL:ES2_KEY_SELECT_PROFILE:VCAP_ES2_KEY_SEL */
#define EACL_VCAP_ES2_KEY_SEL(g, r) \
__REG(TARGET_EACL, 0, 1, regs->gaddr[GA_EACL_ES2_KEY_SELECT_PROFILE], \
@@ -7353,6 +7986,34 @@ extern const struct sparx5_regs *regs;
#define REW_ES0_CTRL_ES0_LU_ENA_GET(x)\
FIELD_GET(REW_ES0_CTRL_ES0_LU_ENA, x)
+/* REW:COMMON:RLEG_CFG_0 */
+#define REW_RLEG_CFG_0 \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_COMMON], 0, 1, 1232, 1156, \
+ 0, 1, 4)
+
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB GENMASK(23, 0)
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)
+#define REW_RLEG_CFG_0_RLEG_MAC_LSB_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_0_RLEG_MAC_LSB, x)
+
+/* REW:COMMON:RLEG_CFG_1 */
+#define REW_RLEG_CFG_1 \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_COMMON], 0, 1, 1232, 1160, \
+ 0, 1, 4)
+
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL GENMASK(25, 24)
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+#define REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL, x)
+
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB GENMASK(23, 0)
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(x)\
+ FIELD_PREP(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)
+#define REW_RLEG_CFG_1_RLEG_MAC_MSB_GET(x)\
+ FIELD_GET(REW_RLEG_CFG_1_RLEG_MAC_MSB, x)
+
/* REW:PORT:PORT_VLAN_CFG */
#define REW_PORT_VLAN_CFG(g) \
__REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_PORT], g, \
@@ -7579,6 +8240,30 @@ extern const struct sparx5_regs *regs;
#define REW_PTP_GEN_STAMP_FMT_RT_FMT_GET(x)\
FIELD_GET(REW_PTP_GEN_STAMP_FMT_RT_FMT, x)
+/* REW:VMID:RLEG_CTRL */
+#define REW_RLEG_CTRL(g) \
+ __REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_VMID], g, \
+ regs->gcnt[GC_REW_VMID], 4, 0, 0, 1, 4)
+
+#define REW_RLEG_CTRL_DECAP_IRLEG\
+ GENMASK(regs->fsize[FW_REW_RLEG_CTRL_DECAP_IRLEG] + 13 - 1, 13)
+#define REW_RLEG_CTRL_DECAP_IRLEG_SET(x)\
+ spx5_field_prep(REW_RLEG_CTRL_DECAP_IRLEG, x)
+#define REW_RLEG_CTRL_DECAP_IRLEG_GET(x)\
+ spx5_field_get(REW_RLEG_CTRL_DECAP_IRLEG, x)
+
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED BIT(12)
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_SET(x)\
+ FIELD_PREP(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)
+#define REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED_GET(x)\
+ FIELD_GET(REW_RLEG_CTRL_RLEG_VSTAX2_WAS_TAGGED, x)
+
+#define REW_RLEG_CTRL_RLEG_EVID GENMASK(11, 0)
+#define REW_RLEG_CTRL_RLEG_EVID_SET(x)\
+ FIELD_PREP(REW_RLEG_CTRL_RLEG_EVID, x)
+#define REW_RLEG_CTRL_RLEG_EVID_GET(x)\
+ FIELD_GET(REW_RLEG_CTRL_RLEG_EVID, x)
+
/* REW:RAM_CTRL:RAM_INIT */
#define REW_RAM_INIT \
__REG(TARGET_REW, 0, 1, regs->gaddr[GA_REW_RAM_CTRL], 0, 1, 4, 0, 0, 1,\
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
index 3863f954bd832..36e772cfdb6df 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:29 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#include "sparx5_regs.h"
@@ -77,12 +77,16 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_ANA_CL_COMMON] = 166912,
[GA_ANA_L2_COMMON] = 566024,
[GA_ANA_L3_COMMON] = 493632,
+ [GA_ANA_L3_VMID] = 458752,
+ [GA_ANA_L3_ARP_PTR_REMAP] = 493824,
+ [GA_ANA_L3_ARP] = 327680,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 491460,
[GA_ASM_CFG] = 33280,
[GA_ASM_PFC_TIMER_CFG] = 34716,
[GA_ASM_LBK_WM_CFG] = 34744,
[GA_ASM_LBK_MISC_CFG] = 34756,
[GA_ASM_RAM_CTRL] = 34832,
+ [GA_EACL_COMMON] = 118480,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 149504,
[GA_EACL_CNT_TBL] = 122880,
[GA_EACL_POL_CFG] = 150608,
@@ -103,6 +107,7 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_QSYS_RAM_CTRL] = 2344,
[GA_REW_COMMON] = 387264,
[GA_REW_PORT] = 360448,
+ [GA_REW_VMID] = 389120,
[GA_REW_VOE_PORT_LM_CNT] = 393216,
[GA_REW_RAM_CTRL] = 378696,
[GA_VOP_RAM_CTRL] = 279176,
@@ -124,6 +129,8 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {
[GC_ANA_L2_ISDX_LIMIT] = 1536,
[GC_ANA_L2_ISDX] = 4096,
[GC_ANA_L3_VLAN] = 5120,
+ [GC_ANA_L3_VMID] = 511,
+ [GC_ANA_L3_ARP] = 2048,
[GC_ASM_DEV_STATISTICS] = 65,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 138,
[GC_EACL_CNT_TBL] = 2048,
@@ -134,6 +141,7 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = {
[GC_PTP_PTP_PINS] = 5,
[GC_PTP_PHASE_DETECTOR_CTRL] = 5,
[GC_REW_PORT] = 70,
+ [GC_REW_VMID] = 511,
[GC_REW_VOE_PORT_LM_CNT] = 520,
};
@@ -191,7 +199,12 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 32,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 13,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 10,
+ [FW_ANA_L3_L3_UC_ENA_L3_UC_ENA] = 32,
+ [FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1] = 32,
+ [FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA] = 32,
+ [FW_ANA_L3_VMID_CFG_VMID] = 9,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 32,
+ [FW_ANA_L3_ARP_CFG_0_ARP_VMID] = 9,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 4,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 9,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 7,
@@ -217,6 +230,7 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_QSYS_ATOP_ATOP] = 12,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 12,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 7,
+ [FW_REW_RLEG_CTRL_DECAP_IRLEG] = 9,
[FW_XQS_STAT_CFG_STAT_VIEW] = 13,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 15,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 15,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
index 585589a31e906..45b13da98af76 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
- * Copyright (c) 2024 Microchip Technology Inc.
+ * Copyright (c) 2026 Microchip Technology Inc.
*/
-/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
- * Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
+/* This file is autogenerated by cml-utils 2026-02-16 21:50:30 +0100.
+ * Commit ID: 09d52195beeeb9682063ef0bd6eec50eebc137e2
*/
#ifndef _SPARX5_REGS_H_
@@ -86,12 +86,16 @@ enum sparx5_gaddr_enum {
GA_ANA_CL_COMMON,
GA_ANA_L2_COMMON,
GA_ANA_L3_COMMON,
+ GA_ANA_L3_VMID,
+ GA_ANA_L3_ARP_PTR_REMAP,
+ GA_ANA_L3_ARP,
GA_ANA_L3_VLAN_ARP_L3MC_STICKY,
GA_ASM_CFG,
GA_ASM_PFC_TIMER_CFG,
GA_ASM_LBK_WM_CFG,
GA_ASM_LBK_MISC_CFG,
GA_ASM_RAM_CTRL,
+ GA_EACL_COMMON,
GA_EACL_ES2_KEY_SELECT_PROFILE,
GA_EACL_CNT_TBL,
GA_EACL_POL_CFG,
@@ -112,6 +116,7 @@ enum sparx5_gaddr_enum {
GA_QSYS_RAM_CTRL,
GA_REW_COMMON,
GA_REW_PORT,
+ GA_REW_VMID,
GA_REW_VOE_PORT_LM_CNT,
GA_REW_RAM_CTRL,
GA_VOP_RAM_CTRL,
@@ -134,6 +139,8 @@ enum sparx5_gcnt_enum {
GC_ANA_L2_ISDX_LIMIT,
GC_ANA_L2_ISDX,
GC_ANA_L3_VLAN,
+ GC_ANA_L3_VMID,
+ GC_ANA_L3_ARP,
GC_ASM_DEV_STATISTICS,
GC_EACL_ES2_KEY_SELECT_PROFILE,
GC_EACL_CNT_TBL,
@@ -144,6 +151,7 @@ enum sparx5_gcnt_enum {
GC_PTP_PTP_PINS,
GC_PTP_PHASE_DETECTOR_CTRL,
GC_REW_PORT,
+ GC_REW_VMID,
GC_REW_VOE_PORT_LM_CNT,
GCNT_LAST,
};
@@ -204,7 +212,12 @@ enum sparx5_fsize_enum {
FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA,
FW_ANA_L2_DLB_CFG_DLB_IDX,
FW_ANA_L2_TSN_CFG_TSN_SFID,
+ FW_ANA_L3_L3_UC_ENA_L3_UC_ENA,
+ FW_ANA_L3_SIP_SECURE_ENA1_SIP_CMP_ENA1,
+ FW_ANA_L3_DIP_SECURE_ENA_DIP_CMP_ENA,
+ FW_ANA_L3_VMID_CFG_VMID,
FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK,
+ FW_ANA_L3_ARP_CFG_0_ARP_VMID,
FW_FDMA_CH_CFG_CH_DCB_DB_CNT,
FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL,
FW_HSCH_SE_CFG_SE_DWRR_CNT,
@@ -230,6 +243,7 @@ enum sparx5_fsize_enum {
FW_QSYS_ATOP_ATOP,
FW_QSYS_ATOP_TOT_CFG_ATOP_TOT,
FW_REW_RTAG_ETAG_CTRL_IPE_TBL,
+ FW_REW_RLEG_CTRL_DECAP_IRLEG,
FW_XQS_STAT_CFG_STAT_VIEW,
FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP,
FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_router.c b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
new file mode 100644
index 0000000000000..5f6add84b0d6c
--- /dev/null
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_router.c
@@ -0,0 +1,3021 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/inetdevice.h>
+#include <linux/list.h>
+#include <linux/rhashtable.h>
+#include <net/addrconf.h>
+#include <net/arp.h>
+#include <net/fib_notifier.h>
+#include <net/ip6_fib.h>
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/neighbour.h>
+#include <net/netevent.h>
+#include <net/nexthop.h>
+
+#include "sparx5_main.h"
+#include "sparx5_port.h"
+#include "sparx5_vcap_ag_api.h"
+#include "sparx5_vcap_impl.h"
+
+/* The main routing objects are
+ *
+ * 1) Router legs, which correspond to IP interfaces. They can have multiple IPs
+ * attached and are associated with a VLAN. Only routes with nexthops that
+ * egress on ports that are part of a router leg are considered for
+ * offloading.
+ *
+ * 2) Fib entries, which correspond to entries in the routing table.
+ * > ip route add 6.6.0.0/16 nexthop via 1.0.10.10
+ * will create fib entry for 6.6.0.0/16, if it is offloadable.
+ *
+ * 3) Nexthop groups and nexthops. Each fib entry has exactly one nexthop hop.
+ * To simplify the SW representation, we duplicate nexthops and nexthops
+ * groups for each fib entry, even when the same nexthop is used for many
+ * different routes.
+ *
+ * 4) Neigh entries, which correspond to directly connected neighbours.
+ * These are created mainly by ARP events. Their job is to maintain the
+ * association of L3 and L2 addresses.
+ *
+ * Neigh entries are anchored to the router leg they belong to, and live as
+ * long as that leg exists. They are also referenced by any nexthops using
+ * them as a gateway, so we keep track of those references.
+ *
+ * If a neighbour which is used in a nexthop group dies, we will set the mac
+ * to zero, so traffic for this nexthop is trapped.
+ *
+ *
+ * Both fib_entry and neigh_entry can trigger writes to the LPM VCAP, and own
+ * entries in HW. Fib entries own the corresponding route associated with them.
+ * Neigh entries own a /32 route, for traffic destined directly to the
+ * neighbour.
+ *
+ * We have 3 main cases for routing:
+ *
+ * 1) Routes for directly connected subnets. E.g. router has IP 1.0.10.1, and
+ * routes subnet 1.0.10.0/24.
+ *
+ * In this case, we have a fib_entry, with a non-gateway nexthop group. We
+ * install a LPM VCAP route, with action type arp entry, for 1.0.10.0/24 with
+ * a zero mac, to ensure frames destined for this subnet will be sent to the
+ * CPU and start the ARP process.
+ *
+ * When we get the arp reply, we create a neigh_entry for each neighbour and
+ * install a direct route in the LPM VCAP for this neighbour. For example,
+ * 1.0.10.11 is sent to DMAC 0x1111110001. This is how we route directly
+ * connected subnets.
+ *
+ * Moreover, each router leg maintains a list of all neighbours discovered
+ * on it. Neighbours are anchored to the leg, not to the covering route, so
+ * they persist as long as the leg exists, independent of which fib entry
+ * covers their subnet.
+ *
+ * +-------------+ |-> neigh_entry 1.0.10.10
+ * | router leg | neigh_list |-> neigh_entry 1.0.10.11
+ * | 1.0.10.1 |------------|-> neigh_entry 1.0.10.12
+ * +-------------+
+ *
+ * +-------------+ +--------------+
+ * | fib_entry | | nexthop group|
+ * | 1.0.10.0/24 |----+ |
+ * +-------------+ | nexthop --------> NULL (Write zero mac in VCAP)
+ * +--------------+
+ *
+ *
+ * 2) Routes for non-connected subnets. E.g. we are routing subnet 6.6.0.0/16,
+ * but we have no IP in this subnet. We are routing via nexthops, which are
+ * directly connected. Say we have >=2 nexthops.
+ *
+ * In this case, have a fib_entry with a gateway nexthop group. Each nexthop
+ * points to a neigh_entry, corresponding to the gateway used for routing.
+ *
+ * Say we use the nexthops:
+ *
+ * - 1.0.11.10
+ * - 1.0.10.10
+ * - 1.0.9.10
+ *
+ * We install a LPM VCAP route for 6.6.0.0/16, which contains a pointer
+ * to the hw arp table, and the size of the group. The ARP table
+ * contains the Mac addresses of the nexthops. The Mac addresses are
+ * supplied by neigh_entries.
+ *
+ *
+ * +-------------+ +--------------+
+ * | fib_entry | | nexthop group|
+ * | 6.6.0.0/164 |----+ |
+ * +-------------+ | nexthop --------> neigh_entry 1.0.11.10
+ * | nexthop --------> neigh_entry 1.0.10.10
+ * | nexthop --------> neigh_entry 1.0.9.10
+ * +--------------+
+ *
+ *
+ * 3) Local routes for traffic destined to the router. If the router has an IP
+ * 1.0.10.1, then we must ensure this traffic is sent to the CPU.
+ * Therefore, we install a direct route for 1.0.10.1/32 in the LPM VCAP, with
+ * zero mac.
+ *
+ * 4) All IPv6 link-local traffic is explicitly trapped.
+ *
+ * On the hardware side, we use the VCAP LPM and ARP table for UC IPv4
+ * routing. HW picks the match found at the highest address in the VCAP LPM.
+ * To ensure the longest prefix match we make sure to order the entries
+ * according to mask length, with longer masks at higher addresses.
+ *
+ * It is possible to store ARP data, such as DMAC, directly in the VCAP LPM
+ * using ARP entry actions. We do this whenever possible, so the ARP table
+ * is only used when a route has multiple nexthops.
+ *
+ * With the above breakdown in mind, cases 1) and 3) use arp entries, and
+ * case 2) use the arp table if the number of nexthops is >1.
+ *
+ * If the DMAC written to HW is all zero, the chip will trap the frame,
+ * redirecting it to the CPU. This is how we get the kernel to perform ARP
+ * requests on our behalf.
+ *
+ * The nexthop group must be laid out at contiguous addresses in the ARP table.
+ * The VCAP LPM stores a pointer to the bottom address in the group, and the
+ * group size. We do not use the arp pointer remap table.
+ *
+ * The layout of nexthops in a nexthop group matches the layout in HW, e.g.
+ *
+ * nhgi->nexthops[0] -> arp table address n
+ * ...
+ * nhgi->nexthops[k] -> arp table address n+k
+ *
+ * where the n is the ARP table offset (atbl_offset) for the group.
+ */
+
+#define SPARX5_MAX_ECMP_SIZE 16
+#define SPARX5_RLEG_USE_GLOBAL_BASE_MAC 2
+#define SPARX5_LINK_LOCAL_PREFIX_LEN 64
+#define SPARX5_BLACKHOLE_VMID(spx5) ((spx5)->data->consts->vmid_cnt - 1)
+/* The rewriter field REW_RLEG_CTRL_RLEG_EVID will be written with the
+ * blackhole vid, but it is only 12 bits wide. However, this is only used for
+ * routing based rewrites on egress, which is not used for the blackhole.
+ * The important field is ANA_L3_RLEG_CTRL_RLEG_EVID which is 13 bits wide.
+ * Therefore, it is safe to use VIDs wider than 12 bits for the blackhole
+ * vid, frames will not be forwarded into VLAN 0s port mask.
+ */
+#define SPARX5_BLACKHOLE_VID VLAN_N_VID
+
+struct sparx5_rr_fib6_entry_info {
+ struct fib6_info **rt_arr;
+ unsigned int nrt6;
+};
+
+enum sparx5_rr_l3_version {
+ SPARX5_IPV4 = 0,
+ SPARX5_IPV6,
+};
+
+#define SPARX5_IADDR_LEN(v) ((v) == SPARX5_IPV4 ? 32 : 128)
+/* Order longer prefixes at high addresses. */
+#define LPM_SORT_KEY(plen) (SPARX5_IADDR_LEN(SPARX5_IPV6) - (plen))
+
+struct sparx5_rr_fib_info {
+ union {
+ struct fib_entry_notifier_info fen4_info;
+ struct sparx5_rr_fib6_entry_info fe6_info;
+ };
+ enum sparx5_rr_l3_version version;
+};
+
+struct sparx5_fib_event_work {
+ struct work_struct work;
+ struct sparx5_rr_fib_info fi;
+ struct sparx5 *sparx5;
+ unsigned long event;
+};
+
+struct sparx5_rr_netevent_work {
+ struct work_struct work;
+ struct sparx5 *sparx5;
+ struct neighbour *neigh;
+ unsigned long event;
+};
+
+struct sparx5_rr_router_leg {
+ struct net_device *dev;
+ netdevice_tracker dev_tracker;
+ struct sparx5 *sparx5;
+ struct list_head leg_list_node; /* Router member */
+ struct list_head neigh_list; /* Neighbours bound to this leg */
+ u16 vmid; /* Internal id */
+ u32 vid; /* VLAN id */
+};
+
+struct sparx5_iaddr {
+ union {
+ __be32 ipv4;
+ struct in6_addr ipv6;
+ }; /* Must be first */
+ enum sparx5_rr_l3_version version;
+};
+
+#define LPM_PROTO(iaddr) ((iaddr)->version ? ETH_P_IPV6 : ETH_P_IP)
+
+struct sparx5_rr_neigh_entry {
+ struct sparx5_rr_neigh_key {
+ struct net_device *dev;
+ struct sparx5_iaddr iaddr;
+ } key;
+ netdevice_tracker dev_tracker;
+ struct rhash_head ht_node;
+ struct sparx5_rr_router_leg *leg;
+ struct list_head leg_list_node; /* Member of leg->neigh_list */
+ struct neigh_table *neigh_tbl; /* Kernel neighbour table */
+ struct list_head nexthop_list; /* Nexthops using this neigh entry */
+ struct sparx5_rr_hw_route hw_route;
+ unsigned char hwaddr[ETH_ALEN];
+ u16 vmid;
+ bool connected;
+};
+
+struct sparx5_rr_nexthop {
+ struct sparx5_rr_neigh_entry *neigh_entry;
+ struct sparx5_rr_nexthop_group *grp;
+ struct list_head neigh_list_node; /* Neigh entry member */
+ struct neigh_table *neigh_tbl; /* Kernel neighbour table */
+ struct sparx5_iaddr gw_addr;
+ int ifindex;
+ bool gateway;
+ bool trapped;
+};
+
+struct sparx5_rr_nexthop_group_info {
+ struct sparx5_rr_nexthop_group *grp;
+ u16 atbl_offset;
+ bool atbl_offset_valid;
+ u8 count; /* HW allows up to 16 nexthops */
+ struct sparx5_rr_nexthop nexthops[] __counted_by(count);
+};
+
+struct sparx5_rr_nexthop_group {
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_nexthop_group_info *nhgi;
+};
+
+enum sparx5_rr_fib_type {
+ SPARX5_RR_FIB_TYPE_INVALID = 0,
+ SPARX5_RR_FIB_TYPE_LOCAL,
+ SPARX5_RR_FIB_TYPE_UNICAST,
+ SPARX5_RR_FIB_TYPE_MULTICAST,
+ SPARX5_RR_FIB_TYPE_BLACKHOLE,
+ SPARX5_RR_FIB_TYPE_PROHIBIT,
+ SPARX5_RR_FIB_TYPE_UNREACHABLE,
+};
+
+struct sparx5_rr_fib_key {
+ struct sparx5_iaddr addr;
+ u32 prefix_len;
+ u32 tb_id; /* Routing table type: RT_TABLE_* */
+};
+
+struct sparx5_rr_fib_entry {
+ struct sparx5_rr_fib_key key;
+ enum sparx5_rr_fib_type type;
+ struct rhash_head ht_node; /* Router member */
+ struct list_head fib_node; /* Router member, all fib entries */
+ struct sparx5_rr_hw_route hw_route;
+ struct sparx5_rr_nexthop_group *nh_grp;
+ struct sparx5_rr_fib_info fi;
+ bool trap;
+ bool offload_fail;
+};
+
+struct sparx5_rr_inet6addr_event_work {
+ struct work_struct work;
+ struct sparx5 *sparx5;
+ struct net_device *dev;
+ netdevice_tracker dev_tracker;
+ unsigned long event;
+};
+
+static void sparx5_rr_schedule_work(struct sparx5 *sparx5,
+ struct work_struct *work)
+{
+ queue_work(sparx5->router->sparx5_router_owq, work);
+}
+
+static void sparx5_rr_fib_info_init(struct sparx5_rr_fib_info *fi,
+ enum sparx5_rr_l3_version version)
+{
+ fi->version = version;
+
+ switch (version) {
+ case SPARX5_IPV4:
+ fi->fen4_info.fi = NULL;
+ return;
+ case SPARX5_IPV6:
+ fi->fe6_info.nrt6 = 0;
+ fi->fe6_info.rt_arr = NULL;
+ return;
+ }
+}
+
+/* Return number of nexthops. */
+static int sparx5_rr_fib_info_nhs(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fib_info_num_path(fi->fen4_info.fi);
+ case SPARX5_IPV6:
+ return fi->fe6_info.nrt6;
+ default:
+ WARN_ON(1);
+ return 0;
+ }
+}
+
+static struct fib_nh_common *
+sparx5_rr_fib_info_nhc(struct sparx5_rr_fib_info *fi, int nhsel)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fib_info_nhc(fi->fen4_info.fi, nhsel);
+ case SPARX5_IPV6:
+ return &fi->fe6_info.rt_arr[nhsel]->fib6_nh->nh_common;
+ default:
+ WARN_ON(1);
+ return NULL;
+ }
+}
+
+static bool sparx5_rr_fib_info_is_nh_obj(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return !!fi->fen4_info.fi->nh;
+ case SPARX5_IPV6:
+ return !!fi->fe6_info.rt_arr[0]->nh;
+ default:
+ WARN_ON(1);
+ return false;
+ }
+}
+
+static u8 sparx5_rr_fib_info_type(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fi->fen4_info.type;
+ case SPARX5_IPV6:
+ return fi->fe6_info.rt_arr[0]->fib6_type;
+ default:
+ WARN_ON(1);
+ return RTN_UNSPEC;
+ }
+}
+
+static u32 sparx5_rr_fib_info_tb_id(struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return fi->fen4_info.tb_id;
+ case SPARX5_IPV6:
+ return fi->fe6_info.rt_arr[0]->fib6_table->tb6_id;
+ default:
+ WARN_ON(1);
+ return RT_TABLE_UNSPEC;
+ }
+}
+
+static bool sparx5_rr_fib_info_should_ignore(struct sparx5_rr_fib_info *fi)
+{
+ return fi->version == SPARX5_IPV6 &&
+ ipv6_addr_type(&fi->fe6_info.rt_arr[0]->fib6_dst.addr) &
+ (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void sparx5_rr_rt6_release(struct fib6_info *rt)
+{
+ if (!rt->nh)
+ rt->fib6_nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
+
+ fib6_info_release(rt);
+}
+#else
+static void sparx5_rr_rt6_release(struct fib6_info *rt)
+{
+}
+#endif
+
+static void sparx5_rr_fib6_info_put(struct sparx5_rr_fib6_entry_info *fi)
+{
+ for (int i = 0; i < fi->nrt6; i++)
+ sparx5_rr_rt6_release(fi->rt_arr[i]);
+
+ kfree(fi->rt_arr);
+ fi->nrt6 = 0;
+ fi->rt_arr = NULL;
+}
+
+static void sparx5_rr_fib_info_put(struct sparx5_rr_fib_info *fi)
+{
+ if (fi->version == SPARX5_IPV4) {
+ if (fi->fen4_info.fi) {
+ fib_info_put(fi->fen4_info.fi);
+ fi->fen4_info.fi = NULL;
+ }
+ return;
+ }
+
+ sparx5_rr_fib6_info_put(&fi->fe6_info);
+}
+
+static void sparx5_rr_split_mac(unsigned char mac[ETH_ALEN], u32 split,
+ u32 *msb, u32 *lsb)
+{
+ u32 mask = GENMASK(split - 1, 0);
+ u64 m = ether_addr_to_u64(mac);
+
+ *lsb = m & mask;
+ *msb = m >> split;
+}
+
+static int sparx5_rr_arp_tbl_grp_alloc(struct sparx5 *sparx5,
+ unsigned int nh_grp_size)
+{
+ int offset;
+
+ offset = bitmap_find_next_zero_area(sparx5->router->arp_tbl_mask,
+ sparx5->data->consts->arp_tbl_cnt,
+ 0, nh_grp_size, 0);
+ if (offset >= sparx5->data->consts->arp_tbl_cnt)
+ return -ENOMEM;
+
+ bitmap_set(sparx5->router->arp_tbl_mask, offset, nh_grp_size);
+
+ return offset;
+}
+
+static void sparx5_rr_arp_tbl_grp_free(struct sparx5 *sparx5,
+ unsigned int nh_grp_size, int offset)
+{
+ bitmap_clear(sparx5->router->arp_tbl_mask, offset, nh_grp_size);
+}
+
+static int sparx5_vmid_alloc(struct sparx5 *sparx5)
+{
+ int vmid;
+
+ vmid = find_first_zero_bit(sparx5->router->vmid_mask,
+ sparx5->data->consts->vmid_cnt);
+ if (vmid >= sparx5->data->consts->vmid_cnt)
+ return -ENOMEM;
+
+ set_bit(vmid, sparx5->router->vmid_mask);
+
+ return vmid;
+}
+
+static void sparx5_vmid_free(struct sparx5 *sparx5, u16 vmid)
+{
+ clear_bit(vmid, sparx5->router->vmid_mask);
+}
+
+static void sparx5_rr_nb2neigh_key(struct neighbour *n,
+ struct sparx5_rr_neigh_key *key)
+{
+ memset(key, 0, sizeof(*key));
+
+ /* The primary_key, tbl->family and dev are constant for the lifetime of
+ * the neighbour, so we can read them without n->lock.
+ */
+ if (n->tbl->family == AF_INET) {
+ key->iaddr.version = SPARX5_IPV4;
+ key->iaddr.ipv4 = *(__be32 *)n->primary_key;
+ } else {
+ key->iaddr.version = SPARX5_IPV6;
+ key->iaddr.ipv6 = *(struct in6_addr *)n->primary_key;
+ }
+
+ key->dev = n->dev;
+}
+
+static void
+sparx5_rr_neigh_entry_offload_mark(struct sparx5_rr_neigh_entry *entry,
+ bool offloaded)
+{
+ struct neighbour *n;
+
+ if (!entry->neigh_tbl)
+ return;
+
+ n = neigh_lookup(entry->neigh_tbl, &entry->key.iaddr,
+ entry->key.dev);
+ if (!n)
+ return;
+
+ write_lock_bh(&n->lock);
+ if (offloaded)
+ n->flags |= NTF_OFFLOADED;
+ else
+ n->flags &= ~NTF_OFFLOADED;
+ write_unlock_bh(&n->lock);
+
+ neigh_release(n);
+}
+
+static const struct rhashtable_params sparx5_neigh_ht_params = {
+ .key_offset = offsetof(struct sparx5_rr_neigh_entry, key),
+ .head_offset = offsetof(struct sparx5_rr_neigh_entry, ht_node),
+ .key_len = sizeof(struct sparx5_rr_neigh_key),
+ .automatic_shrinking = true,
+};
+
+static const struct rhashtable_params sparx5_rr_fib_entry_ht_params = {
+ .key_offset = offsetof(struct sparx5_rr_fib_entry, key),
+ .head_offset = offsetof(struct sparx5_rr_fib_entry, ht_node),
+ .key_len = sizeof(struct sparx5_rr_fib_key),
+ .automatic_shrinking = true,
+};
+
+static void sparx5_rr_to_fib4_key(u32 dst, int dst_len, u32 tb_id,
+ struct sparx5_rr_fib_key *key)
+{
+ memset(key, 0, sizeof(*key));
+ key->addr.version = SPARX5_IPV4;
+ key->addr.ipv4 = cpu_to_be32(dst);
+ key->prefix_len = dst_len;
+ key->tb_id = tb_id;
+}
+
+static void sparx5_rr_to_fib6_key(struct in6_addr *addr, int prefix_len,
+ u32 tb_id, struct sparx5_rr_fib_key *key)
+{
+ memset(key, 0, sizeof(*key));
+ key->addr.version = SPARX5_IPV6;
+ memcpy(&key->addr.ipv6, addr, sizeof(*addr));
+ key->prefix_len = prefix_len;
+ key->tb_id = tb_id;
+}
+
+static void sparx5_rr_fib_info_to_fib_key(struct sparx5_rr_fib_info *fi,
+ struct sparx5_rr_fib_key *key)
+{
+ struct fib_entry_notifier_info *fen_info;
+ struct fib6_info *rt;
+
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ fen_info = &fi->fen4_info;
+ sparx5_rr_to_fib4_key(fen_info->dst, fen_info->dst_len,
+ fen_info->tb_id, key);
+ return;
+ case SPARX5_IPV6:
+ rt = fi->fe6_info.rt_arr[0];
+
+ sparx5_rr_to_fib6_key(&rt->fib6_dst.addr, rt->fib6_dst.plen,
+ rt->fib6_table->tb6_id, key);
+ return;
+ }
+}
+
+static void sparx5_rr_inet6_make_mask_le(int logmask, u8 *mask)
+{
+ /* Caller must ensure 0 <= logmask <= 128 */
+ int byte_prefix = logmask / BITS_PER_BYTE;
+ int rem = logmask % BITS_PER_BYTE;
+
+ memset(mask, 0, 16);
+
+ for (int i = 0; i < byte_prefix; i++)
+ mask[15 - i] = 0xff;
+
+ if (rem)
+ mask[15 - byte_prefix] = GENMASK(7, 7 - rem + 1);
+}
+
+static int sparx5_rr_lpm_rule_xip_add(struct vcap_rule *rule,
+ struct sparx5_iaddr *addr, u32 prefix_len)
+{
+ struct vcap_u128_key addr_key;
+ u32 mask, iaddr;
+
+ switch (addr->version) {
+ case SPARX5_IPV4:
+ mask = ntohl(inet_make_mask(prefix_len));
+ iaddr = ntohl(addr->ipv4);
+
+ return vcap_rule_add_key_u32(rule, VCAP_KF_IP4_XIP, iaddr,
+ mask);
+ case SPARX5_IPV6:
+ sparx5_rr_inet6_make_mask_le(prefix_len, addr_key.mask);
+ vcap_netbytes_copy(addr_key.value, addr->ipv6.s6_addr, 16);
+
+ return vcap_rule_add_key_u128(rule, VCAP_KF_IP6_XIP, &addr_key);
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+}
+
+static struct sparx5_rr_router_leg *
+sparx5_rr_leg_find_by_dev(struct sparx5 *sparx5, struct net_device *dev)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ list_for_each_entry(leg, &sparx5->router->leg_list, leg_list_node) {
+ if (leg->dev == dev)
+ return leg;
+ }
+
+ return NULL;
+}
+
+static struct sparx5_rr_fib_entry *
+sparx5_rr_fib_entry_lookup(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key)
+{
+ return rhashtable_lookup_fast(&sparx5->router->fib_ht, key,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static int sparx5_rr_fib_entry_insert(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ return rhashtable_insert_fast(&sparx5->router->fib_ht,
+ &fib_entry->ht_node,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static void sparx5_rr_fib_entry_remove(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ rhashtable_remove_fast(&sparx5->router->fib_ht, &fib_entry->ht_node,
+ sparx5_rr_fib_entry_ht_params);
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_lookup(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key)
+{
+ return rhashtable_lookup_fast(&sparx5->router->neigh_ht, key,
+ sparx5_neigh_ht_params);
+}
+
+static int sparx5_rr_neigh_entry_insert(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ return rhashtable_insert_fast(&sparx5->router->neigh_ht,
+ &entry->ht_node, sparx5_neigh_ht_params);
+}
+
+static void sparx5_rr_neigh_entry_remove(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ rhashtable_remove_fast(&sparx5->router->neigh_ht, &entry->ht_node,
+ sparx5_neigh_ht_params);
+}
+
+static int sparx5_lower_dev_walk(struct net_device *lower_dev,
+ struct netdev_nested_priv *priv)
+{
+ int ret = 0;
+
+ if (sparx5_netdevice_check(lower_dev)) {
+ priv->data = (void *)netdev_priv(lower_dev);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static struct sparx5_port *
+sparx5_port_dev_lower_find_rcu(struct net_device *dev)
+{
+ struct netdev_nested_priv priv = {
+ .data = NULL,
+ };
+
+ if (sparx5_netdevice_check(dev))
+ return netdev_priv(dev);
+
+ netdev_walk_all_lower_dev_rcu(dev, sparx5_lower_dev_walk, &priv);
+
+ return priv.data;
+}
+
+static struct sparx5_port *sparx5_port_dev_lower_find(struct net_device *dev)
+{
+ struct sparx5_port *port;
+
+ rcu_read_lock();
+ port = sparx5_port_dev_lower_find_rcu(dev);
+ rcu_read_unlock();
+
+ return port;
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_alloc(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key,
+ struct sparx5_rr_router_leg *leg)
+{
+ struct sparx5_rr_neigh_entry *entry;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return NULL;
+
+ memcpy(&entry->key, key, sizeof(*key));
+ entry->vmid = leg->vmid;
+ entry->leg = leg;
+
+ switch (key->iaddr.version) {
+ case SPARX5_IPV4:
+ entry->neigh_tbl = &arp_tbl;
+ break;
+ case SPARX5_IPV6:
+#if IS_ENABLED(CONFIG_IPV6)
+ entry->neigh_tbl = &nd_tbl;
+ break;
+#else
+ kfree(entry);
+ return NULL;
+#endif
+ }
+
+ INIT_LIST_HEAD(&entry->nexthop_list);
+ INIT_LIST_HEAD(&entry->leg_list_node);
+
+ return entry;
+}
+
+static struct sparx5_rr_neigh_entry *
+sparx5_rr_neigh_entry_create(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_key *key)
+{
+ struct sparx5_rr_neigh_entry *entry;
+ struct sparx5_rr_router_leg *leg;
+ struct sparx5_port *port_below;
+ int err;
+
+ port_below = sparx5_port_dev_lower_find(key->dev);
+ if (!port_below)
+ return ERR_PTR(-EINVAL);
+
+ leg = sparx5_rr_leg_find_by_dev(sparx5, key->dev);
+ if (!leg)
+ return ERR_PTR(-EINVAL);
+
+ entry = sparx5_rr_neigh_entry_alloc(sparx5, key, leg);
+ if (!entry)
+ return ERR_PTR(-ENOMEM);
+
+ err = sparx5_rr_neigh_entry_insert(sparx5, entry);
+ if (err)
+ goto err_insert;
+
+ list_add(&entry->leg_list_node, &leg->neigh_list);
+
+ netdev_hold(entry->key.dev, &entry->dev_tracker, GFP_KERNEL);
+
+ return entry;
+
+err_insert:
+ kfree(entry);
+ return ERR_PTR(err);
+}
+
+static int sparx5_rr_nexthop_neigh_init(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg,
+ struct sparx5_rr_nexthop *nh)
+{
+ struct sparx5_rr_neigh_entry *neigh_entry;
+ struct net_device *dev = leg->dev;
+ struct sparx5_rr_neigh_key key;
+ struct neighbour *n;
+ int err = 0;
+
+ if (!nh->gateway || nh->neigh_entry || !nh->neigh_tbl)
+ return 0;
+
+ /* Look up neighbor in the global neighbor table. Takes ref to n. */
+ n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, dev);
+ if (!n) {
+ n = neigh_create(nh->neigh_tbl, &nh->gw_addr, dev);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+ /* Start arp process */
+ neigh_event_send(n, NULL);
+ }
+
+ sparx5_rr_nb2neigh_key(n, &key);
+
+ neigh_entry = sparx5_rr_neigh_entry_lookup(sparx5, &key);
+ if (!neigh_entry) {
+ neigh_entry = sparx5_rr_neigh_entry_create(sparx5, &key);
+ if (IS_ERR(neigh_entry)) {
+ err = PTR_ERR(neigh_entry);
+ goto out;
+ }
+ }
+
+ nh->neigh_entry = neigh_entry;
+ list_add_tail(&nh->neigh_list_node, &neigh_entry->nexthop_list);
+
+out:
+ neigh_release(n);
+ return err;
+}
+
+static void sparx5_rr_neigh_entry_destroy(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ WARN_ON(entry->hw_route.vrule_id_valid);
+
+ if (entry->leg)
+ list_del(&entry->leg_list_node);
+
+ sparx5_rr_neigh_entry_offload_mark(entry, false);
+ sparx5_rr_neigh_entry_remove(sparx5, entry);
+ netdev_put(entry->key.dev, &entry->dev_tracker);
+
+ kfree(entry);
+}
+
+static void sparx5_rr_neigh_entry_put(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *neigh_entry)
+{
+ if (neigh_entry && list_empty(&neigh_entry->nexthop_list) &&
+ !neigh_entry->hw_route.vrule_id_valid)
+ sparx5_rr_neigh_entry_destroy(sparx5, neigh_entry);
+}
+
+static void sparx5_rr_nexthop_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop *nh)
+{
+ struct sparx5_rr_neigh_entry *neigh_entry = nh->neigh_entry;
+
+ if (neigh_entry) {
+ list_del(&nh->neigh_list_node);
+ sparx5_rr_neigh_entry_put(sparx5, neigh_entry);
+ }
+
+ nh->neigh_entry = NULL;
+}
+
+static int sparx5_rr_nexthop_init(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp,
+ struct sparx5_rr_nexthop *nh,
+ struct fib_nh_common *fnhc)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ nh->ifindex = -1;
+ nh->grp = nh_grp;
+ nh->gateway = fnhc->nhc_gw_family != 0;
+ nh->trapped = false;
+ nh->neigh_entry = NULL;
+ nh->neigh_tbl = NULL;
+
+ memset(&nh->gw_addr, 0, sizeof(nh->gw_addr));
+
+ if (!nh->gateway)
+ return 0;
+
+ switch (fnhc->nhc_gw_family) {
+ case AF_INET:
+ nh->gw_addr.version = SPARX5_IPV4;
+ nh->gw_addr.ipv4 = fnhc->nhc_gw.ipv4;
+ nh->neigh_tbl = &arp_tbl;
+ break;
+ case AF_INET6:
+ nh->gw_addr.version = SPARX5_IPV6;
+ nh->gw_addr.ipv6 = fnhc->nhc_gw.ipv6;
+#if IS_ENABLED(CONFIG_IPV6)
+ nh->neigh_tbl = &nd_tbl;
+ break;
+#else
+ return -EINVAL;
+#endif
+ default:
+ WARN_ON_ONCE(1); /* BUG */
+ return 0;
+ }
+
+ /* Blackhole route nexthops have no egress device. */
+ if (!fnhc->nhc_dev)
+ return 0;
+
+ nh->ifindex = fnhc->nhc_dev->ifindex;
+
+ /* When a router leg is removed, all the nexthops with gateway IPs in a
+ * subnet governed by the leg will receive fib delete events. However,
+ * these delete events are received one by one. Therefore, this nexthop
+ * init could have been triggered by a group resize action for such an
+ * event, where the underlying leg is already removed.
+ *
+ * This is not an error. We handle this during offloading by
+ * trapping nexthops which do not have a neigh_entry. As fib deletion
+ * events are processed, we converge to the proper state.
+ */
+ leg = sparx5_rr_leg_find_by_dev(sparx5, fnhc->nhc_dev);
+ if (!leg)
+ return 0;
+
+ return sparx5_rr_nexthop_neigh_init(sparx5, leg, nh);
+}
+
+static int
+sparx5_rr_nexthop_group_info_init(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp,
+ struct sparx5_rr_fib_info *fi)
+{
+ unsigned int nhs = sparx5_rr_fib_info_nhs(fi);
+ struct sparx5_rr_nexthop_group_info *nhgi;
+ struct sparx5_rr_nexthop *nh;
+ int err, i;
+
+ nhgi = kzalloc_flex(*nhgi, nexthops, nhs);
+ if (!nhgi)
+ return -ENOMEM;
+
+ nh_grp->nhgi = nhgi;
+ nhgi->grp = nh_grp;
+ nhgi->atbl_offset_valid = false;
+ nhgi->atbl_offset = 0;
+ nhgi->count = nhs;
+
+ for (i = 0; i < nhgi->count; i++) {
+ struct fib_nh_common *fnhc;
+
+ nh = &nhgi->nexthops[i];
+ fnhc = sparx5_rr_fib_info_nhc(fi, i);
+ err = sparx5_rr_nexthop_init(sparx5, nh_grp, nh, fnhc);
+ if (err)
+ goto err_nexthop_init;
+ }
+
+ return 0;
+
+err_nexthop_init:
+ for (i--; i >= 0; i--) {
+ nh = &nhgi->nexthops[i];
+ sparx5_rr_nexthop_deinit(sparx5, nh);
+ }
+ kfree(nhgi);
+ return err;
+}
+
+static void
+sparx5_rr_nexthop_group_info_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = nh_grp->nhgi;
+ struct sparx5_rr_nexthop *nh;
+ int i;
+
+ WARN_ON(!nhgi->count);
+ WARN_ON_ONCE(nhgi->atbl_offset_valid);
+
+ for (i = nhgi->count - 1; i >= 0; i--) {
+ nh = &nhgi->nexthops[i];
+
+ sparx5_rr_nexthop_deinit(sparx5, nh);
+ }
+
+ kfree(nhgi);
+}
+
+static void sparx5_rr_arp_tbl_hw_addr_apply(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN],
+ u16 evmid, int offset)
+{
+ u32 mac_msb, mac_lsb;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ spx5_rmw(ANA_L3_ARP_CFG_0_MAC_MSB_SET(mac_msb) |
+ ANA_L3_ARP_CFG_0_ARP_VMID_SET(evmid) |
+ ANA_L3_ARP_CFG_0_ARP_ENA_SET(1),
+ ANA_L3_ARP_CFG_0_ARP_ENA |
+ ANA_L3_ARP_CFG_0_ARP_VMID |
+ ANA_L3_ARP_CFG_0_MAC_MSB,
+ sparx5, ANA_L3_ARP_CFG_0(offset));
+
+ spx5_wr(mac_lsb, sparx5, ANA_L3_ARP_CFG_1(offset));
+}
+
+static void sparx5_rr_arp_tbl_hw_addr_clear(struct sparx5 *sparx5, int offset)
+{
+ spx5_rmw(ANA_L3_ARP_CFG_0_ARP_ENA_SET(0), ANA_L3_ARP_CFG_0_ARP_ENA,
+ sparx5, ANA_L3_ARP_CFG_0(offset));
+}
+
+static void
+sparx5_rr_nh_grp_arp_tbl_grp_clear(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ int offset = nh_grp->nhgi->atbl_offset;
+
+ if (nh_grp->nhgi->atbl_offset_valid) {
+ for (u8 i = 0; i < nh_grp->nhgi->count; i++)
+ sparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);
+ sparx5_rr_arp_tbl_grp_free(sparx5, nh_grp->nhgi->count,
+ offset);
+ }
+ nh_grp->nhgi->atbl_offset_valid = false;
+}
+
+static void sparx5_rr_nexthop_group_put(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop_group *nh_grp)
+{
+ sparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, nh_grp);
+ sparx5_rr_nexthop_group_info_deinit(sparx5, nh_grp);
+ kfree(nh_grp);
+}
+
+static struct sparx5_rr_nexthop_group *
+sparx5_rr_nexthop_group_create(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group *nh_grp;
+ int err;
+
+ nh_grp = kzalloc_obj(*nh_grp);
+ if (!nh_grp)
+ return ERR_PTR(-ENOMEM);
+
+ err = sparx5_rr_nexthop_group_info_init(sparx5, nh_grp, &fib_entry->fi);
+ if (err)
+ goto err_group_info_init;
+
+ return nh_grp;
+
+err_group_info_init:
+ kfree(nh_grp);
+ return ERR_PTR(err);
+}
+
+static enum sparx5_rr_fib_type sparx5_rr_rtm_type2fib_type(u8 type)
+{
+ switch (type) {
+ case RTN_UNICAST:
+ return SPARX5_RR_FIB_TYPE_UNICAST;
+ case RTN_LOCAL:
+ return SPARX5_RR_FIB_TYPE_LOCAL;
+ case RTN_MULTICAST:
+ return SPARX5_RR_FIB_TYPE_MULTICAST;
+ case RTN_BLACKHOLE:
+ return SPARX5_RR_FIB_TYPE_BLACKHOLE;
+ case RTN_PROHIBIT:
+ return SPARX5_RR_FIB_TYPE_PROHIBIT;
+ case RTN_UNREACHABLE:
+ return SPARX5_RR_FIB_TYPE_UNREACHABLE;
+ default:
+ return SPARX5_RR_FIB_TYPE_INVALID;
+ }
+}
+
+static void
+sparx5_rr_fib_entry_fib4_info_set(struct sparx5_rr_fib_entry *fib_entry,
+ struct fib_entry_notifier_info *fen4_info)
+{
+ /* Prevent the fib_info from being deleted while we store the
+ * fen_info
+ */
+ fib_info_hold(fen4_info->fi);
+ memcpy(&fib_entry->fi.fen4_info, fen4_info, sizeof(*fen4_info));
+}
+
+static int
+sparx5_rr_fib_entry_fib6_info_add(struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib6_entry_info *fib6_info)
+{
+ struct sparx5_rr_fib6_entry_info *f6i = &fib_entry->fi.fe6_info;
+ unsigned int old_ntr6, new_ntr6;
+ struct fib6_info **rt_arr;
+
+ old_ntr6 = f6i->nrt6;
+ new_ntr6 = old_ntr6 + fib6_info->nrt6;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, new_ntr6);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ /* Copy existing */
+ for (int i = 0; i < old_ntr6; i++)
+ rt_arr[i] = f6i->rt_arr[i];
+
+ /* Copy new and hold fib6_info */
+ for (int i = 0; i < fib6_info->nrt6; i++) {
+ struct fib6_info *rt = fib6_info->rt_arr[i];
+
+ rt_arr[old_ntr6 + i] = rt;
+ fib6_info_hold(rt);
+ }
+
+ /* Free old fib6_info */
+ kfree(f6i->rt_arr);
+ f6i->rt_arr = rt_arr;
+ f6i->nrt6 = new_ntr6;
+
+ WARN_ON(!fib_entry->fi.fe6_info.rt_arr);
+ WARN_ON(!fib_entry->fi.fe6_info.nrt6);
+
+ return 0;
+}
+
+static int
+sparx5_rr_fib_entry_fib_info_add(struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib_info *fi)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ /* IPv4 nexthops can not be added/removed piecemeal similar to
+ * IPv6, so this is a replace in practice.
+ */
+ sparx5_rr_fib_entry_fib4_info_set(fib_entry, &fi->fen4_info);
+ return 0;
+ case SPARX5_IPV6:
+ return sparx5_rr_fib_entry_fib6_info_add(fib_entry,
+ &fi->fe6_info);
+ default:
+ WARN_ON(1);
+ return 0;
+ }
+}
+
+static struct sparx5_rr_fib_entry *
+sparx5_rr_fib_entry_create(struct sparx5 *sparx5, struct sparx5_rr_fib_key *key,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_nexthop_group *nh_grp;
+ u8 type = sparx5_rr_fib_info_type(fi);
+ struct sparx5_rr_fib_entry *fib_entry;
+ int err;
+
+ fib_entry = kzalloc_obj(*fib_entry);
+ if (!fib_entry)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&fib_entry->key, key, sizeof(*key));
+ sparx5_rr_fib_info_init(&fib_entry->fi, fi->version);
+ fib_entry->type = sparx5_rr_rtm_type2fib_type(type);
+
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err)
+ goto err_fib_info_set;
+
+ err = sparx5_rr_fib_entry_insert(sparx5, fib_entry);
+ if (err)
+ goto err_fib_entry_insert;
+
+ nh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);
+ if (IS_ERR(nh_grp)) {
+ err = PTR_ERR(nh_grp);
+ goto err_nexthop_group_create;
+ }
+
+ fib_entry->nh_grp = nh_grp;
+ nh_grp->fib_entry = fib_entry;
+
+ list_add(&fib_entry->fib_node, &sparx5->router->fib_list);
+
+ return fib_entry;
+
+err_nexthop_group_create:
+ sparx5_rr_fib_entry_remove(sparx5, fib_entry);
+err_fib_entry_insert:
+ sparx5_rr_fib_info_put(&fib_entry->fi);
+err_fib_info_set:
+ kfree(fib_entry);
+
+ return ERR_PTR(err);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void
+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib6_entry_info *fen6_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+ for (int i = 0; i < fen6_info->nrt6; i++)
+ fib6_info_hw_flags_set(&init_net, fen6_info->rt_arr[i], offload,
+ trap, offload_failed);
+}
+#else
+static void
+sparx5_rr_fib6_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib6_entry_info *fen6_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+}
+#endif
+
+static void
+sparx5_rr_fib4_entry_offload_mark(struct sparx5 *sparx5,
+ struct fib_entry_notifier_info *fen4_info,
+ bool offload,
+ bool trap,
+ bool offload_failed)
+{
+ struct fib_rt_info fri;
+
+ fri.fi = fen4_info->fi;
+ fri.tb_id = fen4_info->tb_id;
+ fri.dst = cpu_to_be32(fen4_info->dst);
+ fri.dst_len = fen4_info->dst_len;
+ fri.dscp = fen4_info->dscp;
+ fri.type = fen4_info->type;
+ fri.offload = offload;
+ fri.trap = trap;
+ fri.offload_failed = offload_failed;
+
+ fib_alias_hw_flags_set(&init_net, &fri);
+}
+
+static void sparx5_rr_fib_info_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi,
+ bool offload, bool trap,
+ bool offload_failed)
+{
+ switch (fi->version) {
+ case SPARX5_IPV4:
+ return sparx5_rr_fib4_entry_offload_mark(sparx5,
+ &fi->fen4_info,
+ offload, trap,
+ offload_failed);
+ case SPARX5_IPV6:
+ return sparx5_rr_fib6_entry_offload_mark(sparx5,
+ &fi->fe6_info,
+ offload, trap,
+ offload_failed);
+ }
+}
+
+static void
+sparx5_rr_fib_entry_offload_mark(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ bool offload, trap, offload_failed;
+
+ offload_failed = fib_entry->offload_fail;
+ offload = !fib_entry->offload_fail;
+ trap = !fib_entry->offload_fail && fib_entry->trap;
+
+ sparx5_rr_fib_info_offload_mark(sparx5, &fib_entry->fi, offload, trap,
+ offload_failed);
+}
+
+static int
+sparx5_rr_lpm_arp_entry_create(struct sparx5 *sparx5,
+ struct sparx5_iaddr *addr,
+ u32 prefix_len, unsigned char mac[ETH_ALEN],
+ u16 evmid, struct sparx5_rr_hw_route *hw_route)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 priority = LPM_SORT_KEY(prefix_len);
+ struct vcap_rule *rule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ rule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,
+ VCAP_USER_L3, priority, 0);
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
+
+ err = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);
+ err |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);
+ err |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_MSB, mac_msb);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_MAC_LSB, mac_lsb);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_VMID, evmid);
+ err |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_ENA, VCAP_BIT_1);
+
+ err = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));
+ if (!err) {
+ hw_route->vrule_id = rule->id;
+ hw_route->vrule_id_valid = true;
+ }
+ vcap_free_rule(rule);
+ return err;
+}
+
+static int sparx5_rr_lpm_arp_entry_mod(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN], u16 evmid,
+ u32 vrule_id)
+{
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ struct vcap_rule *vrule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ vrule = vcap_get_rule(vctrl, vrule_id);
+ if (IS_ERR(vrule))
+ return -EINVAL;
+
+ err = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB, mac_msb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB, mac_lsb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_ARP_VMID, evmid);
+ err |= vcap_rule_mod_action_bit(vrule, VCAP_AF_ARP_ENA, VCAP_BIT_1);
+
+ err = err ? -EINVAL : vcap_mod_rule(vrule);
+
+ vcap_free_rule(vrule);
+ return err;
+}
+
+static int
+sparx5_rr_fib_entry_update_arp_entry(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry,
+ unsigned char mac[ETH_ALEN], u16 evmid)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 vrule_id = fib_entry->hw_route.vrule_id;
+ struct vcap_rule *vrule;
+ u32 mac_msb, mac_lsb;
+ int err;
+
+ sparx5_rr_split_mac(mac, 32, &mac_msb, &mac_lsb);
+
+ vrule = vcap_get_rule(vctrl, vrule_id);
+ if (IS_ERR(vrule)) {
+ fib_entry->hw_route.vrule_id_valid = false;
+ return PTR_ERR(vrule);
+ }
+
+ switch (vrule->actionset) {
+ case VCAP_AFS_ARP_ENTRY:
+ err = vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_MSB,
+ mac_msb);
+ err |= vcap_rule_mod_action_u32(vrule, VCAP_AF_MAC_LSB,
+ mac_lsb);
+
+ err = err ? -EINVAL : vcap_mod_rule(vrule);
+ goto free_rule;
+ case VCAP_AFS_ARP_PTR:
+ /* Convert arp_ptr to arp_entry */
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, evmid,
+ &fib_entry->hw_route);
+ if (err)
+ goto free_rule;
+
+ sparx5_rr_nh_grp_arp_tbl_grp_clear(sparx5, fib_entry->nh_grp);
+ err = vcap_del_rule(vctrl, pdev, vrule_id);
+ goto free_rule;
+ default:
+ err = -EINVAL;
+ WARN_ON(1); /* BUG */
+ }
+
+free_rule:
+ vcap_free_rule(vrule);
+
+ return err;
+}
+
+static int sparx5_rr_lpm_arp_ptr_create(struct sparx5 *sparx5,
+ struct sparx5_iaddr *addr,
+ u32 prefix_len, u32 arp_offset_addr,
+ u8 ecmp_size,
+ struct sparx5_rr_hw_route *hw_route)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ u32 priority = LPM_SORT_KEY(prefix_len);
+ struct vcap_rule *rule;
+ int err;
+
+ rule = vcap_alloc_rule(vctrl, pdev, VCAP_CID_PREROUTING_L0,
+ VCAP_USER_L3, priority, 0);
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
+
+ err = sparx5_rr_lpm_rule_xip_add(rule, addr, prefix_len);
+ err |= vcap_rule_add_key_u32(rule, VCAP_KF_AFFIX, 0, 0);
+ err |= vcap_rule_add_key_bit(rule, VCAP_KF_DST_FLAG, VCAP_BIT_1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ARP_PTR, arp_offset_addr);
+ err |= vcap_rule_add_action_bit(rule, VCAP_AF_ARP_PTR_REMAP_ENA,
+ VCAP_BIT_0);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_ECMP_CNT, ecmp_size - 1);
+ err |= vcap_rule_add_action_u32(rule, VCAP_AF_RGID, 0);
+
+ err = err ? -EINVAL : vcap_val_add_rule(rule, LPM_PROTO(addr));
+ if (!err) {
+ hw_route->vrule_id_valid = true;
+ hw_route->vrule_id = rule->id;
+ }
+
+ vcap_free_rule(rule);
+ return err;
+}
+
+/* Get egress mac and vmid for nexthop. */
+static void sparx5_rr_nexthop_egress_derive(struct sparx5_rr_nexthop *nh,
+ u8 *mac, u16 *vmid)
+{
+ struct sparx5_rr_neigh_entry *nh_neigh = nh->neigh_entry;
+
+ nh->trapped = !nh_neigh || is_zero_ether_addr(nh_neigh->hwaddr);
+
+ if (nh_neigh) {
+ memcpy(mac, nh_neigh->hwaddr, ETH_ALEN);
+ *vmid = nh_neigh->vmid;
+ return;
+ }
+
+ eth_zero_addr(mac);
+ *vmid = 0;
+}
+
+static int
+sparx5_rr_fib_entry_ecmp_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = fib_entry->nh_grp->nhgi;
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_rr_nexthop *nh;
+ int err, i, offset;
+ u16 vmid;
+
+ offset = sparx5_rr_arp_tbl_grp_alloc(sparx5, nhgi->count);
+
+ if (offset < 0) {
+ fib_entry->offload_fail = true;
+ return offset;
+ }
+
+ for (i = 0; i < nhgi->count; i++) {
+ nh = &nhgi->nexthops[i];
+
+ sparx5_rr_nexthop_egress_derive(nh, mac, &vmid);
+
+ sparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, offset + i);
+ }
+
+ err = sparx5_rr_lpm_arp_ptr_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len, offset,
+ nhgi->count, &fib_entry->hw_route);
+ if (err)
+ goto err_arp_ptr_create;
+
+ nhgi->atbl_offset = offset;
+ nhgi->atbl_offset_valid = true;
+ fib_entry->offload_fail = false;
+
+ return 0;
+
+err_arp_ptr_create:
+ for (i--; i >= 0; i--)
+ sparx5_rr_arp_tbl_hw_addr_clear(sparx5, offset + i);
+ sparx5_rr_arp_tbl_grp_free(sparx5, nhgi->count, offset);
+ fib_entry->offload_fail = true;
+ nhgi->atbl_offset_valid = false;
+
+ return err;
+}
+
+static int
+sparx5_rr_fib_blackhole_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ unsigned char mac[ETH_ALEN];
+
+ /* Hardware blackholes are implemented by:
+ *
+ * 1) Making sure traffic is not trapped with non-zero dmac.
+ * 2) Using reserved router leg vmid for egress.
+ * 3) This router leg is attached to a VLAN id > 4095.
+ * 4) The port-mask for this VLAN is all zero.
+ *
+ * The hardware VLAN table has more than 4096 entries. The specific
+ * size depends on the chip. LAN969x has 4608 and Sparx5 has 5120
+ * entries. These additional VLAN entries can be used for internal
+ * logic.
+ *
+ * The port-mask for the blackhole VLAN is zero. Therefore, frames
+ * routed to the blackhole leg will not egress on any ports.
+ */
+ eth_zero_addr(mac);
+ mac[5] = 0xff;
+
+ return sparx5_rr_lpm_arp_entry_create(sparx5, &fib_entry->key.addr,
+ fib_entry->key.prefix_len, mac,
+ SPARX5_BLACKHOLE_VMID(sparx5),
+ &fib_entry->hw_route);
+}
+
+static int sparx5_rr_fib_entry_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct sparx5_rr_nexthop_group_info *nhgi = fib_entry->nh_grp->nhgi;
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_rr_nexthop *nh;
+ int err = 0;
+ u16 vmid;
+
+ switch (fib_entry->type) {
+ case SPARX5_RR_FIB_TYPE_UNREACHABLE:
+ fallthrough;
+ case SPARX5_RR_FIB_TYPE_PROHIBIT:
+ /* Ensure kernel can respond with correct ICMP packets. */
+ fallthrough;
+ case SPARX5_RR_FIB_TYPE_LOCAL:
+ /* Trap traffic destined for device itself, to ensure
+ * device can receive traffic even when default gateways are
+ * configured.
+ */
+ fib_entry->trap = true;
+
+ /* Trap frames with zero mac, VMID does not matter */
+ eth_zero_addr(mac);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, 0,
+ &fib_entry->hw_route);
+ goto out;
+
+ case SPARX5_RR_FIB_TYPE_UNICAST:
+ fib_entry->trap = false;
+
+ if (!nhgi->nexthops->gateway) {
+ /* Directly connected subnet. Trap traffic so kernel
+ * can perform ARP/NDP on our behalf.
+ */
+ eth_zero_addr(mac);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, 0,
+ &fib_entry->hw_route);
+ goto out;
+ }
+
+ if (nhgi->count == 1) { /* Use arp_entry */
+ nh = &nhgi->nexthops[0];
+ sparx5_rr_nexthop_egress_derive(nh, mac, &vmid);
+ err = sparx5_rr_lpm_arp_entry_create(sparx5,
+ &fib_entry->key.addr,
+ fib_entry->key.prefix_len,
+ mac, vmid,
+ &fib_entry->hw_route);
+ goto out;
+ }
+
+ /* Multiple nexthops so we use the HW arp table. */
+ err = sparx5_rr_fib_entry_ecmp_hw_apply(sparx5,
+ fib_entry);
+ goto out;
+
+ case SPARX5_RR_FIB_TYPE_BLACKHOLE:
+ fib_entry->trap = false;
+ err = sparx5_rr_fib_blackhole_hw_apply(sparx5, fib_entry);
+ goto out;
+
+ default:
+ dev_warn(sparx5->dev, "Fib entry offload, unhandled type=%d\n",
+ fib_entry->type);
+ return -EINVAL;
+ }
+
+out:
+ fib_entry->offload_fail = !!err;
+
+ return err;
+}
+
+static void sparx5_rr_nexthop_neigh_update(struct sparx5 *sparx5,
+ struct sparx5_rr_nexthop *nh,
+ bool entry_connected)
+{
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ int err, nh_offset, grp_idx;
+ u16 vmid;
+
+ if (!nh->gateway)
+ return;
+
+ vmid = nh->neigh_entry->vmid;
+
+ /* Trap traffic with zero mac */
+ eth_zero_addr(mac);
+
+ if (entry_connected)
+ ether_addr_copy(mac, nh->neigh_entry->hwaddr);
+
+ if (nh->trapped && !entry_connected)
+ return;
+
+ nh->trapped = !entry_connected;
+
+ if (nh->grp->nhgi->count == 1) {
+ err = sparx5_rr_fib_entry_update_arp_entry(sparx5,
+ nh->grp->fib_entry,
+ mac, vmid);
+ if (err)
+ dev_err(sparx5->dev,
+ "Nexthop fib entry update failed\n");
+
+ return;
+ }
+
+ if (!nh->grp->nhgi->atbl_offset_valid)
+ return;
+
+ nh_offset = (int)(ptrdiff_t)(nh - nh->grp->nhgi->nexthops);
+ grp_idx = nh->grp->nhgi->atbl_offset;
+
+ sparx5_rr_arp_tbl_hw_addr_apply(sparx5, mac, vmid, grp_idx + nh_offset);
+}
+
+static void
+sparx5_rr_nexthops_update_notify(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *neigh_entry,
+ bool entry_connected)
+{
+ struct sparx5_rr_nexthop *nh;
+
+ list_for_each_entry(nh, &neigh_entry->nexthop_list, neigh_list_node)
+ sparx5_rr_nexthop_neigh_update(sparx5, nh, entry_connected);
+}
+
+static int sparx5_rr_neigh_entry_hw_apply(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry)
+{
+ u32 prefix_len = SPARX5_IADDR_LEN(entry->key.iaddr.version);
+
+ if (!entry->hw_route.vrule_id_valid)
+ return sparx5_rr_lpm_arp_entry_create(sparx5,
+ &entry->key.iaddr,
+ prefix_len,
+ entry->hwaddr,
+ entry->vmid,
+ &entry->hw_route);
+
+ return sparx5_rr_lpm_arp_entry_mod(sparx5, entry->hwaddr, entry->vmid,
+ entry->hw_route.vrule_id);
+}
+
+static void sparx5_rr_neigh_entry_update(struct sparx5 *sparx5,
+ struct sparx5_rr_neigh_entry *entry,
+ bool adding)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ bool offloaded = adding;
+ int err;
+
+ if (!adding && !entry->connected && !entry->hw_route.vrule_id_valid)
+ return;
+
+ entry->connected = adding;
+
+ if (adding) {
+ err = sparx5_rr_neigh_entry_hw_apply(sparx5, entry);
+ if (err)
+ offloaded = false;
+ } else if (entry->hw_route.vrule_id_valid) {
+ vcap_del_rule(sparx5->vcap_ctrl, pdev,
+ entry->hw_route.vrule_id);
+ entry->hw_route.vrule_id_valid = false;
+ }
+
+ return sparx5_rr_neigh_entry_offload_mark(entry, offloaded);
+}
+
+static void sparx5_rr_fib_entry_destroy(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+
+ list_del(&fib_entry->fib_node);
+ sparx5_rr_fib_entry_remove(sparx5, fib_entry);
+ sparx5_rr_nexthop_group_put(sparx5, fib_entry->nh_grp);
+ if (fib_entry->hw_route.vrule_id_valid)
+ vcap_del_rule(vctrl, pdev, fib_entry->hw_route.vrule_id);
+ sparx5_rr_fib_info_put(&fib_entry->fi);
+ kfree(fib_entry);
+}
+
+/* Update nexthop group based on current fib_info state. */
+static int
+sparx5_rr_entry_nexthop_group_update(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry)
+{
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+ struct sparx5_rr_nexthop_group *new_nh_grp;
+ struct sparx5_rr_nexthop_group *old_nh_grp;
+ bool old_vrule_id_valid;
+ u32 old_vrule_id;
+ int err;
+
+ old_nh_grp = fib_entry->nh_grp;
+ old_vrule_id = fib_entry->hw_route.vrule_id;
+ old_vrule_id_valid = fib_entry->hw_route.vrule_id_valid;
+
+ /* Prepare new group in SW representation */
+ new_nh_grp = sparx5_rr_nexthop_group_create(sparx5, fib_entry);
+ if (IS_ERR(new_nh_grp)) {
+ dev_warn(sparx5->dev, "Failed to create nexthop group\n");
+ return PTR_ERR(new_nh_grp);
+ }
+
+ fib_entry->nh_grp = new_nh_grp;
+ new_nh_grp->fib_entry = fib_entry;
+
+ /* Write new rule to HW */
+ err = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);
+ if (err)
+ goto hw_apply_err;
+
+ /* Clean up old rule and start routing traffic according to new rule */
+ if (old_vrule_id_valid && fib_entry->hw_route.vrule_id != old_vrule_id)
+ vcap_del_rule(vctrl, pdev, old_vrule_id);
+
+ /* Remove old unused group */
+ sparx5_rr_nexthop_group_put(sparx5, old_nh_grp);
+
+ return 0;
+
+hw_apply_err:
+ fib_entry->nh_grp = old_nh_grp;
+ new_nh_grp->fib_entry = NULL;
+ sparx5_rr_nexthop_group_put(sparx5, new_nh_grp);
+ return err;
+}
+
+static void sparx5_rr_leg_hw_init(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg)
+{
+ /* Associate Router leg VMID to VLAN */
+ spx5_rmw(ANA_L3_VMID_CFG_VMID_SET(leg->vmid), ANA_L3_VMID_CFG_VMID,
+ sparx5, ANA_L3_VMID_CFG(leg->vid));
+
+ /* Enable Router leg for VLAN */
+ spx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(1),
+ ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,
+ ANA_L3_VLAN_CFG(leg->vid));
+
+ /* Configure router leg */
+
+#if IS_ENABLED(CONFIG_IPV6)
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |
+ ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg->vid) |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(1),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_EVID |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+#else
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(1) |
+ ANA_L3_RLEG_CTRL_RLEG_EVID_SET(leg->vid),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_EVID, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+#endif
+
+ /* Configure egress VLAN in rewriter */
+ spx5_rmw(REW_RLEG_CTRL_RLEG_EVID_SET(leg->vid), REW_RLEG_CTRL_RLEG_EVID,
+ sparx5, REW_RLEG_CTRL(leg->vmid));
+}
+
+static void sparx5_rr_leg_hw_deinit(struct sparx5 *sparx5,
+ struct sparx5_rr_router_leg *leg)
+{
+ /* Disable Router leg for VLAN */
+ spx5_rmw(ANA_L3_VLAN_CFG_VLAN_RLEG_ENA_SET(0),
+ ANA_L3_VLAN_CFG_VLAN_RLEG_ENA, sparx5,
+ ANA_L3_VLAN_CFG(leg->vid));
+
+ /* Disable IP UC routing on leg */
+ spx5_rmw(ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA_SET(0) |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA_SET(0),
+ ANA_L3_RLEG_CTRL_RLEG_IP4_UC_ENA |
+ ANA_L3_RLEG_CTRL_RLEG_IP6_UC_ENA, sparx5,
+ ANA_L3_RLEG_CTRL(leg->vmid));
+}
+
+static int sparx5_rr_lpm_link_local_create(struct sparx5 *sparx5)
+{
+ struct sparx5_iaddr addr __aligned(2) = { };
+ unsigned char zero_mac[ETH_ALEN];
+
+ eth_zero_addr(zero_mac);
+
+ /* Trap traffic to fe80::/64 */
+ addr.version = SPARX5_IPV6;
+ addr.ipv6.in6_u.u6_addr8[0] = 0xfe;
+ addr.ipv6.in6_u.u6_addr8[1] = 0x80;
+
+ return sparx5_rr_lpm_arp_entry_create(sparx5, &addr,
+ SPARX5_LINK_LOCAL_PREFIX_LEN,
+ zero_mac, 0,
+ &sparx5->router->link_local);
+}
+
+static void sparx5_rr_lpm_link_local_destroy(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_hw_route *llocal = &sparx5->router->link_local;
+ struct net_device *pdev = sparx5->router->port_dev;
+ struct vcap_control *vctrl = sparx5->vcap_ctrl;
+
+ if (!llocal->vrule_id_valid)
+ return;
+
+ vcap_del_rule(vctrl, pdev, llocal->vrule_id);
+ llocal->vrule_id_valid = false;
+}
+
+static struct sparx5_rr_router_leg *
+__sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vmid,
+ u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ leg = kzalloc_obj(*leg);
+ if (!leg)
+ return NULL;
+
+ INIT_LIST_HEAD(&leg->leg_list_node);
+ INIT_LIST_HEAD(&leg->neigh_list);
+ leg->dev = dev;
+ leg->vmid = vmid;
+ leg->vid = vid;
+ leg->sparx5 = sparx5;
+
+ return leg;
+}
+
+/* Router legs are identified by their VMID in hw */
+static struct sparx5_rr_router_leg *
+sparx5_rr_leg_alloc(struct sparx5 *sparx5, struct net_device *dev, u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+ int next_vmid;
+
+ next_vmid = sparx5_vmid_alloc(sparx5);
+ if (next_vmid < 0)
+ return NULL;
+
+ leg = __sparx5_rr_leg_alloc(sparx5, dev, next_vmid, vid);
+ if (!leg)
+ goto err_kzalloc;
+
+ return leg;
+
+err_kzalloc:
+ sparx5_vmid_free(sparx5, next_vmid);
+
+ return NULL;
+}
+
+static void sparx5_rr_router_leg_destroy(struct sparx5_rr_router_leg *leg)
+{
+ struct sparx5_rr_neigh_entry *neigh, *neigh_tmp;
+ struct sparx5 *sparx5 = leg->sparx5;
+
+ dev_dbg(sparx5->dev, "Leg destroy vid=%u vmid=%u dev=%s\n", leg->vid,
+ leg->vmid, leg->dev ? netdev_name(leg->dev) : "blackhole");
+
+ list_for_each_entry_safe(neigh, neigh_tmp, &leg->neigh_list,
+ leg_list_node) {
+ struct sparx5_rr_nexthop *nh, *nh_tmp;
+
+ list_for_each_entry_safe(nh, nh_tmp, &neigh->nexthop_list,
+ neigh_list_node) {
+ sparx5_rr_nexthop_neigh_update(sparx5, nh, false);
+ list_del(&nh->neigh_list_node);
+ nh->neigh_entry = NULL;
+ }
+
+ sparx5_rr_neigh_entry_update(sparx5, neigh, false);
+ sparx5_rr_neigh_entry_destroy(sparx5, neigh);
+ }
+
+ sparx5_rr_leg_hw_deinit(sparx5, leg);
+ sparx5_vmid_free(leg->sparx5, leg->vmid);
+ list_del(&leg->leg_list_node);
+
+ if (leg->dev) {
+ if (atomic_dec_return(&sparx5->router->legs_count) == 0)
+ sparx5_rr_lpm_link_local_destroy(sparx5);
+
+ netdev_put(leg->dev, &leg->dev_tracker);
+ }
+ kfree(leg);
+}
+
+static struct sparx5_rr_router_leg *
+sparx5_rr_router_leg_create(struct sparx5 *sparx5, struct net_device *dev,
+ u16 vid)
+{
+ struct sparx5_rr_router_leg *leg;
+
+ leg = sparx5_rr_leg_alloc(sparx5, dev, vid);
+ if (!leg)
+ return ERR_PTR(-ENOMEM);
+
+ /* Prevent net device from being freed while we have added it to a
+ * router leg.
+ */
+ netdev_hold(dev, &leg->dev_tracker, GFP_KERNEL);
+
+ /* While a router leg exists, add route to trap link-local traffic. */
+ if (atomic_inc_return(&sparx5->router->legs_count) == 1) {
+ if (sparx5_rr_lpm_link_local_create(sparx5))
+ dev_warn(sparx5->dev,
+ "Failed to create link-local route\n");
+ }
+
+ list_add(&leg->leg_list_node, &sparx5->router->leg_list);
+ sparx5_rr_leg_hw_init(sparx5, leg);
+
+ dev_dbg(sparx5->dev, "Leg create dev=%s vid=%u vmid=%u\n", dev->name,
+ leg->vid, leg->vmid);
+
+ return leg;
+}
+
+static void sparx5_rr_fib4_del(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_key key;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return;
+
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+}
+
+static bool sparx5_rr_dev_real_is_vlan_aware(struct net_device *dev)
+{
+ struct net_device *vlan_rdev;
+ /* Support l3 offloading for:
+ * 1) upper vlan interfaces for the bridge.
+ */
+ if (is_vlan_dev(dev)) {
+ if (netif_is_bridge_port(dev))
+ return false;
+
+ vlan_rdev = vlan_dev_real_dev(dev);
+ if (sparx5_netdevice_check(vlan_rdev))
+ return false;
+
+ return netif_is_bridge_master(vlan_rdev) &&
+ br_vlan_enabled(vlan_rdev) &&
+ sparx5_port_dev_lower_find(vlan_rdev);
+ }
+
+ return false;
+}
+
+static bool sparx5_rr_fib_info_should_offload(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ u32 tb_id = sparx5_rr_fib_info_tb_id(fi);
+ u8 type = sparx5_rr_fib_info_type(fi);
+ int nhs = sparx5_rr_fib_info_nhs(fi);
+
+ if (!(type == RTN_UNICAST ||
+ type == RTN_LOCAL ||
+ type == RTN_BLACKHOLE ||
+ type == RTN_PROHIBIT ||
+ type == RTN_UNREACHABLE))
+ return false;
+
+ if (!(tb_id == RT_TABLE_MAIN ||
+ tb_id == RT_TABLE_LOCAL))
+ return false;
+
+ /* No support for nexthop objects (optimization for larger scale
+ * routing). Instead each route has a copy of it's nexthops.
+ */
+ if (sparx5_rr_fib_info_is_nh_obj(fi))
+ return false;
+
+ /* For IPv4 the nexthops of these route types have NULL egress device.
+ * However, for IPv6 the nexthops use the loopback interface, so accept
+ * early.
+ */
+ if (type == RTN_BLACKHOLE ||
+ type == RTN_PROHIBIT ||
+ type == RTN_UNREACHABLE)
+ return true;
+
+ if (nhs > SPARX5_MAX_ECMP_SIZE)
+ return false;
+
+ for (int i = 0; i < nhs; i++) {
+ struct fib_nh_common *nhc = sparx5_rr_fib_info_nhc(fi, i);
+
+ if (nhc->nhc_dev &&
+ !sparx5_rr_dev_real_is_vlan_aware(nhc->nhc_dev))
+ return false;
+
+ /* HW only supports equal weight nexthops */
+ if (nhc->nhc_weight != 1)
+ return false;
+ }
+
+ return true;
+}
+
+static int sparx5_rr_fib_replace(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ u8 fi_type = sparx5_rr_fib_info_type(fi);
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_info old_fi;
+ struct sparx5_rr_fib_key key;
+ int err = 0;
+
+ if (sparx5_rr_fib_info_should_ignore(fi))
+ return 0;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+
+ if (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {
+ /* A previously offloadable fib, is modified to unoffloadable
+ * state, so we must remove it.
+ */
+ if (fib_entry)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return 0;
+ }
+
+ if (!fib_entry) {
+ /* Holds refs to kernel fib_info */
+ fib_entry = sparx5_rr_fib_entry_create(sparx5, &key, fi);
+ if (IS_ERR(fib_entry)) {
+ dev_warn(sparx5->dev, "Failed to create fib entry\n");
+ sparx5_rr_fib_info_offload_mark(sparx5, fi, false,
+ false, true);
+ return PTR_ERR(fib_entry);
+ }
+
+ err = sparx5_rr_fib_entry_hw_apply(sparx5, fib_entry);
+ goto out_fib_mark_offload;
+ }
+
+ /* Save old fib_info, add new one, then release old. This ordering
+ * ensures fib_entry retains valid fi on allocation failure.
+ */
+ old_fi = fib_entry->fi;
+
+ /* Clear fib_entry fi */
+ sparx5_rr_fib_info_init(&fib_entry->fi, fi->version);
+
+ /* Hold and replace with new fib_info */
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err) {
+ fib_entry->fi = old_fi;
+ dev_err(sparx5->dev, "Failed to replace fib info\n");
+ sparx5_rr_fib_info_offload_mark(sparx5, fi, false, false, true);
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return err;
+ }
+
+ /* Release and allow any previous fib_info to be deleted */
+ sparx5_rr_fib_info_put(&old_fi);
+
+ fib_entry->type = sparx5_rr_rtm_type2fib_type(fi_type);
+
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+out_fib_mark_offload:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return err;
+}
+
+static void sparx5_rr_fib4_event_work(struct work_struct *work)
+{
+ struct sparx5_fib_event_work *fib_work =
+ container_of(work, struct sparx5_fib_event_work, work);
+ struct sparx5 *sparx5 = fib_work->sparx5;
+ int err;
+
+ mutex_lock(&sparx5->router->lock);
+
+ switch (fib_work->event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ err = sparx5_rr_fib_replace(sparx5, &fib_work->fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB replace failed, ip=%pI4l\n",
+ &fib_work->fi.fen4_info.dst);
+
+ break;
+ case FIB_EVENT_ENTRY_DEL:
+ sparx5_rr_fib4_del(sparx5, &fib_work->fi);
+ break;
+ default:
+ /* FIB_EVENT_ENTRY_APPEND only occurs for IPv6. */
+ WARN_ON_ONCE(1); /* BUG */
+ break;
+ }
+
+ /* Release fib_info hold for workqueue. */
+ sparx5_rr_fib_info_put(&fib_work->fi);
+ mutex_unlock(&sparx5->router->lock);
+ kfree(fib_work);
+}
+
+static int sparx5_rr_fib6_append(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ struct sparx5_rr_fib_key key;
+ int err = 0;
+
+ if (sparx5_rr_fib_info_should_ignore(fi))
+ return 0;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return 0;
+
+ /* Are we adding new nexthops which can not be offloaded */
+ if (!sparx5_rr_fib_info_should_offload(sparx5, fi)) {
+ err = -EINVAL;
+ goto out_fib_mark_offload;
+ }
+
+ /* Append new rt_arr data to fen6_info rt data */
+ err = sparx5_rr_fib_entry_fib_info_add(fib_entry, fi);
+ if (err)
+ goto out_fib_mark_offload;
+
+ /* Realloc nexthop group and apply to hw. */
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+out_fib_mark_offload:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+
+ return err;
+}
+
+static bool sparx5_rr_fib6_rt_exists(struct sparx5_rr_fib6_entry_info *f6i,
+ struct fib6_info *rt)
+{
+ for (int i = 0; i < f6i->nrt6; i++)
+ if (f6i->rt_arr[i] == rt)
+ return true;
+
+ return false;
+}
+
+static int sparx5_rr_fib6_nexthop_prune(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_entry *fib_entry,
+ struct sparx5_rr_fib6_entry_info *f6i)
+{
+ struct fib6_info **old_rt_arr = fib_entry->fi.fe6_info.rt_arr;
+ unsigned int old_nrt6, new_nrt6;
+ struct fib6_info **rt_arr;
+ int j = 0;
+
+ old_nrt6 = fib_entry->fi.fe6_info.nrt6;
+ new_nrt6 = old_nrt6 >= f6i->nrt6 ? old_nrt6 - f6i->nrt6 : 0;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, old_nrt6);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ for (int i = 0; i < old_nrt6; i++) {
+ struct fib6_info *fi = old_rt_arr[i];
+
+ if (sparx5_rr_fib6_rt_exists(f6i, fi)) {
+ sparx5_rr_rt6_release(fi);
+ continue;
+ }
+
+ rt_arr[j++] = fi;
+ }
+
+ WARN_ON_ONCE(j != new_nrt6);
+
+ kfree(fib_entry->fi.fe6_info.rt_arr);
+ fib_entry->fi.fe6_info.nrt6 = j;
+ fib_entry->fi.fe6_info.rt_arr = rt_arr;
+ return 0;
+}
+
+static int sparx5_rr_fib6_del(struct sparx5 *sparx5,
+ struct sparx5_rr_fib_info *fi)
+{
+ struct sparx5_rr_fib_entry *fib_entry;
+ int nhs = sparx5_rr_fib_info_nhs(fi);
+ struct sparx5_rr_fib_key key;
+ int err;
+
+ sparx5_rr_fib_info_to_fib_key(fi, &key);
+
+ fib_entry = sparx5_rr_fib_entry_lookup(sparx5, &key);
+ if (!fib_entry)
+ return 0;
+
+ /* Full delete. */
+ if (nhs == sparx5_rr_fib_info_nhs(&fib_entry->fi)) {
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+ return 0;
+ }
+
+ /* Partial delete. Remove fi nexthops from fib_entry. */
+ err = sparx5_rr_fib6_nexthop_prune(sparx5, fib_entry, &fi->fe6_info);
+ if (err)
+ goto err_nexthop_prune;
+
+ /* Realloc nexthop group and apply to hw. */
+ err = sparx5_rr_entry_nexthop_group_update(sparx5, fib_entry);
+
+err_nexthop_prune:
+ fib_entry->offload_fail = !!err;
+ sparx5_rr_fib_entry_offload_mark(sparx5, fib_entry);
+ if (err)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+
+ return err;
+}
+
+static void sparx5_rr_fib6_event_work(struct work_struct *work)
+{
+ struct sparx5_fib_event_work *fib_work =
+ container_of(work, struct sparx5_fib_event_work, work);
+ struct sparx5_rr_fib_info *fi = &fib_work->fi;
+ struct sparx5 *sparx5 = fib_work->sparx5;
+ int err;
+
+ mutex_lock(&sparx5->router->lock);
+
+ switch (fib_work->event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ err = sparx5_rr_fib_replace(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 replace failed.\n");
+
+ break;
+
+ case FIB_EVENT_ENTRY_APPEND:
+ /* Netlink API for IPv6 is different from IPV4. It is
+ * possible to do partial update/deletes of nexthops on a
+ * route. In this case fi only contains the nexthops to
+ * add/remove, and must be merged with the existing nexthops
+ * on the route. Therefore, we only share fib_replace between
+ * IPv6 and IPv4 logic.
+ */
+ err = sparx5_rr_fib6_append(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 append failed.\n");
+
+ break;
+
+ case FIB_EVENT_ENTRY_DEL:
+ err = sparx5_rr_fib6_del(sparx5, fi);
+ if (err)
+ dev_warn(sparx5->dev, "FIB 6 delete failed.\n");
+
+ break;
+
+ default:
+ WARN_ON_ONCE(1); /* BUG */
+ break;
+ }
+
+ /* Release fib6_info holds for workqueue. */
+ sparx5_rr_fib_info_put(fi);
+ mutex_unlock(&sparx5->router->lock);
+ kfree(fib_work);
+}
+
+static int sparx5_rr_fib6_work_init(struct sparx5_fib_event_work *fib_work,
+ struct fib6_entry_notifier_info *fen6_info)
+{
+ struct sparx5_rr_fib6_entry_info *fib6_info = &fib_work->fi.fe6_info;
+ struct fib6_info *rt = fen6_info->rt;
+ struct fib6_info **rt_arr;
+ struct fib6_info *iter;
+ unsigned int nrt6;
+ int i = 0;
+
+ nrt6 = fen6_info->nsiblings + 1;
+
+ rt_arr = kzalloc_objs(struct fib6_info *, nrt6, GFP_ATOMIC);
+ if (!rt_arr)
+ return -ENOMEM;
+
+ fib6_info->rt_arr = rt_arr;
+ fib6_info->nrt6 = nrt6;
+
+ rt_arr[0] = rt;
+ fib6_info_hold(rt);
+
+ if (!fen6_info->nsiblings)
+ return 0;
+
+ list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
+ if (i == fen6_info->nsiblings)
+ break;
+
+ rt_arr[i + 1] = iter;
+ fib6_info_hold(iter);
+ i++;
+ }
+
+ return 0;
+}
+
+/* Handle fib events, which manage fib_entries. Called in atomic context, with
+ * rcu_read_lock().
+ */
+static int sparx5_rr_fib_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct fib6_entry_notifier_info *fen6_info;
+ struct fib_entry_notifier_info *fen_info;
+ struct sparx5_fib_event_work *fib_work;
+ struct fib_notifier_info *info = ptr;
+ struct sparx5_router *router;
+ int err;
+
+ /* Handle IPv4 and IPv6 */
+ if (info->family != AF_INET && info->family != AF_INET6)
+ return NOTIFY_DONE;
+
+ if (event != FIB_EVENT_ENTRY_REPLACE &&
+ event != FIB_EVENT_ENTRY_DEL &&
+ event != FIB_EVENT_ENTRY_APPEND)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, fib_nb);
+
+ fib_work = kzalloc_obj(*fib_work, GFP_ATOMIC);
+ if (!fib_work)
+ return NOTIFY_BAD;
+
+ fib_work->sparx5 = router->sparx5;
+ fib_work->event = event;
+
+ switch (info->family) {
+ case AF_INET:
+ INIT_WORK(&fib_work->work, sparx5_rr_fib4_event_work);
+
+ fen_info = container_of(info, struct fib_entry_notifier_info,
+ info);
+ fib_work->fi.fen4_info = *fen_info;
+ fib_work->fi.version = SPARX5_IPV4;
+
+ /* Hold fib_info while item is queued */
+ fib_info_hold(fib_work->fi.fen4_info.fi);
+
+ sparx5_rr_schedule_work(router->sparx5, &fib_work->work);
+ break;
+ case AF_INET6:
+ INIT_WORK(&fib_work->work, sparx5_rr_fib6_event_work);
+
+ /* Copy and hold fib6_info for route and all nhs while item is
+ * queued.
+ */
+ fen6_info = container_of(info, struct fib6_entry_notifier_info,
+ info);
+ err = sparx5_rr_fib6_work_init(fib_work, fen6_info);
+ if (err)
+ goto err_fib6;
+
+ fib_work->fi.version = SPARX5_IPV6;
+
+ sparx5_rr_schedule_work(router->sparx5, &fib_work->work);
+ break;
+ default:
+ goto err_fam_unhandled;
+ }
+
+ return NOTIFY_DONE;
+
+err_fam_unhandled:
+ WARN_ON_ONCE(1); /* BUG */
+err_fib6:
+ kfree(fib_work);
+ return NOTIFY_BAD;
+}
+
+static void sparx5_rr_neigh_event_work(struct work_struct *work)
+{
+ struct sparx5_rr_netevent_work *net_work =
+ container_of(work, struct sparx5_rr_netevent_work, work);
+ unsigned char hwaddr[ETH_ALEN] __aligned(2);
+ struct sparx5 *sparx5 = net_work->sparx5;
+ struct neighbour *n = net_work->neigh;
+ struct sparx5_rr_neigh_key key = { };
+ struct sparx5_rr_neigh_entry *entry;
+ bool entry_connected;
+ u8 nud_state, dead;
+
+ sparx5_rr_nb2neigh_key(n, &key);
+
+ /* Frames with link-local dip are trapped, so ignore the neighbour. */
+ if (key.iaddr.version == SPARX5_IPV6 &&
+ ipv6_addr_type(&key.iaddr.ipv6) & IPV6_ADDR_LINKLOCAL)
+ goto out;
+
+ /* If n changes after this read section, we will get another neigh
+ * event, which is processed after the current one.
+ */
+ read_lock_bh(&n->lock);
+ ether_addr_copy(hwaddr, n->ha);
+ nud_state = n->nud_state;
+ dead = n->dead;
+ read_unlock_bh(&n->lock);
+
+ mutex_lock(&sparx5->router->lock);
+
+ entry_connected = nud_state & NUD_VALID && !dead;
+ entry = sparx5_rr_neigh_entry_lookup(sparx5, &key);
+ if (!entry_connected && !entry)
+ goto out_mutex;
+
+ if (!entry) {
+ entry = sparx5_rr_neigh_entry_create(sparx5, &key);
+ if (IS_ERR(entry))
+ goto out_mutex;
+ }
+
+ if (entry->connected && entry_connected &&
+ ether_addr_equal(entry->hwaddr, hwaddr))
+ goto out_mutex;
+
+ ether_addr_copy(entry->hwaddr, hwaddr);
+ sparx5_rr_neigh_entry_update(sparx5, entry, entry_connected);
+ sparx5_rr_nexthops_update_notify(sparx5, entry, entry_connected);
+ if (!entry_connected)
+ sparx5_rr_neigh_entry_put(sparx5, entry);
+
+out_mutex:
+ mutex_unlock(&sparx5->router->lock);
+out:
+ neigh_release(n);
+ kfree(net_work);
+}
+
+/* Handle neighbour update events. Used to manage neigh_entries. Called in
+ * atomic context, with rcu_read_lock().
+ */
+static int sparx5_rr_netevent_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct sparx5_rr_netevent_work *net_work;
+ struct sparx5_router *router;
+ struct sparx5_port *port;
+ struct neighbour *n;
+
+ router = container_of(nb, struct sparx5_router, netevent_nb);
+
+ switch (event) {
+ case NETEVENT_NEIGH_UPDATE:
+ n = ptr;
+
+ if (!net_eq(dev_net(n->dev), &init_net))
+ return NOTIFY_DONE;
+
+ if (n->tbl->family != AF_INET && n->tbl->family != AF_INET6)
+ return NOTIFY_DONE;
+
+ port = sparx5_port_dev_lower_find(n->dev);
+ if (!port)
+ return NOTIFY_DONE;
+
+ net_work = kzalloc_obj(*net_work, GFP_ATOMIC);
+ if (!net_work)
+ return NOTIFY_BAD;
+
+ INIT_WORK(&net_work->work, sparx5_rr_neigh_event_work);
+ net_work->sparx5 = router->sparx5;
+ net_work->neigh = neigh_clone(n);
+ net_work->event = event;
+ sparx5_rr_schedule_work(router->sparx5, &net_work->work);
+
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_DONE;
+};
+
+static void sparx5_rr_leg_base_mac_set(struct sparx5 *sparx5,
+ unsigned char mac[ETH_ALEN])
+{
+ u8 rleg_type_sel = SPARX5_RLEG_USE_GLOBAL_BASE_MAC;
+ u32 mac_msb, mac_lsb;
+
+ sparx5_rr_split_mac(mac, 24, &mac_msb, &mac_lsb);
+
+ dev_dbg(sparx5->dev, "Router leg base MAC=%pM\n", mac);
+
+ /* The global router leg MAC must be set consistently across ANA_L3, REW
+ * and EACL.
+ */
+ spx5_wr(ANA_L3_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ ANA_L3_RLEG_CFG_0);
+
+ spx5_rmw(ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_MSB |
+ ANA_L3_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, ANA_L3_RLEG_CFG_1);
+
+ /* Set global Router leg MAC (REW) */
+ spx5_wr(REW_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ REW_RLEG_CFG_0);
+
+ spx5_rmw(REW_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ REW_RLEG_CFG_1_RLEG_MAC_MSB | REW_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, REW_RLEG_CFG_1);
+
+ /* Set global Router leg MAC (EACL) */
+ spx5_wr(EACL_RLEG_CFG_0_RLEG_MAC_LSB_SET(mac_lsb), sparx5,
+ EACL_RLEG_CFG_0);
+
+ spx5_rmw(EACL_RLEG_CFG_1_RLEG_MAC_MSB_SET(mac_msb) |
+ EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL_SET(rleg_type_sel),
+ EACL_RLEG_CFG_1_RLEG_MAC_MSB |
+ EACL_RLEG_CFG_1_RLEG_MAC_TYPE_SEL,
+ sparx5, EACL_RLEG_CFG_1);
+}
+
+static bool
+sparx5_rr_router_leg_addr_list_empty_rcu(struct sparx5_rr_router_leg *leg)
+{
+ struct inet6_dev *inet6_dev;
+ struct in_device *in_dev;
+
+ in_dev = __in_dev_get_rcu(leg->dev);
+ if (in_dev && rcu_access_pointer(in_dev->ifa_list))
+ return false;
+
+ inet6_dev = __in6_dev_get(leg->dev);
+ if (inet6_dev && !list_empty(&inet6_dev->addr_list))
+ return false;
+
+ return true;
+}
+
+static bool
+sparx5_rr_router_leg_addr_list_empty(struct sparx5_rr_router_leg *leg)
+{
+ bool addr_list_empty;
+
+ rcu_read_lock();
+ addr_list_empty = sparx5_rr_router_leg_addr_list_empty_rcu(leg);
+ rcu_read_unlock();
+
+ return addr_list_empty;
+}
+
+static int __sparx5_rr_inetaddr_event(struct sparx5 *sparx5,
+ struct net_device *dev,
+ unsigned long event)
+{
+ struct sparx5_rr_router_leg *leg;
+ u16 vid;
+
+ if (!sparx5_rr_dev_real_is_vlan_aware(dev))
+ return 0;
+
+ /* Our basic case: ip addr/subnet added to vlan upper of
+ * bridge dev.
+ */
+ switch (event) {
+ case NETDEV_UP:
+ leg = sparx5_rr_leg_find_by_dev(sparx5, dev);
+ if (leg)
+ return 0;
+
+ /* HW allows at most 1 leg per VLAN, but we do not need to
+ * lookup leg by vid, since the kernel does not allow multiple
+ * vlan devs with the same vid on top of a given device.
+ */
+ vid = vlan_dev_vlan_id(dev);
+
+ leg = sparx5_rr_router_leg_create(sparx5, dev, vid);
+ if (IS_ERR(leg))
+ return PTR_ERR(leg);
+ break;
+ case NETDEV_DOWN:
+ leg = sparx5_rr_leg_find_by_dev(sparx5, dev);
+ if (!leg || !sparx5_rr_router_leg_addr_list_empty(leg))
+ return 0;
+
+ sparx5_rr_router_leg_destroy(leg);
+ break;
+ }
+
+ return 0;
+}
+
+static int sparx5_rr_inetaddr_event_handle(struct sparx5 *sparx5,
+ struct net_device *dev,
+ unsigned long event)
+{
+ int err;
+
+ if (!net_eq(dev_net(dev), &init_net))
+ return NOTIFY_DONE;
+
+ mutex_lock(&sparx5->router->lock);
+ err = __sparx5_rr_inetaddr_event(sparx5, dev, event);
+ mutex_unlock(&sparx5->router->lock);
+
+ return notifier_from_errno(err);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_inet6addr_valid_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in6_validator_info *i6vi = (struct in6_validator_info *)ptr;
+ struct net_device *dev = i6vi->i6vi_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_UP)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inet6addr_valid_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+static void sparx5_rr_inet6addr_event_work(struct work_struct *work)
+{
+ struct sparx5_rr_inet6addr_event_work *addr_work =
+ container_of(work, struct sparx5_rr_inet6addr_event_work, work);
+ struct sparx5_router *router = addr_work->sparx5->router;
+
+ rtnl_lock();
+ mutex_lock(&router->lock);
+
+ __sparx5_rr_inetaddr_event(addr_work->sparx5, addr_work->dev,
+ addr_work->event);
+
+ mutex_unlock(&router->lock);
+ rtnl_unlock();
+ netdev_put(addr_work->dev, &addr_work->dev_tracker);
+ kfree(addr_work);
+}
+
+/* Called in atomic context. */
+static int sparx5_rr_inet6addr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr;
+ struct sparx5_rr_inet6addr_event_work *work;
+ struct net_device *dev = if6->idev->dev;
+ struct sparx5_router *router;
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ if (!net_eq(dev_net(dev), &init_net))
+ return NOTIFY_DONE;
+
+ work = kzalloc_obj(*work, GFP_ATOMIC);
+ if (!work)
+ return NOTIFY_BAD;
+
+ router = container_of(nb, struct sparx5_router, inet6addr_nb);
+ INIT_WORK(&work->work, sparx5_rr_inet6addr_event_work);
+ work->sparx5 = router->sparx5;
+ work->dev = dev;
+ work->event = event;
+ netdev_hold(dev, &work->dev_tracker, GFP_ATOMIC);
+ sparx5_rr_schedule_work(router->sparx5, &work->work);
+
+ return NOTIFY_DONE;
+}
+
+/* Handle events for ip address changes on ifs. Used to manage router legs.
+ * Called with RTNL.
+ */
+static int sparx5_rr_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct net_device *dev = ifa->ifa_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inetaddr_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_inetaddr_valid_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_validator_info *ivi = (struct in_validator_info *)ptr;
+ struct net_device *dev = ivi->ivi_dev->dev;
+ struct sparx5_router *router;
+
+ ASSERT_RTNL();
+
+ if (event != NETDEV_UP)
+ return NOTIFY_DONE;
+
+ router = container_of(nb, struct sparx5_router, inetaddr_valid_nb);
+
+ return sparx5_rr_inetaddr_event_handle(router->sparx5, dev, event);
+}
+
+/* Called with RTNL. */
+static int sparx5_rr_netdevice_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ struct sparx5_rr_router_leg *leg;
+ struct sparx5_router *router;
+ struct sparx5 *sparx5;
+
+ ASSERT_RTNL();
+
+ if (!net_eq(dev_net(dev), &init_net))
+ return NOTIFY_OK;
+
+ router = container_of(nb, struct sparx5_router, netdevice_nb);
+ sparx5 = router->sparx5;
+
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ /* Allow single bridge. Global router leg MAC tracks bridge mac. */
+ if (netif_is_bridge_master(dev) && sparx5_port_dev_lower_find(dev)) {
+ ether_addr_copy(mac, dev->dev_addr);
+ sparx5_rr_leg_base_mac_set(sparx5, mac);
+ }
+ break;
+ case NETDEV_UNREGISTER:
+ mutex_lock(&router->lock);
+ leg = sparx5_rr_leg_find_by_dev(sparx5, dev);
+ if (leg)
+ sparx5_rr_router_leg_destroy(leg);
+ mutex_unlock(&router->lock);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int sparx5_rr_blackhole_leg_create(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_router_leg *leg;
+ u16 vmid, vid;
+
+ vmid = SPARX5_BLACKHOLE_VMID(sparx5);
+ vid = SPARX5_BLACKHOLE_VID;
+
+ leg = __sparx5_rr_leg_alloc(sparx5, NULL, vmid, vid);
+ if (!leg)
+ return -ENOMEM;
+
+ set_bit(vmid, sparx5->router->vmid_mask);
+
+ list_add(&leg->leg_list_node, &sparx5->router->leg_list);
+ sparx5_rr_leg_hw_init(sparx5, leg);
+
+ dev_dbg(sparx5->dev, "Blackhole leg create vid=%u vmid=%u\n",
+ leg->vid, leg->vmid);
+
+ return 0;
+}
+
+static void sparx5_rr_router_legs_flush(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_router_leg *leg, *tmp;
+
+ list_for_each_entry_safe(leg, tmp, &sparx5->router->leg_list,
+ leg_list_node)
+ sparx5_rr_router_leg_destroy(leg);
+}
+
+static void sparx5_rr_fib_flush(struct sparx5 *sparx5);
+
+int sparx5_rr_router_init(struct sparx5 *sparx5)
+{
+ struct sparx5_router *r;
+ int err;
+
+ r = kzalloc_obj(*sparx5->router);
+ if (!r)
+ return -ENOMEM;
+
+ mutex_init(&r->lock);
+ sparx5->router = r;
+ r->sparx5 = sparx5;
+
+ INIT_LIST_HEAD(&r->leg_list);
+ INIT_LIST_HEAD(&r->fib_list);
+
+ /* Add reserved leg for blackhole routes. */
+ err = sparx5_rr_blackhole_leg_create(sparx5);
+ if (err)
+ goto err_free_router;
+
+ err = rhashtable_init(&r->neigh_ht, &sparx5_neigh_ht_params);
+ if (err)
+ goto err_blackhole_destroy;
+
+ err = rhashtable_init(&r->fib_ht, &sparx5_rr_fib_entry_ht_params);
+ if (err)
+ goto err_neigh_ht_destroy;
+
+ r->sparx5_router_owq = alloc_ordered_workqueue("sparx5_router_owq", 0);
+ if (!r->sparx5_router_owq) {
+ err = -ENOMEM;
+ goto err_fib_ht_destroy;
+ }
+
+ atomic_set(&r->legs_count, 0);
+ r->link_local.vrule_id = 0;
+ r->link_local.vrule_id_valid = false;
+ /* VCAP API requires a port net_device, to get a sparx5 reference.
+ * Fetch any valid port.
+ */
+ for (int i = 0; i < sparx5->data->consts->n_ports; i++) {
+ if (!sparx5->ports[i])
+ continue;
+
+ r->port_dev = sparx5->ports[i]->ndev;
+ if (r->port_dev)
+ break;
+ }
+ if (!r->port_dev) {
+ err = -ENXIO;
+ goto err_workqueue_destroy;
+ }
+
+ /* Enable L3 UC routing on all ports. */
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA);
+ if (is_sparx5(sparx5)) {
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA1);
+ spx5_wr(~0, sparx5, ANA_L3_L3_UC_ENA2);
+ }
+
+ /* Enable routing and global router options */
+ spx5_rmw(ANA_L3_ROUTING_CFG_L3_ENA_MODE_SET(1) |
+ ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA_SET(1) |
+ ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA_SET(1),
+ ANA_L3_ROUTING_CFG_L3_ENA_MODE |
+ ANA_L3_ROUTING_CFG_RT_SMAC_UPDATE_ENA |
+ ANA_L3_ROUTING_CFG_CPU_RLEG_IP_HDR_FAIL_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_CPU_IP4_OPTIONS_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_CPU_IP6_HOPBYHOP_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_IP6_HC_REDIR_ENA |
+ ANA_L3_ROUTING_CFG_IP4_TTL_REDIR_ENA,
+ sparx5, ANA_L3_ROUTING_CFG);
+
+ /* By default, routing related frame edits are done in REW, but when
+ * combining routing with PTP, ANA_ACL must be configured to change DMAC
+ * to next-hop DMAC in order to allow other information to be stored in
+ * the IFH.
+ *
+ * This enables routing related frame edits independently of VCAP_S2
+ * action ACL_RT_MODE.
+ */
+ spx5_rmw(ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL_SET(1),
+ ANA_ACL_VCAP_S2_MISC_CTRL_ACL_RT_SEL, sparx5,
+ ANA_ACL_VCAP_S2_MISC_CTRL);
+
+ r->netevent_nb.notifier_call = sparx5_rr_netevent_event;
+ err = register_netevent_notifier(&r->netevent_nb);
+ if (err)
+ goto err_workqueue_destroy;
+
+ r->fib_nb.notifier_call = sparx5_rr_fib_event;
+ err = register_fib_notifier(&init_net, &r->fib_nb, NULL, NULL);
+ if (err)
+ goto err_unreg_netevent_notifier;
+
+ r->inetaddr_nb.notifier_call = sparx5_rr_inetaddr_event;
+ err = register_inetaddr_notifier(&r->inetaddr_nb);
+ if (err)
+ goto err_unreg_fib_notifier;
+
+ r->inetaddr_valid_nb.notifier_call = sparx5_rr_inetaddr_valid_event;
+ err = register_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
+ if (err)
+ goto err_unreg_inet_notifier;
+
+ r->netdevice_nb.notifier_call = sparx5_rr_netdevice_event;
+ err = register_netdevice_notifier(&r->netdevice_nb);
+ if (err)
+ goto err_unreg_inet_addr_val_notifier;
+
+ r->inet6addr_valid_nb.notifier_call = sparx5_rr_inet6addr_valid_event;
+ err = register_inet6addr_validator_notifier(&r->inet6addr_valid_nb);
+ if (err)
+ goto err_unreg_netdev_notifier;
+
+ r->inet6addr_nb.notifier_call = sparx5_rr_inet6addr_event;
+ err = register_inet6addr_notifier(&r->inet6addr_nb);
+ if (err)
+ goto err_unreg_inet6_addr_val_notifier;
+
+ return 0;
+
+err_unreg_inet6_addr_val_notifier:
+ unregister_inet6addr_validator_notifier(&r->inet6addr_valid_nb);
+err_unreg_netdev_notifier:
+ unregister_netdevice_notifier(&r->netdevice_nb);
+err_unreg_inet_addr_val_notifier:
+ unregister_inetaddr_validator_notifier(&r->inetaddr_valid_nb);
+err_unreg_inet_notifier:
+ unregister_inetaddr_notifier(&r->inetaddr_nb);
+err_unreg_fib_notifier:
+ unregister_fib_notifier(&init_net, &r->fib_nb);
+err_unreg_netevent_notifier:
+ unregister_netevent_notifier(&r->netevent_nb);
+err_workqueue_destroy:
+ destroy_workqueue(r->sparx5_router_owq);
+ sparx5_rr_fib_flush(sparx5);
+err_fib_ht_destroy:
+ rhashtable_destroy(&r->fib_ht);
+err_neigh_ht_destroy:
+ rhashtable_destroy(&r->neigh_ht);
+err_blackhole_destroy:
+ sparx5_rr_router_legs_flush(sparx5);
+err_free_router:
+ mutex_destroy(&r->lock);
+ kfree(r);
+
+ return err;
+}
+
+static void sparx5_rr_fib_flush(struct sparx5 *sparx5)
+{
+ struct sparx5_rr_fib_entry *fib_entry, *tmp;
+
+ list_for_each_entry_safe(fib_entry, tmp, &sparx5->router->fib_list,
+ fib_node)
+ sparx5_rr_fib_entry_destroy(sparx5, fib_entry);
+}
+
+void sparx5_rr_router_deinit(struct sparx5 *sparx5)
+{
+ struct sparx5_router *router = sparx5->router;
+
+ unregister_inet6addr_notifier(&router->inet6addr_nb);
+ unregister_inet6addr_validator_notifier(&router->inet6addr_valid_nb);
+ unregister_netdevice_notifier(&router->netdevice_nb);
+ unregister_inetaddr_validator_notifier(&router->inetaddr_valid_nb);
+ unregister_inetaddr_notifier(&router->inetaddr_nb);
+ unregister_fib_notifier(&init_net, &router->fib_nb);
+ unregister_netevent_notifier(&router->netevent_nb);
+ destroy_workqueue(router->sparx5_router_owq);
+ sparx5_rr_fib_flush(sparx5);
+ sparx5_rr_router_legs_flush(sparx5);
+ WARN_ON(atomic_read(&router->neigh_ht.nelems));
+ rhashtable_destroy(&router->fib_ht);
+ rhashtable_destroy(&router->neigh_ht);
+ mutex_destroy(&router->lock);
+ kfree(router);
+}
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c
index 556d6ea0acd1b..7bc8b29757b14 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.c
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: BSD-3-Clause
-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.
+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP API
*/
-/* This file is autogenerated by cml-utils 2023-02-10 11:15:56 +0100.
- * Commit ID: c30fb4bf0281cd4a7133bdab6682f9e43c872ada
+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.
+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a
*/
#include <linux/types.h>
@@ -1381,6 +1381,83 @@ static const struct vcap_field es0_isdx_keyfield[] = {
},
};
+static const struct vcap_field lpm_sgl_ip4_keyfield[] = {
+ [VCAP_KF_DST_FLAG] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP4_XIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 11,
+ .width = 32,
+ },
+};
+
+static const struct vcap_field lpm_dbl_ip4_keyfield[] = {
+ [VCAP_KF_TYPE] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP4_SIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 11,
+ .width = 32,
+ },
+ [VCAP_KF_IP4_DIP] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 43,
+ .width = 32,
+ },
+};
+
+static const struct vcap_field lpm_sgl_ip6_keyfield[] = {
+ [VCAP_KF_DST_FLAG] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 0,
+ .width = 1,
+ },
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 1,
+ .width = 10,
+ },
+ [VCAP_KF_IP6_XIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 11,
+ .width = 128,
+ },
+};
+
+static const struct vcap_field lpm_dbl_ip6_keyfield[] = {
+ [VCAP_KF_AFFIX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 10,
+ },
+ [VCAP_KF_IP6_SIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 10,
+ .width = 128,
+ },
+ [VCAP_KF_IP6_DIP] = {
+ .type = VCAP_FIELD_U128,
+ .offset = 138,
+ .width = 128,
+ },
+};
+
static const struct vcap_field es2_mac_etype_keyfield[] = {
[VCAP_KF_TYPE] = {
.type = VCAP_FIELD_U32,
@@ -2339,6 +2416,29 @@ static const struct vcap_set es0_keyfield_set[] = {
},
};
+static const struct vcap_set lpm_keyfield_set[] = {
+ [VCAP_KFS_SGL_IP4] = {
+ .type_id = -1,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+ [VCAP_KFS_DBL_IP4] = {
+ .type_id = 0,
+ .sw_per_item = 2,
+ .sw_cnt = 6,
+ },
+ [VCAP_KFS_SGL_IP6] = {
+ .type_id = -1,
+ .sw_per_item = 3,
+ .sw_cnt = 4,
+ },
+ [VCAP_KFS_DBL_IP6] = {
+ .type_id = -1,
+ .sw_per_item = 6,
+ .sw_cnt = 2,
+ },
+};
+
static const struct vcap_set es2_keyfield_set[] = {
[VCAP_KFS_MAC_ETYPE] = {
.type_id = 0,
@@ -2391,6 +2491,13 @@ static const struct vcap_field *es0_keyfield_set_map[] = {
[VCAP_KFS_ISDX] = es0_isdx_keyfield,
};
+static const struct vcap_field *lpm_keyfield_set_map[] = {
+ [VCAP_KFS_SGL_IP4] = lpm_sgl_ip4_keyfield,
+ [VCAP_KFS_DBL_IP4] = lpm_dbl_ip4_keyfield,
+ [VCAP_KFS_SGL_IP6] = lpm_sgl_ip6_keyfield,
+ [VCAP_KFS_DBL_IP6] = lpm_dbl_ip6_keyfield,
+};
+
static const struct vcap_field *es2_keyfield_set_map[] = {
[VCAP_KFS_MAC_ETYPE] = es2_mac_etype_keyfield,
[VCAP_KFS_ARP] = es2_arp_keyfield,
@@ -2419,6 +2526,13 @@ static int es0_keyfield_set_map_size[] = {
[VCAP_KFS_ISDX] = ARRAY_SIZE(es0_isdx_keyfield),
};
+static int lpm_keyfield_set_map_size[] = {
+ [VCAP_KFS_SGL_IP4] = ARRAY_SIZE(lpm_sgl_ip4_keyfield),
+ [VCAP_KFS_DBL_IP4] = ARRAY_SIZE(lpm_dbl_ip4_keyfield),
+ [VCAP_KFS_SGL_IP6] = ARRAY_SIZE(lpm_sgl_ip6_keyfield),
+ [VCAP_KFS_DBL_IP6] = ARRAY_SIZE(lpm_dbl_ip6_keyfield),
+};
+
static int es2_keyfield_set_map_size[] = {
[VCAP_KFS_MAC_ETYPE] = ARRAY_SIZE(es2_mac_etype_keyfield),
[VCAP_KFS_ARP] = ARRAY_SIZE(es2_arp_keyfield),
@@ -2994,6 +3108,92 @@ static const struct vcap_field es0_es0_actionfield[] = {
},
};
+static const struct vcap_field lpm_arp_ptr_actionfield[] = {
+ [VCAP_AF_TYPE] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 2,
+ },
+ [VCAP_AF_ARP_PTR] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 2,
+ .width = 11,
+ },
+ [VCAP_AF_ARP_PTR_REMAP_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 13,
+ .width = 1,
+ },
+ [VCAP_AF_ECMP_CNT] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 14,
+ .width = 4,
+ },
+ [VCAP_AF_RGID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 18,
+ .width = 3,
+ },
+};
+
+static const struct vcap_field lpm_arp_entry_actionfield[] = {
+ [VCAP_AF_TYPE] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 0,
+ .width = 2,
+ },
+ [VCAP_AF_MAC_MSB] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 2,
+ .width = 16,
+ },
+ [VCAP_AF_MAC_LSB] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 18,
+ .width = 32,
+ },
+ [VCAP_AF_ARP_VMID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 50,
+ .width = 9,
+ },
+ [VCAP_AF_ZERO_DMAC_CPU_QU] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 59,
+ .width = 3,
+ },
+ [VCAP_AF_SIP_RPF_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 62,
+ .width = 1,
+ },
+ [VCAP_AF_SECUR_MATCH_VMID_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 63,
+ .width = 1,
+ },
+ [VCAP_AF_SECUR_MATCH_MAC_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 64,
+ .width = 1,
+ },
+ [VCAP_AF_ARP_ENA] = {
+ .type = VCAP_FIELD_BIT,
+ .offset = 65,
+ .width = 1,
+ },
+ [VCAP_AF_ENCAP_ID] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 66,
+ .width = 10,
+ },
+ [VCAP_AF_RSDX] = {
+ .type = VCAP_FIELD_U32,
+ .offset = 76,
+ .width = 12,
+ },
+};
+
static const struct vcap_field es2_base_type_actionfield[] = {
[VCAP_AF_HIT_ME_ONCE] = {
.type = VCAP_FIELD_BIT,
@@ -3102,6 +3302,19 @@ static const struct vcap_set es0_actionfield_set[] = {
},
};
+static const struct vcap_set lpm_actionfield_set[] = {
+ [VCAP_AFS_ARP_PTR] = {
+ .type_id = 0,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+ [VCAP_AFS_ARP_ENTRY] = {
+ .type_id = 2,
+ .sw_per_item = 1,
+ .sw_cnt = 12,
+ },
+};
+
static const struct vcap_set es2_actionfield_set[] = {
[VCAP_AFS_BASE_TYPE] = {
.type_id = -1,
@@ -3125,6 +3338,11 @@ static const struct vcap_field *es0_actionfield_set_map[] = {
[VCAP_AFS_ES0] = es0_es0_actionfield,
};
+static const struct vcap_field *lpm_actionfield_set_map[] = {
+ [VCAP_AFS_ARP_PTR] = lpm_arp_ptr_actionfield,
+ [VCAP_AFS_ARP_ENTRY] = lpm_arp_entry_actionfield,
+};
+
static const struct vcap_field *es2_actionfield_set_map[] = {
[VCAP_AFS_BASE_TYPE] = es2_base_type_actionfield,
};
@@ -3144,6 +3362,11 @@ static int es0_actionfield_set_map_size[] = {
[VCAP_AFS_ES0] = ARRAY_SIZE(es0_es0_actionfield),
};
+static int lpm_actionfield_set_map_size[] = {
+ [VCAP_AFS_ARP_PTR] = ARRAY_SIZE(lpm_arp_ptr_actionfield),
+ [VCAP_AFS_ARP_ENTRY] = ARRAY_SIZE(lpm_arp_entry_actionfield),
+};
+
static int es2_actionfield_set_map_size[] = {
[VCAP_AFS_BASE_TYPE] = ARRAY_SIZE(es2_base_type_actionfield),
};
@@ -3334,6 +3557,82 @@ static const struct vcap_typegroup es0_x1_keyfield_set_typegroups[] = {
{}
};
+static const struct vcap_typegroup lpm_x6_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 4,
+ .value = 8,
+ },
+ {
+ .offset = 52,
+ .width = 1,
+ .value = 0,
+ },
+ {
+ .offset = 104,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 156,
+ .width = 3,
+ .value = 0,
+ },
+ {
+ .offset = 208,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 260,
+ .width = 1,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x3_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 3,
+ .value = 4,
+ },
+ {
+ .offset = 52,
+ .width = 2,
+ .value = 0,
+ },
+ {
+ .offset = 104,
+ .width = 2,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x2_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 2,
+ .value = 2,
+ },
+ {
+ .offset = 52,
+ .width = 1,
+ .value = 0,
+ },
+ {}
+};
+
+static const struct vcap_typegroup lpm_x1_keyfield_set_typegroups[] = {
+ {
+ .offset = 0,
+ .width = 1,
+ .value = 1,
+ },
+ {}
+};
+
static const struct vcap_typegroup es2_x12_keyfield_set_typegroups[] = {
{
.offset = 0,
@@ -3407,6 +3706,14 @@ static const struct vcap_typegroup *es0_keyfield_set_typegroups[] = {
[2] = NULL,
};
+static const struct vcap_typegroup *lpm_keyfield_set_typegroups[] = {
+ [6] = lpm_x6_keyfield_set_typegroups,
+ [3] = lpm_x3_keyfield_set_typegroups,
+ [2] = lpm_x2_keyfield_set_typegroups,
+ [1] = lpm_x1_keyfield_set_typegroups,
+ [13] = NULL,
+};
+
static const struct vcap_typegroup *es2_keyfield_set_typegroups[] = {
[12] = es2_x12_keyfield_set_typegroups,
[6] = es2_x6_keyfield_set_typegroups,
@@ -3484,6 +3791,10 @@ static const struct vcap_typegroup es0_x1_actionfield_set_typegroups[] = {
{}
};
+static const struct vcap_typegroup lpm_x1_actionfield_set_typegroups[] = {
+ {}
+};
+
static const struct vcap_typegroup es2_x3_actionfield_set_typegroups[] = {
{
.offset = 0,
@@ -3525,6 +3836,11 @@ static const struct vcap_typegroup *es0_actionfield_set_typegroups[] = {
[2] = NULL,
};
+static const struct vcap_typegroup *lpm_actionfield_set_typegroups[] = {
+ [1] = lpm_x1_actionfield_set_typegroups,
+ [13] = NULL,
+};
+
static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {
[3] = es2_x3_actionfield_set_typegroups,
[1] = es2_x1_actionfield_set_typegroups,
@@ -3535,6 +3851,8 @@ static const struct vcap_typegroup *es2_actionfield_set_typegroups[] = {
static const char * const vcap_keyfield_set_names[] = {
[VCAP_KFS_NO_VALUE] = "(None)",
[VCAP_KFS_ARP] = "VCAP_KFS_ARP",
+ [VCAP_KFS_DBL_IP4] = "VCAP_KFS_DBL_IP4",
+ [VCAP_KFS_DBL_IP6] = "VCAP_KFS_DBL_IP6",
[VCAP_KFS_ETAG] = "VCAP_KFS_ETAG",
[VCAP_KFS_IP4_OTHER] = "VCAP_KFS_IP4_OTHER",
[VCAP_KFS_IP4_TCP_UDP] = "VCAP_KFS_IP4_TCP_UDP",
@@ -3553,6 +3871,8 @@ static const char * const vcap_keyfield_set_names[] = {
[VCAP_KFS_NORMAL_7TUPLE] = "VCAP_KFS_NORMAL_7TUPLE",
[VCAP_KFS_OAM] = "VCAP_KFS_OAM",
[VCAP_KFS_PURE_5TUPLE_IP4] = "VCAP_KFS_PURE_5TUPLE_IP4",
+ [VCAP_KFS_SGL_IP4] = "VCAP_KFS_SGL_IP4",
+ [VCAP_KFS_SGL_IP6] = "VCAP_KFS_SGL_IP6",
[VCAP_KFS_SMAC_SIP4] = "VCAP_KFS_SMAC_SIP4",
[VCAP_KFS_SMAC_SIP6] = "VCAP_KFS_SMAC_SIP6",
};
@@ -3560,6 +3880,8 @@ static const char * const vcap_keyfield_set_names[] = {
/* Actionfieldset names */
static const char * const vcap_actionfield_set_names[] = {
[VCAP_AFS_NO_VALUE] = "(None)",
+ [VCAP_AFS_ARP_ENTRY] = "VCAP_AFS_ARP_ENTRY",
+ [VCAP_AFS_ARP_PTR] = "VCAP_AFS_ARP_PTR",
[VCAP_AFS_BASE_TYPE] = "VCAP_AFS_BASE_TYPE",
[VCAP_AFS_CLASSIFICATION] = "VCAP_AFS_CLASSIFICATION",
[VCAP_AFS_CLASS_REDUCED] = "VCAP_AFS_CLASS_REDUCED",
@@ -3596,6 +3918,7 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_8021Q_VLAN_TAGGED_IS] = "8021Q_VLAN_TAGGED_IS",
[VCAP_KF_8021Q_VLAN_TAGS] = "8021Q_VLAN_TAGS",
[VCAP_KF_ACL_GRP_ID] = "ACL_GRP_ID",
+ [VCAP_KF_AFFIX] = "AFFIX",
[VCAP_KF_ARP_ADDR_SPACE_OK_IS] = "ARP_ADDR_SPACE_OK_IS",
[VCAP_KF_ARP_LEN_OK_IS] = "ARP_LEN_OK_IS",
[VCAP_KF_ARP_OPCODE] = "ARP_OPCODE",
@@ -3604,6 +3927,7 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_ARP_SENDER_MATCH_IS] = "ARP_SENDER_MATCH_IS",
[VCAP_KF_ARP_TGT_MATCH_IS] = "ARP_TGT_MATCH_IS",
[VCAP_KF_COSID_CLS] = "COSID_CLS",
+ [VCAP_KF_DST_FLAG] = "DST_FLAG",
[VCAP_KF_ES0_ISDX_KEY_ENA] = "ES0_ISDX_KEY_ENA",
[VCAP_KF_ETYPE] = "ETYPE",
[VCAP_KF_ETYPE_LEN_IS] = "ETYPE_LEN_IS",
@@ -3617,7 +3941,13 @@ static const char * const vcap_keyfield_names[] = {
[VCAP_KF_IF_IGR_PORT_MASK_RNG] = "IF_IGR_PORT_MASK_RNG",
[VCAP_KF_IF_IGR_PORT_MASK_SEL] = "IF_IGR_PORT_MASK_SEL",
[VCAP_KF_IF_IGR_PORT_SEL] = "IF_IGR_PORT_SEL",
+ [VCAP_KF_IP4_DIP] = "IP4_DIP",
[VCAP_KF_IP4_IS] = "IP4_IS",
+ [VCAP_KF_IP4_SIP] = "IP4_SIP",
+ [VCAP_KF_IP4_XIP] = "IP4_XIP",
+ [VCAP_KF_IP6_DIP] = "IP6_DIP",
+ [VCAP_KF_IP6_SIP] = "IP6_SIP",
+ [VCAP_KF_IP6_XIP] = "IP6_XIP",
[VCAP_KF_IP_MC_IS] = "IP_MC_IS",
[VCAP_KF_IP_PAYLOAD_5TUPLE] = "IP_PAYLOAD_5TUPLE",
[VCAP_KF_IP_SNAP_IS] = "IP_SNAP_IS",
@@ -3690,6 +4020,10 @@ static const char * const vcap_keyfield_names[] = {
static const char * const vcap_actionfield_names[] = {
[VCAP_AF_NO_VALUE] = "(None)",
[VCAP_AF_ACL_ID] = "ACL_ID",
+ [VCAP_AF_ARP_ENA] = "ARP_ENA",
+ [VCAP_AF_ARP_PTR] = "ARP_PTR",
+ [VCAP_AF_ARP_PTR_REMAP_ENA] = "ARP_PTR_REMAP_ENA",
+ [VCAP_AF_ARP_VMID] = "ARP_VMID",
[VCAP_AF_CLS_VID_SEL] = "CLS_VID_SEL",
[VCAP_AF_CNT_ID] = "CNT_ID",
[VCAP_AF_COPY_PORT_NUM] = "COPY_PORT_NUM",
@@ -3707,6 +4041,8 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_DSCP_ENA] = "DSCP_ENA",
[VCAP_AF_DSCP_SEL] = "DSCP_SEL",
[VCAP_AF_DSCP_VAL] = "DSCP_VAL",
+ [VCAP_AF_ECMP_CNT] = "ECMP_CNT",
+ [VCAP_AF_ENCAP_ID] = "ENCAP_ID",
[VCAP_AF_ES2_REW_CMD] = "ES2_REW_CMD",
[VCAP_AF_ESDX] = "ESDX",
[VCAP_AF_FWD_KILL_ENA] = "FWD_KILL_ENA",
@@ -3721,6 +4057,8 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_ISDX_VAL] = "ISDX_VAL",
[VCAP_AF_LOOP_ENA] = "LOOP_ENA",
[VCAP_AF_LRN_DIS] = "LRN_DIS",
+ [VCAP_AF_MAC_LSB] = "MAC_LSB",
+ [VCAP_AF_MAC_MSB] = "MAC_MSB",
[VCAP_AF_MAP_IDX] = "MAP_IDX",
[VCAP_AF_MAP_KEY] = "MAP_KEY",
[VCAP_AF_MAP_LOOKUP_SEL] = "MAP_LOOKUP_SEL",
@@ -3754,7 +4092,12 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_QOS_ENA] = "QOS_ENA",
[VCAP_AF_QOS_VAL] = "QOS_VAL",
[VCAP_AF_REW_OP] = "REW_OP",
+ [VCAP_AF_RGID] = "RGID",
+ [VCAP_AF_RSDX] = "RSDX",
[VCAP_AF_RT_DIS] = "RT_DIS",
+ [VCAP_AF_SECUR_MATCH_MAC_ENA] = "SECUR_MATCH_MAC_ENA",
+ [VCAP_AF_SECUR_MATCH_VMID_ENA] = "SECUR_MATCH_VMID_ENA",
+ [VCAP_AF_SIP_RPF_ENA] = "SIP_RPF_ENA",
[VCAP_AF_SWAP_MACS_ENA] = "SWAP_MACS_ENA",
[VCAP_AF_TAG_A_DEI_SEL] = "TAG_A_DEI_SEL",
[VCAP_AF_TAG_A_PCP_SEL] = "TAG_A_PCP_SEL",
@@ -3774,6 +4117,7 @@ static const char * const vcap_actionfield_names[] = {
[VCAP_AF_VID_B_VAL] = "VID_B_VAL",
[VCAP_AF_VID_C_VAL] = "VID_C_VAL",
[VCAP_AF_VID_VAL] = "VID_VAL",
+ [VCAP_AF_ZERO_DMAC_CPU_QU] = "ZERO_DMAC_CPU_QU",
};
/* VCAPs */
@@ -3841,6 +4185,27 @@ const struct vcap_info sparx5_vcaps[] = {
.keyfield_set_typegroups = es0_keyfield_set_typegroups,
.actionfield_set_typegroups = es0_actionfield_set_typegroups,
},
+ [VCAP_TYPE_LPM] = {
+ .name = "lpm",
+ .rows = 512,
+ .sw_count = 12,
+ .sw_width = 52,
+ .sticky_width = 1,
+ .act_width = 89,
+ .default_cnt = 0,
+ .require_cnt_dis = 0,
+ .version = 1,
+ .keyfield_set = lpm_keyfield_set,
+ .keyfield_set_size = ARRAY_SIZE(lpm_keyfield_set),
+ .actionfield_set = lpm_actionfield_set,
+ .actionfield_set_size = ARRAY_SIZE(lpm_actionfield_set),
+ .keyfield_set_map = lpm_keyfield_set_map,
+ .keyfield_set_map_size = lpm_keyfield_set_map_size,
+ .actionfield_set_map = lpm_actionfield_set_map,
+ .actionfield_set_map_size = lpm_actionfield_set_map_size,
+ .keyfield_set_typegroups = lpm_keyfield_set_typegroups,
+ .actionfield_set_typegroups = lpm_actionfield_set_typegroups,
+ },
[VCAP_TYPE_ES2] = {
.name = "es2",
.rows = 1024,
@@ -3866,7 +4231,7 @@ const struct vcap_info sparx5_vcaps[] = {
const struct vcap_statistics sparx5_vcap_stats = {
.name = "sparx5",
- .count = 4,
+ .count = 5,
.keyfield_set_names = vcap_keyfield_set_names,
.actionfield_set_names = vcap_actionfield_set_names,
.keyfield_names = vcap_keyfield_names,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h
index e68f5639a40a5..22bb416f433d8 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_ag_api.h
@@ -1,10 +1,10 @@
/* SPDX-License-Identifier: BSD-3-Clause */
-/* Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries.
+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP API
*/
-/* This file is autogenerated by cml-utils 2022-10-13 10:04:41 +0200.
- * Commit ID: fd7cafd175899f0672c73afb3a30fc872500ae86
+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.
+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a
*/
#ifndef __SPARX5_VCAP_AG_API_H__
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
index cf332de6bf736..20450462e2d80 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
@@ -54,8 +54,8 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {
.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,
.first_cid = SPARX5_VCAP_CID_IS0_L0,
.last_cid = SPARX5_VCAP_CID_IS0_L2 - 1,
- .blockno = 8, /* Maps block 8-9 */
- .blocks = 2,
+ .blockno = 8, /* Maps block 8 */
+ .blocks = 1,
.ingress = true,
},
{
@@ -124,6 +124,18 @@ const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[] = {
.count = 12288, /* Addresses according to datasheet */
.ingress = false,
},
+ {
+ .vtype = VCAP_TYPE_LPM,
+ .vinst = 0,
+ .map_id = 6,
+ .lookups = SPARX5_LPM_LOOKUPS,
+ .lookups_per_instance = SPARX5_LPM_LOOKUPS,
+ .first_cid = SPARX5_VCAP_CID_LPM_L0,
+ .last_cid = SPARX5_VCAP_CID_LPM_MAX,
+ .blockno = 9,
+ .blocks = 1,
+ .ingress = true,
+ },
};
/* These protocols have dedicated keysets in IS0 and a TC dissector */
@@ -149,6 +161,12 @@ static u16 sparx5_vcap_es2_known_etypes[] = {
ETH_P_IPV6,
};
+static u16 sparx5_vcap_lpm_known_etypes[] = {
+ ETH_P_ALL,
+ ETH_P_IP,
+ ETH_P_IPV6,
+};
+
static void sparx5_vcap_type_err(struct sparx5 *sparx5,
struct vcap_admin *admin,
const char *fname)
@@ -195,6 +213,7 @@ static void _sparx5_vcap_range_init(struct sparx5 *sparx5,
u32 size = count - 1;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
spx5_wr(VCAP_SUPER_CFG_MV_NUM_POS_SET(0) |
@@ -386,6 +405,11 @@ static int sparx5_vcap_es2_cid_to_lookup(int cid)
return lookup;
}
+static int sparx5_vcap_lpm_cid_to_lookup(int cid)
+{
+ return 0;
+}
+
/* Add ethernet type IS0 keyset to a list */
static void
sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,
@@ -401,6 +425,28 @@ sparx5_vcap_is0_get_port_etype_keysets(struct vcap_keyset_list *keysetlist,
}
}
+static int sparx5_vcap_lpm_get_port_keysets(struct net_device *ndev,
+ int lookup,
+ struct vcap_keyset_list *keysetlist,
+ u16 l3_proto)
+{
+ /* LPM keysets are static. */
+ if (l3_proto == ETH_P_ALL) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);
+ } else if (l3_proto == ETH_P_IP) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP4);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP4);
+ } else if (l3_proto == ETH_P_IPV6) {
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_DBL_IP6);
+ vcap_keyset_list_add(keysetlist, VCAP_KFS_SGL_IP6);
+ }
+
+ return 0;
+}
+
/* Return the list of keysets for the vcap port configuration */
static int sparx5_vcap_is0_get_port_keysets(struct net_device *ndev,
int lookup,
@@ -683,6 +729,11 @@ int sparx5_vcap_get_port_keyset(struct net_device *ndev,
struct sparx5_port *port;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ lookup = sparx5_vcap_lpm_cid_to_lookup(cid);
+ err = sparx5_vcap_lpm_get_port_keysets(ndev, lookup, kslist,
+ l3_proto);
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(cid);
err = sparx5_vcap_is0_get_port_keysets(ndev, lookup, kslist,
@@ -716,6 +767,10 @@ bool sparx5_vcap_is_known_etype(struct vcap_admin *admin, u16 etype)
int size, idx;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ known_etypes = sparx5_vcap_lpm_known_etypes;
+ size = ARRAY_SIZE(sparx5_vcap_lpm_known_etypes);
+ break;
case VCAP_TYPE_IS0:
known_etypes = sparx5_vcap_is0_known_etypes;
size = ARRAY_SIZE(sparx5_vcap_is0_known_etypes);
@@ -760,6 +815,11 @@ sparx5_vcap_validate_keyset(struct net_device *ndev,
/* Get a list of currently configured keysets in the lookups */
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ lookup = sparx5_vcap_lpm_cid_to_lookup(rule->vcap_chain_id);
+ sparx5_vcap_lpm_get_port_keysets(ndev, lookup, &keysetlist,
+ l3_proto);
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(rule->vcap_chain_id);
sparx5_vcap_is0_get_port_keysets(ndev, lookup, &keysetlist,
@@ -873,6 +933,9 @@ static void sparx5_vcap_add_default_fields(struct net_device *ndev,
/* add the lookup bit */
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ /* LPM VCAP has no default fields */
+ break;
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_ingress_add_default_fields(ndev, admin, rule);
@@ -1083,6 +1146,46 @@ static void sparx5_vcap_es2_cache_write(struct sparx5 *sparx5,
}
}
+static void sparx5_vcap_lpm_cache_write(struct sparx5 *sparx5,
+ struct vcap_admin *admin,
+ enum vcap_selection sel,
+ u32 start,
+ u32 count)
+{
+ u32 *keystr, *mskstr, *actstr;
+ int idx;
+
+ keystr = &admin->cache.keystream[start];
+ mskstr = &admin->cache.maskstream[start];
+ actstr = &admin->cache.actionstream[start];
+
+ switch (sel) {
+ case VCAP_SEL_ENTRY:
+ for (idx = 0; idx < count; ++idx) {
+ /* Avoid 'match-off' by setting value & mask */
+ spx5_wr(keystr[idx] & mskstr[idx], sparx5,
+ VCAP_SUPER_VCAP_ENTRY_DAT(idx));
+ spx5_wr(~mskstr[idx], sparx5,
+ VCAP_SUPER_VCAP_MASK_DAT(idx));
+ }
+ break;
+ case VCAP_SEL_ACTION:
+ for (idx = 0; idx < count; ++idx)
+ spx5_wr(actstr[idx], sparx5,
+ VCAP_SUPER_VCAP_ACTION_DAT(idx));
+ break;
+ case VCAP_SEL_ALL:
+ pr_err("%s:%d: cannot write all streams at once\n", __func__,
+ __LINE__);
+ break;
+ default:
+ break;
+ }
+ if (sel & VCAP_SEL_COUNTER)
+ spx5_wr(admin->cache.counter, sparx5,
+ VCAP_SUPER_VCAP_CNT_DAT(0));
+}
+
/* API callback used for writing to the VCAP cache */
static void sparx5_vcap_cache_write(struct net_device *ndev,
struct vcap_admin *admin,
@@ -1094,6 +1197,9 @@ static void sparx5_vcap_cache_write(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ sparx5_vcap_lpm_cache_write(sparx5, admin, sel, start, count);
+ break;
case VCAP_TYPE_IS0:
sparx5_vcap_is0_cache_write(sparx5, admin, sel, start, count);
break;
@@ -1273,6 +1379,41 @@ static void sparx5_vcap_es2_cache_read(struct sparx5 *sparx5,
}
}
+static void sparx5_vcap_lpm_cache_read(struct sparx5 *sparx5,
+ struct vcap_admin *admin,
+ enum vcap_selection sel,
+ u32 start,
+ u32 count)
+{
+ u32 *keystr, *mskstr, *actstr;
+ int idx;
+
+ keystr = &admin->cache.keystream[start];
+ mskstr = &admin->cache.maskstream[start];
+ actstr = &admin->cache.actionstream[start];
+
+ if (sel & VCAP_SEL_ENTRY) {
+ for (idx = 0; idx < count; ++idx) {
+ keystr[idx] = spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_ENTRY_DAT(idx));
+ mskstr[idx] = ~spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_MASK_DAT(idx));
+ }
+ }
+
+ if (sel & VCAP_SEL_ACTION)
+ for (idx = 0; idx < count; ++idx)
+ actstr[idx] = spx5_rd(sparx5,
+ VCAP_SUPER_VCAP_ACTION_DAT(idx));
+
+ if (sel & VCAP_SEL_COUNTER) {
+ u32 val = spx5_rd(sparx5, VCAP_SUPER_VCAP_CNT_DAT(0));
+
+ admin->cache.counter = val;
+ admin->cache.sticky = !!val;
+ }
+}
+
/* API callback used for reading from the VCAP into the VCAP cache */
static void sparx5_vcap_cache_read(struct net_device *ndev,
struct vcap_admin *admin,
@@ -1284,6 +1425,9 @@ static void sparx5_vcap_cache_read(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ sparx5_vcap_lpm_cache_read(sparx5, admin, sel, start, count);
+ break;
case VCAP_TYPE_IS0:
sparx5_vcap_is0_cache_read(sparx5, admin, sel, start, count);
break;
@@ -1379,6 +1523,7 @@ static void sparx5_vcap_update(struct net_device *ndev,
struct sparx5 *sparx5 = port->sparx5;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_super_update(sparx5, cmd, sel, addr);
@@ -1475,6 +1620,7 @@ static void sparx5_vcap_move(struct net_device *ndev, struct vcap_admin *admin,
}
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
sparx5_vcap_super_move(sparx5, addr, cmd, mv_num_pos, mv_size);
@@ -1743,6 +1889,8 @@ void sparx5_vcap_set_port_keyset(struct net_device *ndev,
int lookup;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
+ break;
case VCAP_TYPE_IS0:
lookup = sparx5_vcap_is0_cid_to_lookup(cid);
if (orig)
@@ -1879,6 +2027,9 @@ static void sparx5_vcap_port_key_selection(struct sparx5 *sparx5,
case VCAP_TYPE_ES2:
sparx5_vcap_es2_port_key_selection(sparx5, admin);
break;
+ case VCAP_TYPE_LPM:
+ /* VCAP LPM key selection is static */
+ break;
default:
sparx5_vcap_type_err(sparx5, admin, __func__);
break;
@@ -1920,6 +2071,9 @@ static void sparx5_vcap_port_key_deselection(struct sparx5 *sparx5,
sparx5,
EACL_VCAP_ES2_KEY_SEL(portno, lookup));
break;
+ case VCAP_TYPE_LPM:
+ /* LPM key selection is static */
+ break;
default:
sparx5_vcap_type_err(sparx5, admin, __func__);
break;
@@ -1979,6 +2133,7 @@ static void sparx5_vcap_block_alloc(struct sparx5 *sparx5,
int idx, cores;
switch (admin->vtype) {
+ case VCAP_TYPE_LPM:
case VCAP_TYPE_IS0:
case VCAP_TYPE_IS2:
/* Super VCAP block mapping and address configuration. Block 0
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
index d0a42406bf26c..94c77a60e4f84 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.h
@@ -20,6 +20,7 @@
#define SPARX5_IS0_LOOKUPS 6
#define SPARX5_ES0_LOOKUPS 1
#define SPARX5_ES2_LOOKUPS 2
+#define SPARX5_LPM_LOOKUPS 1
#define SPARX5_VCAP_CID_IS0_L0 VCAP_CID_INGRESS_L0 /* IS0/CLM lookup 0 */
#define SPARX5_VCAP_CID_IS0_L1 VCAP_CID_INGRESS_L1 /* IS0/CLM lookup 1 */
@@ -30,6 +31,10 @@
#define SPARX5_VCAP_CID_IS0_MAX \
(VCAP_CID_INGRESS_L5 + VCAP_CID_LOOKUP_SIZE - 1) /* IS0/CLM Max */
+#define SPARX5_VCAP_CID_LPM_L0 VCAP_CID_PREROUTING_L0 /* LPM lookup 0 */
+#define SPARX5_VCAP_CID_LPM_MAX \
+ (VCAP_CID_PREROUTING_L0 + VCAP_CID_LOOKUP_SIZE - 1) /* LPM Max */
+
#define SPARX5_VCAP_CID_IS2_L0 VCAP_CID_INGRESS_STAGE2_L0 /* IS2 lookup 0 */
#define SPARX5_VCAP_CID_IS2_L1 VCAP_CID_INGRESS_STAGE2_L1 /* IS2 lookup 1 */
#define SPARX5_VCAP_CID_IS2_L2 VCAP_CID_INGRESS_STAGE2_L2 /* IS2 lookup 2 */
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h b/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h
index 4735fad057081..487d564409365 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h
+++ b/drivers/net/ethernet/microchip/vcap/vcap_ag_api.h
@@ -1,10 +1,10 @@
/* SPDX-License-Identifier: BSD-3-Clause */
-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.
+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP API
*/
-/* This file is autogenerated by cml-utils 2023-03-13 10:16:42 +0100.
- * Commit ID: 259f0efd6d6d91bfbf62858de153cc757b6bffa3 (dirty)
+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.
+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a
*/
#ifndef __VCAP_AG_API__
@@ -16,6 +16,7 @@ enum vcap_type {
VCAP_TYPE_IS0,
VCAP_TYPE_IS1,
VCAP_TYPE_IS2,
+ VCAP_TYPE_LPM,
VCAP_TYPE_MAX
};
@@ -26,6 +27,8 @@ enum vcap_keyfield_set {
VCAP_KFS_5TUPLE_IP6, /* lan966x is1 X4 */
VCAP_KFS_7TUPLE, /* lan966x is1 X4 */
VCAP_KFS_ARP, /* sparx5 is2 X6, sparx5 es2 X6, lan966x is2 X2 */
+ VCAP_KFS_DBL_IP4, /* sparx5 lpm X2 */
+ VCAP_KFS_DBL_IP6, /* sparx5 lpm X6 */
VCAP_KFS_DBL_VID, /* lan966x is1 X1 */
VCAP_KFS_DMAC_VID, /* lan966x is1 X1 */
VCAP_KFS_ETAG, /* sparx5 is0 X2 */
@@ -49,6 +52,8 @@ enum vcap_keyfield_set {
VCAP_KFS_OAM, /* lan966x is2 X2 */
VCAP_KFS_PURE_5TUPLE_IP4, /* sparx5 is0 X3 */
VCAP_KFS_RT, /* lan966x is1 X1 */
+ VCAP_KFS_SGL_IP4, /* sparx5 lpm X1 */
+ VCAP_KFS_SGL_IP6, /* sparx5 lpm X3 */
VCAP_KFS_SMAC_SIP4, /* lan966x is2 X1 */
VCAP_KFS_SMAC_SIP6, /* lan966x is2 X2 */
VCAP_KFS_VID, /* lan966x es0 X1 */
@@ -117,6 +122,10 @@ enum vcap_keyfield_set {
* tagged, 7: Triple tagged
* VCAP_KF_ACL_GRP_ID: W8, sparx5: es2
* Used in interface map table
+ * VCAP_KF_AFFIX: W10, sparx5: lpm
+ * LPM affix. Defaults to 0. Using the LPM affix, the VCAP LPM can be split into
+ * multiple logically separate routing tables, e.g. to support features such as
+ * VRF-lite.
* VCAP_KF_ARP_ADDR_SPACE_OK_IS: W1, sparx5: is2/es2, lan966x: is2
* Set if hardware address is Ethernet
* VCAP_KF_ARP_LEN_OK_IS: W1, sparx5: is2/es2, lan966x: is2
@@ -133,6 +142,9 @@ enum vcap_keyfield_set {
* Target Hardware Address = SMAC (RARP)
* VCAP_KF_COSID_CLS: W3, sparx5: es0/es2
* Class of service
+ * VCAP_KF_DST_FLAG: W1, sparx5: lpm
+ * 0: IP4_XIP/IP6_XIP is only to be used for SIP matching, 1: IP4_XIP/IP6_XIP is
+ * only to be used for DIP matching
* VCAP_KF_ES0_ISDX_KEY_ENA: W1, sparx5: es2
* The value taken from the IFH .FWD.ES0_ISDX_KEY_ENA
* VCAP_KF_ETYPE: W16, sparx5: is0/is2/es2, lan966x: is1/is2
@@ -167,8 +179,22 @@ enum vcap_keyfield_set {
* Mapping: 0: DEFAULT 1: LOOPBACK 2: MASQUERADE 3: CPU_VD
* VCAP_KF_IF_IGR_PORT_SEL: W1, sparx5: es2
* Selector for IF_IGR_PORT: physical port number or ERLEG
+ * VCAP_KF_IP4_DIP: W32, sparx5: lpm
+ * IPv4 destination address. Used for IPv4 MC routing.
* VCAP_KF_IP4_IS: W1, sparx5: is0/is2/es2, lan966x: is1/is2
* Set if frame has EtherType = 0x800 and IP version = 4
+ * VCAP_KF_IP4_SIP: W32, sparx5: lpm
+ * IPv4 source address. Used for IPv4 MC routing.
+ * VCAP_KF_IP4_XIP: W32, sparx5: lpm
+ * IPv4 address. Used for IPv4 UC routing as well as IPv4 Source/Destination
+ * Guard.
+ * VCAP_KF_IP6_DIP: W128, sparx5: lpm
+ * IPv6 destination address. Used for IPv6 MC routing.
+ * VCAP_KF_IP6_SIP: W128, sparx5: lpm
+ * IPv6 source address. Used for IPv6 MC routing.
+ * VCAP_KF_IP6_XIP: W128, sparx5: lpm
+ * IPv6 address. Used for IPv6 UC routing as well as IPv6 Source/Destination
+ * Guard.
* VCAP_KF_IP_MC_IS: W1, sparx5: is0, lan966x: is1
* Set if frame is IPv4 frame and frame's destination MAC address is an IPv4
* multicast address (0x01005E0 /25). Set if frame is IPv6 frame and frame's
@@ -352,8 +378,8 @@ enum vcap_keyfield_set {
* Set if frame is IPv4/IPv6 TCP or UDP frame (IP protocol/next header equals 6
* or 17)
* VCAP_KF_TYPE: sparx5 is0 W2, sparx5 is0 W1, sparx5 is2 W4, sparx5 is2 W2,
- * sparx5 es0 W1, sparx5 es2 W3, lan966x is1 W1, lan966x is1 W2, lan966x is2 W4,
- * lan966x is2 W2
+ * sparx5 es0 W1, sparx5 lpm W1, sparx5 es2 W3, lan966x is1 W1, lan966x is1 W2,
+ * lan966x is2 W4, lan966x is2 W2
* Keyset type id - set by the API
*/
@@ -387,6 +413,7 @@ enum vcap_key_field {
VCAP_KF_8021Q_VLAN_TAGGED_IS,
VCAP_KF_8021Q_VLAN_TAGS,
VCAP_KF_ACL_GRP_ID,
+ VCAP_KF_AFFIX,
VCAP_KF_ARP_ADDR_SPACE_OK_IS,
VCAP_KF_ARP_LEN_OK_IS,
VCAP_KF_ARP_OPCODE,
@@ -395,6 +422,7 @@ enum vcap_key_field {
VCAP_KF_ARP_SENDER_MATCH_IS,
VCAP_KF_ARP_TGT_MATCH_IS,
VCAP_KF_COSID_CLS,
+ VCAP_KF_DST_FLAG,
VCAP_KF_ES0_ISDX_KEY_ENA,
VCAP_KF_ETYPE,
VCAP_KF_ETYPE_LEN_IS,
@@ -408,7 +436,13 @@ enum vcap_key_field {
VCAP_KF_IF_IGR_PORT_MASK_RNG,
VCAP_KF_IF_IGR_PORT_MASK_SEL,
VCAP_KF_IF_IGR_PORT_SEL,
+ VCAP_KF_IP4_DIP,
VCAP_KF_IP4_IS,
+ VCAP_KF_IP4_SIP,
+ VCAP_KF_IP4_XIP,
+ VCAP_KF_IP6_DIP,
+ VCAP_KF_IP6_SIP,
+ VCAP_KF_IP6_XIP,
VCAP_KF_IP_MC_IS,
VCAP_KF_IP_PAYLOAD_5TUPLE,
VCAP_KF_IP_PAYLOAD_S1_IP6,
@@ -490,6 +524,8 @@ enum vcap_key_field {
/* Actionset names with origin information */
enum vcap_actionfield_set {
VCAP_AFS_NO_VALUE, /* initial value */
+ VCAP_AFS_ARP_ENTRY, /* sparx5 lpm X1 */
+ VCAP_AFS_ARP_PTR, /* sparx5 lpm X1 */
VCAP_AFS_BASE_TYPE, /* sparx5 is2 X3, sparx5 es2 X3, lan966x is2 X2 */
VCAP_AFS_CLASSIFICATION, /* sparx5 is0 X2 */
VCAP_AFS_CLASS_REDUCED, /* sparx5 is0 X1 */
@@ -506,6 +542,15 @@ enum vcap_actionfield_set {
* Logical ID for the entry. This ID is extracted together with the frame in the
* CPU extraction header. Only applicable to actions with CPU_COPY_ENA or
* HIT_ME_ONCE set.
+ * VCAP_AF_ARP_ENA: W1, sparx5: lpm
+ * Enable entry for address resolution usage.
+ * VCAP_AF_ARP_PTR: W11, sparx5: lpm
+ * Pointer to entry in ARP Table.
+ * VCAP_AF_ARP_PTR_REMAP_ENA: W1, sparx5: lpm
+ * If this bit is set, ARP_PTR is used to point to an entry in the ARP pointer
+ * remap table.
+ * VCAP_AF_ARP_VMID: W9, sparx5: lpm
+ * Routing lookup. Egress router leg (EVMID).
* VCAP_AF_CLS_VID_SEL: W3, sparx5: is0
* Controls the classified VID: 0: VID_NONE: No action. 1: VID_ADD: New VID =
* old VID + VID_VAL. 2: VID_REPLACE: New VID = VID_VAL. 3: VID_FIRST_TAG: New
@@ -562,6 +607,11 @@ enum vcap_actionfield_set {
* 7: Mapped using mapping table 3, otherwise use mapping table 2
* VCAP_AF_DSCP_VAL: W6, sparx5: is0/es0, lan966x: is1
* See DSCP_ENA.
+ * VCAP_AF_ECMP_CNT: W4, sparx5: lpm
+ * Number of equal cost, multiple paths routes to DIP.
+ * VCAP_AF_ENCAP_ID: W10, sparx5: lpm
+ * Index into REW:ENCAP_IP4 when encapsulating IP packet in IPv4. Disabled when
+ * set to 0.
* VCAP_AF_ES2_REW_CMD: W3, sparx5: es2
* Command forwarded to REW: 0: No action. 1: SWAP MAC addresses. 2: Do L2CP
* DMAC translation when entering or leaving a tunnel.
@@ -610,6 +660,12 @@ enum vcap_actionfield_set {
* 0: Forward based on PIPELINE_PT and FWD_SEL
* VCAP_AF_LRN_DIS: W1, sparx5: is2, lan966x: is2
* Setting this bit to 1 disables learning of frames hitting this action.
+ * VCAP_AF_MAC_LSB: W32, sparx5: lpm
+ * 32 least significant bits of MAC address. Used for ARP entry and/or
+ * (SMAC,SIP)/(DMAC,DIP) check.
+ * VCAP_AF_MAC_MSB: W16, sparx5: lpm
+ * 16 most significant bits of MAC address. Used for ARP entry and/or
+ * (SMAC,SIP)/(DMAC,DIP) check.
* VCAP_AF_MAP_IDX: W9, sparx5: is0
* Index for QoS mapping table lookup
* VCAP_AF_MAP_KEY: W3, sparx5: is0
@@ -715,9 +771,17 @@ enum vcap_actionfield_set {
* See QOS_ENA.
* VCAP_AF_REW_OP: W16, lan966x: is2
* Rewriter operation command.
+ * VCAP_AF_RGID: W3, sparx5: lpm
+ * Route Group ID. Used for SIP RPF check.
+ * VCAP_AF_RSDX: W12, sparx5: lpm
+ * Router Leg Service Index for ARP table entry.
* VCAP_AF_RT_DIS: W1, sparx5: is2
* If set, routing is disallowed. Only applies when IS_INNER_ACL is 0. See also
* IGR_ACL_ENA, EGR_ACL_ENA, and RLEG_STAT_IDX.
+ * VCAP_AF_SECUR_MATCH_MAC_ENA: W1, sparx5: lpm
+ * Enable Security MAC check.
+ * VCAP_AF_SECUR_MATCH_VMID_ENA: W1, sparx5: lpm
+ * Enable Security VMID check.
* VCAP_AF_SFID_ENA: W1, lan966x: is1
* If set, SFID_VAL is used to lookup ANA::SFID.
* VCAP_AF_SFID_VAL: W8, lan966x: is1
@@ -726,6 +790,8 @@ enum vcap_actionfield_set {
* If set, SGID_VAL is used to lookup ANA::SGID.
* VCAP_AF_SGID_VAL: W8, lan966x: is1
* Stream gate identifier.
+ * VCAP_AF_SIP_RPF_ENA: W1, sparx5: lpm
+ * Enable use for SIP RPF check.
* VCAP_AF_SWAP_MACS_ENA: W1, sparx5: es0
* This setting is only active when FWD_SEL = 1 or FWD_SEL = 2 and PIPELINE_ACT
* = LBK_ASM. 0: No action. 1: Swap MACs and clear bit 40 in new SMAC.
@@ -774,7 +840,7 @@ enum vcap_actionfield_set {
* VCAP_AF_TAG_C_VID_SEL: W2, sparx5: es0
* Selects VID for ES0 tag C. The resulting VID is termed C-TAG.VID. 0:
* Classified VID. 1: VID_C_VAL. 2: IFH.ENCAP.GVID. 3: Reserved.
- * VCAP_AF_TYPE: W1, sparx5: is0, lan966x: is1
+ * VCAP_AF_TYPE: sparx5 is0 W1, sparx5 lpm W2, lan966x is1 W1
* Actionset type id - Set by the API
* VCAP_AF_UNTAG_VID_ENA: W1, sparx5: es0
* Controls insertion of tag C. Untag or insert mode can be selected. See
@@ -798,12 +864,18 @@ enum vcap_actionfield_set {
* If set, use VLAN_POP_CNT as the number of VLAN tags to pop from the incoming
* frame. This number is used by the Rewriter. Otherwise, VLAN_POP_CNT from
* ANA:PORT:VLAN_CFG.VLAN_POP_CNT is used
+ * VCAP_AF_ZERO_DMAC_CPU_QU: W3, sparx5: lpm
+ * CPU queue used for CPU redirect if MAC address in ARP entry is all-zeros.
*/
/* Actionfield names */
enum vcap_action_field {
VCAP_AF_NO_VALUE, /* initial value */
VCAP_AF_ACL_ID,
+ VCAP_AF_ARP_ENA,
+ VCAP_AF_ARP_PTR,
+ VCAP_AF_ARP_PTR_REMAP_ENA,
+ VCAP_AF_ARP_VMID,
VCAP_AF_CLS_VID_SEL,
VCAP_AF_CNT_ID,
VCAP_AF_COPY_PORT_NUM,
@@ -823,6 +895,8 @@ enum vcap_action_field {
VCAP_AF_DSCP_ENA,
VCAP_AF_DSCP_SEL,
VCAP_AF_DSCP_VAL,
+ VCAP_AF_ECMP_CNT,
+ VCAP_AF_ENCAP_ID,
VCAP_AF_ES2_REW_CMD,
VCAP_AF_ESDX,
VCAP_AF_FWD_KILL_ENA,
@@ -839,6 +913,8 @@ enum vcap_action_field {
VCAP_AF_ISDX_VAL,
VCAP_AF_LOOP_ENA,
VCAP_AF_LRN_DIS,
+ VCAP_AF_MAC_LSB,
+ VCAP_AF_MAC_MSB,
VCAP_AF_MAP_IDX,
VCAP_AF_MAP_KEY,
VCAP_AF_MAP_LOOKUP_SEL,
@@ -874,11 +950,16 @@ enum vcap_action_field {
VCAP_AF_QOS_ENA,
VCAP_AF_QOS_VAL,
VCAP_AF_REW_OP,
+ VCAP_AF_RGID,
+ VCAP_AF_RSDX,
VCAP_AF_RT_DIS,
+ VCAP_AF_SECUR_MATCH_MAC_ENA,
+ VCAP_AF_SECUR_MATCH_VMID_ENA,
VCAP_AF_SFID_ENA,
VCAP_AF_SFID_VAL,
VCAP_AF_SGID_ENA,
VCAP_AF_SGID_VAL,
+ VCAP_AF_SIP_RPF_ENA,
VCAP_AF_SWAP_MACS_ENA,
VCAP_AF_TAG_A_DEI_SEL,
VCAP_AF_TAG_A_PCP_SEL,
@@ -901,6 +982,7 @@ enum vcap_action_field {
VCAP_AF_VID_VAL,
VCAP_AF_VLAN_POP_CNT,
VCAP_AF_VLAN_POP_CNT_ENA,
+ VCAP_AF_ZERO_DMAC_CPU_QU,
};
#endif /* __VCAP_AG_API__ */
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c
index 788c0728d7636..1ad2c44da8bd7 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c
@@ -4,7 +4,9 @@
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
+#include <linux/lcm.h>
#include <linux/types.h>
+#include <linux/minmax.h>
#include "vcap_api_private.h"
@@ -214,6 +216,13 @@ static void vcap_decode_field(u32 *stream, struct vcap_stream_iter *itr,
}
}
+/* The type_id field is always right after the typegroup bits, if it exists */
+static u8 vcap_find_stream_type_id(u32 *stream, u16 tg_width,
+ u16 typefld_width)
+{
+ return (stream[0] >> tg_width) & GENMASK(typefld_width - 1, 0);
+}
+
/* Verify that the type id in the stream matches the type id of the keyset */
static bool vcap_verify_keystream_keyset(struct vcap_control *vctrl,
enum vcap_type vt,
@@ -1339,8 +1348,10 @@ vcap_verify_actionstream_actionset(struct vcap_control *vctrl,
enum vcap_actionfield_set actionset)
{
const struct vcap_typegroup *tgt;
+ const struct vcap_field *typefld;
const struct vcap_field *fields;
const struct vcap_set *info;
+ u8 value = 0;
if (vcap_actionfield_count(vctrl, vt, actionset) == 0)
return false;
@@ -1363,8 +1374,11 @@ vcap_verify_actionstream_actionset(struct vcap_control *vctrl,
if (!fields)
return false;
- /* Later this will be expanded with a check of the type id */
- return true;
+ typefld = &fields[VCAP_AF_TYPE];
+ value = vcap_find_stream_type_id(actionstream,
+ tgt->width, typefld->width);
+
+ return value == info->type_id;
}
/* Find the subword width of the action typegroup that matches the stream data
@@ -2106,12 +2120,63 @@ static u32 vcap_set_rule_id(struct vcap_rule_internal *ri)
return ri->data.id;
}
+static void vcap_move_from_block(u32 base_addr,
+ struct vcap_rule_internal *block_first,
+ struct vcap_rule_internal *block_last,
+ int block_lcm, struct vcap_rule_move *move)
+{
+ int unaligned_offset;
+
+ if (!block_first || !block_last)
+ return;
+
+ move->addr = block_last->addr;
+ move->count = block_first->addr + block_first->size - block_last->addr;
+ /* Integer division rounds toward zero. We want to round toward +inf
+ * for the positive case (insertion) to ensure enough room.
+ */
+ unaligned_offset = (block_first->addr + block_first->size) - base_addr;
+ if (unaligned_offset > 0)
+ move->offset =
+ ((unaligned_offset + (block_lcm - 1)) / block_lcm) *
+ block_lcm;
+ else
+ move->offset = (unaligned_offset / block_lcm) * block_lcm;
+}
+
+static void vcap_move_rules_sw(struct vcap_admin *admin,
+ struct vcap_rule_internal *pos,
+ struct vcap_rule_move const *move)
+{
+ if (move->count == 0 || move->offset == 0)
+ return;
+
+ list_for_each_entry_continue(pos, &admin->rules, list)
+ pos->addr -= move->offset;
+}
+
+static int vcap_find_move_block_lcm(struct vcap_admin *admin,
+ struct vcap_rule_internal *pos)
+{
+ int block_lcm = 1;
+
+ list_for_each_entry_continue(pos, &admin->rules, list) {
+ block_lcm = lcm(block_lcm, pos->size);
+ if (pos->size <= 2)
+ break;
+ }
+
+ return block_lcm;
+}
+
static int vcap_insert_rule(struct vcap_rule_internal *ri,
struct vcap_rule_move *move)
{
+ struct vcap_rule_internal *duprule, *iter, *block_first = NULL,
+ *block_last;
int sw_count = ri->vctrl->vcaps[ri->admin->vtype].sw_count;
- struct vcap_rule_internal *duprule, *iter, *elem = NULL;
struct vcap_admin *admin = ri->admin;
+ int block_lcm;
u32 addr;
ri->sort_key = vcap_sort_key(sw_count, ri->size, ri->data.user,
@@ -2124,12 +2189,12 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,
*/
list_for_each_entry(iter, &admin->rules, list) {
if (ri->sort_key < iter->sort_key) {
- elem = iter;
+ block_first = iter;
break;
}
}
- if (!elem) {
+ if (!block_first) {
ri->addr = vcap_next_rule_addr(admin->last_used_addr, ri);
admin->last_used_addr = ri->addr;
@@ -2142,10 +2207,13 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,
return 0;
}
- /* Reuse the space of the current rule */
- addr = elem->addr + elem->size;
+ block_last = list_last_entry(&admin->rules, typeof(*ri), list);
+
+ /* Reuse the space and padding of the current rule */
+ addr = admin->last_valid_addr + 1;
+ if (!list_is_first(&block_first->list, &admin->rules))
+ addr = list_prev_entry(block_first, list)->addr;
ri->addr = vcap_next_rule_addr(addr, ri);
- addr = ri->addr;
/* Add a copy of the rule to the VCAP list */
duprule = vcap_dup_rule(ri, ri->state == VCAP_RS_DISABLED);
@@ -2153,29 +2221,34 @@ static int vcap_insert_rule(struct vcap_rule_internal *ri,
return PTR_ERR(duprule);
/* Add before the current entry */
- list_add_tail(&duprule->list, &elem->list);
-
- /* Update the current rule */
- elem->addr = vcap_next_rule_addr(addr, elem);
- addr = elem->addr;
-
- /* Update the address in the remaining rules in the list */
- list_for_each_entry_continue(elem, &admin->rules, list) {
- elem->addr = vcap_next_rule_addr(addr, elem);
- addr = elem->addr;
+ list_add_tail(&duprule->list, &block_first->list);
+
+ /* Collect block move info in struct move, to update VCAP HW later */
+ block_lcm = vcap_find_move_block_lcm(admin, duprule);
+ vcap_move_from_block(ri->addr, block_first, block_last, block_lcm,
+ move);
+
+ if ((int)block_last->addr - move->offset < admin->first_valid_addr) {
+ pr_err("%s:%d: No room for rule size: %u, %u\n", __func__,
+ __LINE__, duprule->size, admin->first_valid_addr);
+ list_del(&duprule->list);
+ vcap_free_rule(&duprule->data);
+ return -ENOSPC;
}
- /* Update the move info */
- move->addr = admin->last_used_addr;
- move->count = ri->addr - addr;
- move->offset = admin->last_used_addr - addr;
- admin->last_used_addr = addr;
+ /* Apply move to SW */
+ vcap_move_rules_sw(admin, duprule, move);
+ admin->last_used_addr =
+ list_last_entry(&admin->rules, typeof(*ri), list)->addr;
return 0;
}
static void vcap_move_rules(struct vcap_rule_internal *ri,
struct vcap_rule_move *move)
{
+ if (move->count == 0 || move->offset == 0)
+ return;
+
ri->vctrl->ops->move(ri->ndev, ri->admin, move->addr,
move->offset, move->count);
}
@@ -2285,8 +2358,7 @@ int vcap_add_rule(struct vcap_rule *rule)
__func__, __LINE__, ret);
goto out;
}
- if (move.count > 0)
- vcap_move_rules(ri, &move);
+ vcap_move_rules(ri, &move);
/* Set the counter to zero */
ret = vcap_write_counter(ri, &ctr);
@@ -2317,6 +2389,19 @@ int vcap_add_rule(struct vcap_rule *rule)
}
EXPORT_SYMBOL_GPL(vcap_add_rule);
+/* Validate and add rule to a VCAP instance */
+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto)
+{
+ int err;
+
+ err = vcap_val_rule(rule, l3_proto);
+ if (err)
+ return err;
+
+ return vcap_add_rule(rule);
+}
+EXPORT_SYMBOL_GPL(vcap_val_add_rule);
+
/* Allocate a new rule with the provided arguments */
struct vcap_rule *vcap_alloc_rule(struct vcap_control *vctrl,
struct net_device *ndev, int vcap_chain_id,
@@ -2501,59 +2586,52 @@ int vcap_mod_rule(struct vcap_rule *rule)
}
EXPORT_SYMBOL_GPL(vcap_mod_rule);
-/* Return the alignment offset for a new rule address */
-static int vcap_valid_rule_move(struct vcap_rule_internal *el, int offset)
-{
- return (el->addr + offset) % el->size;
-}
-
-/* Update the rule address with an offset */
-static void vcap_adjust_rule_addr(struct vcap_rule_internal *el, int offset)
-{
- el->addr += offset;
-}
-
/* Rules needs to be moved to fill the gap of the deleted rule */
static int vcap_fill_rule_gap(struct vcap_rule_internal *ri)
{
+ struct vcap_rule_internal *block_first = NULL, *block_last = NULL;
struct vcap_admin *admin = ri->admin;
- struct vcap_rule_internal *elem;
- struct vcap_rule_move move;
- int gap = 0, offset = 0;
+ struct net_device *ndev = ri->ndev;
+ struct vcap_rule_move move = {};
+ int block_lcm, addr;
- /* If the first rule is deleted: Move other rules to the top */
- if (list_is_first(&ri->list, &admin->rules))
- offset = admin->last_valid_addr + 1 - ri->addr - ri->size;
+ if (!list_is_last(&ri->list, &admin->rules)) {
+ block_first = list_next_entry(ri, list);
+ block_last = list_last_entry(&admin->rules, typeof(*ri), list);
+ }
+
+ addr = admin->last_valid_addr + 1;
+ if (!list_is_first(&ri->list, &admin->rules))
+ addr = list_prev_entry(ri, list)->addr;
- /* Locate gaps between odd size rules and adjust the move */
- elem = ri;
- list_for_each_entry_continue(elem, &admin->rules, list)
- gap += vcap_valid_rule_move(elem, ri->size);
+ block_lcm = vcap_find_move_block_lcm(admin, ri);
+ vcap_move_from_block(addr, block_first, block_last, block_lcm, &move);
- /* Update the address in the remaining rules in the list */
- elem = ri;
- list_for_each_entry_continue(elem, &admin->rules, list)
- vcap_adjust_rule_addr(elem, ri->size + gap + offset);
+ /* Apply move to SW representation */
+ vcap_move_rules_sw(admin, ri, &move);
- /* Update the move info */
- move.addr = admin->last_used_addr;
- move.count = ri->addr - admin->last_used_addr - gap;
- move.offset = -(ri->size + gap + offset);
+ /* Initialize space used by ri, as it may not be overwritten by move */
+ ri->vctrl->ops->init(ndev, admin, ri->addr, ri->size);
- /* Do the actual move operation */
+ /* Apply move to HW. The VCAP move command automatically disables
+ * (initializes) the source addresses, so no separate init is needed
+ * for the vacated positions.
+ */
vcap_move_rules(ri, &move);
- return gap + offset;
+ if (block_last)
+ return block_last->addr;
+
+ return addr;
}
/* Delete rule in a VCAP instance */
int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)
{
- struct vcap_rule_internal *ri, *elem;
+ struct vcap_rule_internal *ri;
struct vcap_admin *admin;
- int gap = 0, err;
+ int err;
- /* This will later also handle rule moving */
if (!ndev)
return -ENODEV;
err = vcap_api_check(vctrl);
@@ -2566,23 +2644,12 @@ int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)
admin = ri->admin;
- if (ri->addr > admin->last_used_addr)
- gap = vcap_fill_rule_gap(ri);
+ admin->last_used_addr = vcap_fill_rule_gap(ri);
/* Delete the rule from the list of rules and the cache */
list_del(&ri->list);
- vctrl->ops->init(ndev, admin, admin->last_used_addr, ri->size + gap);
vcap_free_rule(&ri->data);
- /* Update the last used address, set to default when no rules */
- if (list_empty(&admin->rules)) {
- admin->last_used_addr = admin->last_valid_addr + 1;
- } else {
- elem = list_last_entry(&admin->rules, struct vcap_rule_internal,
- list);
- admin->last_used_addr = elem->addr;
- }
-
vcap_unlock(admin);
return err;
}
@@ -3506,6 +3573,18 @@ int vcap_rule_mod_action_u32(struct vcap_rule *rule,
}
EXPORT_SYMBOL_GPL(vcap_rule_mod_action_u32);
+/* Modify a bit action with value in the rule */
+int vcap_rule_mod_action_bit(struct vcap_rule *rule,
+ enum vcap_action_field action,
+ enum vcap_bit val)
+{
+ struct vcap_client_actionfield_data data = {};
+
+ vcap_rule_set_action_bitsize(&data.u1, val);
+ return vcap_rule_mod_action(rule, action, VCAP_FIELD_BIT, &data);
+}
+EXPORT_SYMBOL_GPL(vcap_rule_mod_action_bit);
+
/* Drop keys in a keylist and any keys that are not supported by the keyset */
int vcap_filter_rule_keys(struct vcap_rule *rule,
enum vcap_key_field keylist[], int length,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.h b/drivers/net/ethernet/microchip/vcap/vcap_api.h
index 05b4b02e59ef5..011d1cb7b594c 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api.h
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api.h
@@ -22,7 +22,7 @@
#define VCAP_CID_INGRESS_L5 1500000 /* Ingress Stage 1 Lookup 5 */
#define VCAP_CID_PREROUTING_IPV6 3000000 /* Prerouting Stage */
-#define VCAP_CID_PREROUTING 6000000 /* Prerouting Stage */
+#define VCAP_CID_PREROUTING_L0 6000000 /* Prerouting Stage Lookup 0 */
#define VCAP_CID_INGRESS_STAGE2_L0 8000000 /* Ingress Stage 2 Lookup 0 */
#define VCAP_CID_INGRESS_STAGE2_L1 8100000 /* Ingress Stage 2 Lookup 1 */
@@ -41,7 +41,9 @@ enum vcap_user {
VCAP_USER_MRP,
VCAP_USER_CFM,
VCAP_USER_VLAN,
+ VCAP_USER_L3,
VCAP_USER_QOS,
+ /* permanent enabled users above here */
VCAP_USER_VCAP_UTIL,
VCAP_USER_TC,
VCAP_USER_TC_EXTRA,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
index cdf79e17ca547..3f17e1e76b7de 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_client.h
@@ -167,6 +167,8 @@ void vcap_free_rule(struct vcap_rule *rule);
int vcap_val_rule(struct vcap_rule *rule, u16 l3_proto);
/* Add rule to a VCAP instance */
int vcap_add_rule(struct vcap_rule *rule);
+/* Validate and add rule to a VCAP instance */
+int vcap_val_add_rule(struct vcap_rule *rule, u16 l3_proto);
/* Delete rule in a VCAP instance */
int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id);
/* Make a full copy of an existing rule with a new rule id */
@@ -266,6 +268,10 @@ int vcap_rule_mod_key_u32(struct vcap_rule *rule, enum vcap_key_field key,
int vcap_rule_mod_action_u32(struct vcap_rule *rule,
enum vcap_action_field action,
u32 value);
+/* Modify a bit action with value in the rule */
+int vcap_rule_mod_action_bit(struct vcap_rule *rule,
+ enum vcap_action_field action,
+ enum vcap_bit val);
/* Get a 32 bit key field value and mask from the rule */
int vcap_rule_get_key_u32(struct vcap_rule *rule, enum vcap_key_field key,
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
index e0c65c7ab23e1..36150822ce153 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c
@@ -40,7 +40,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,
value = (u8 *)(&data->u32.value);
mask = (u8 *)(&data->u32.mask);
- if (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP) {
+ if (key == VCAP_KF_L3_IP4_SIP || key == VCAP_KF_L3_IP4_DIP ||
+ key == VCAP_KF_IP4_XIP) {
out->prf(out->dst, "%pI4h/%pI4h", &data->u32.value,
&data->u32.mask);
} else if (key == VCAP_KF_ETYPE ||
@@ -88,7 +89,8 @@ static void vcap_debugfs_show_rule_keyfield(struct vcap_control *vctrl,
case VCAP_FIELD_U128:
value = data->u128.value;
mask = data->u128.mask;
- if (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP) {
+ if (key == VCAP_KF_L3_IP6_SIP || key == VCAP_KF_L3_IP6_DIP ||
+ key == VCAP_KF_IP6_XIP) {
u8 nvalue[16], nmask[16];
vcap_netbytes_copy(nvalue, data->u128.value,
@@ -133,7 +135,12 @@ vcap_debugfs_show_rule_actionfield(struct vcap_control *vctrl,
out->prf(out->dst, "%d", value[0]);
break;
case VCAP_FIELD_U32:
- fmsk = (1 << actionfield[action].width) - 1;
+ if (action == VCAP_AF_MAC_LSB || action == VCAP_AF_MAC_MSB) {
+ hex = true;
+ break;
+ }
+ fmsk = actionfield[action].width ?
+ GENMASK(actionfield[action].width - 1, 0) : 0;
val = *(u32 *)value;
out->prf(out->dst, "%u", val & fmsk);
break;
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c b/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c
index 83de384d3e3bd..675289001a001 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_api_kunit.c
@@ -1576,7 +1576,7 @@ static void vcap_api_rule_insert_in_order_test(struct kunit *test)
.lookups = 4,
.last_valid_addr = 3071,
.first_valid_addr = 0,
- .last_used_addr = 800,
+ .last_used_addr = 804,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
@@ -1584,15 +1584,28 @@ static void vcap_api_rule_insert_in_order_test(struct kunit *test)
},
};
+ struct vcap_rule_internal ri0 = {
+ .data = {
+ .id = 2001,
+ },
+ .addr = 804,
+ .size = 12,
+ .sort_key = vcap_sort_key(12, 12, 0, 0),
+ .admin = &admin,
+ .counter_id = 2002,
+ .vctrl = &test_vctrl,
+ };
+
vcap_test_api_init(&admin);
+ list_add_tail(&ri0.list, &admin.rules);
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 774);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 771);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 768);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 792);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 786);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 783);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 780);
vcap_del_rule(&test_vctrl, &test_netdev, 200);
vcap_del_rule(&test_vctrl, &test_netdev, 300);
@@ -1614,7 +1627,7 @@ static void vcap_api_rule_insert_reverse_order_test(struct kunit *test)
.lookups = 4,
.last_valid_addr = 3071,
.first_valid_addr = 0,
- .last_used_addr = 800,
+ .last_used_addr = 804,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
@@ -1622,40 +1635,54 @@ static void vcap_api_rule_insert_reverse_order_test(struct kunit *test)
},
};
struct vcap_rule_internal *elem;
- u32 exp_addr[] = {780, 774, 771, 768, 767};
+ u32 exp_addr[] = {804, 792, 786, 783, 780};
int idx;
+ /* Existing X12 rule at last_used_addr */
+ struct vcap_rule_internal ri0 = {
+ .data = {
+ .id = 2001,
+ },
+ .addr = 804,
+ .size = 12,
+ .sort_key = vcap_sort_key(12, 12, 0, 0),
+ .admin = &admin,
+ .counter_id = 2002,
+ .vctrl = &test_vctrl,
+ };
+
vcap_test_api_init(&admin);
+ list_add_tail(&ri0.list, &admin.rules);
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 200, 2, 798);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 200, 2, 802);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 795);
- KUNIT_EXPECT_EQ(test, 6, test_move_offset);
- KUNIT_EXPECT_EQ(test, 3, test_move_count);
- KUNIT_EXPECT_EQ(test, 798, test_move_addr);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 801);
+ KUNIT_EXPECT_EQ(test, 4, test_move_offset);
+ KUNIT_EXPECT_EQ(test, 2, test_move_count);
+ KUNIT_EXPECT_EQ(test, 802, test_move_addr);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 400, 6, 792);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 400, 6, 798);
KUNIT_EXPECT_EQ(test, 6, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
- KUNIT_EXPECT_EQ(test, 792, test_move_addr);
+ KUNIT_EXPECT_EQ(test, 798, test_move_addr);
- test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 50, 500, 12, 780);
- KUNIT_EXPECT_EQ(test, 18, test_move_offset);
+ test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 50, 500, 12, 792);
+ KUNIT_EXPECT_EQ(test, 12, test_move_offset);
KUNIT_EXPECT_EQ(test, 12, test_move_count);
- KUNIT_EXPECT_EQ(test, 786, test_move_addr);
+ KUNIT_EXPECT_EQ(test, 792, test_move_addr);
idx = 0;
list_for_each_entry(elem, &admin.rules, list) {
KUNIT_EXPECT_EQ(test, exp_addr[idx], elem->addr);
++idx;
}
- KUNIT_EXPECT_EQ(test, 768, admin.last_used_addr);
+ KUNIT_EXPECT_EQ(test, 780, admin.last_used_addr);
vcap_del_rule(&test_vctrl, &test_netdev, 500);
vcap_del_rule(&test_vctrl, &test_netdev, 400);
@@ -1775,7 +1802,7 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, 768, test_move_addr);
KUNIT_EXPECT_EQ(test, -6, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
- KUNIT_EXPECT_EQ(test, 768, test_init_start);
+ KUNIT_EXPECT_EQ(test, 774, test_init_start);
KUNIT_EXPECT_EQ(test, 6, test_init_count);
KUNIT_EXPECT_EQ(test, 774, admin.last_used_addr);
@@ -1785,8 +1812,8 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, 774, test_move_addr);
KUNIT_EXPECT_EQ(test, -4, test_move_offset);
KUNIT_EXPECT_EQ(test, 2, test_move_count);
- KUNIT_EXPECT_EQ(test, 774, test_init_start);
- KUNIT_EXPECT_EQ(test, 4, test_init_count);
+ KUNIT_EXPECT_EQ(test, 777, test_init_start);
+ KUNIT_EXPECT_EQ(test, 3, test_init_count);
KUNIT_EXPECT_EQ(test, 778, admin.last_used_addr);
test_init_rule_deletion();
@@ -1795,8 +1822,8 @@ static void vcap_api_rule_remove_in_middle_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, 778, test_move_addr);
KUNIT_EXPECT_EQ(test, -20, test_move_offset);
KUNIT_EXPECT_EQ(test, 2, test_move_count);
- KUNIT_EXPECT_EQ(test, 778, test_init_start);
- KUNIT_EXPECT_EQ(test, 20, test_init_count);
+ KUNIT_EXPECT_EQ(test, 780, test_init_start);
+ KUNIT_EXPECT_EQ(test, 12, test_init_count);
KUNIT_EXPECT_EQ(test, 798, admin.last_used_addr);
test_init_rule_deletion();
@@ -1856,11 +1883,11 @@ static void vcap_api_rule_remove_in_front_test(struct kunit *test)
ret = vcap_del_rule(&test_vctrl, &test_netdev, 400);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 786, test_move_addr);
- KUNIT_EXPECT_EQ(test, -8, test_move_offset);
+ KUNIT_EXPECT_EQ(test, -6, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
- KUNIT_EXPECT_EQ(test, 786, test_init_start);
- KUNIT_EXPECT_EQ(test, 8, test_init_count);
- KUNIT_EXPECT_EQ(test, 794, admin.last_used_addr);
+ KUNIT_EXPECT_EQ(test, 792, test_init_start);
+ KUNIT_EXPECT_EQ(test, 6, test_init_count);
+ KUNIT_EXPECT_EQ(test, 792, admin.last_used_addr);
vcap_del_rule(&test_vctrl, &test_netdev, 200);
vcap_del_rule(&test_vctrl, &test_netdev, 300);
diff --git a/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c b/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c
index 5dbfc0d0c369b..75819b49e1106 100644
--- a/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c
+++ b/drivers/net/ethernet/microchip/vcap/vcap_model_kunit.c
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: BSD-3-Clause
-/* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries.
+/* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP test model interface for kunit testing
*/
-/* This file is autogenerated by cml-utils 2023-02-10 11:16:00 +0100.
- * Commit ID: c30fb4bf0281cd4a7133bdab6682f9e43c872ada
+/* This file is autogenerated by cml-utils 2026-04-28 13:55:50 +0200.
+ * Commit ID: cd2494df16a1d3b14f650f720f6a8126b88b553a
*/
#include <linux/types.h>
|
| 3/2 | 2026/08/10 11:33 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 103205 output: 50 thoughts: 831 Thoughts: **Assessing the Fuzzing Potential of This Patch Series**
Okay, so I need to figure out if this patch series introduces functional changes to the kernel that are worth fuzzing. It's adding L3 routing offload support for the Microchip Sparx5 switch driver. That sounds interesting – new functionality always warrants a closer look. I see a new `sparx5_router.c` file, handling FIB and neighbor events, offloading them to the hardware using VCAP LPM and ARP tables. It also updates `sparx5_main.c` to initialize and deinitialize the router, modifies `sparx5_vcap_impl.c` to add `VCAP_TYPE_LPM`, and `sparx5_vcap_ag_api.c` for LPM keysets and actionsets, and also touches register definitions in several files (`sparx5_regs.c`, `lan969x_regs.c` etc.). This all indicates fairly substantial changes, and the network domain is often a source of interesting bugs.
The key question is, can this be fuzzed? This is a switch driver. Typically, switch drivers, especially ones like `mlxsw`, `sparx5`, `lan966x`, `lan969x`, rely on physical hardware. I need to be cautious about automatically assuming "yes" here. The instructions are pretty clear: "Return WorthFuzzing=false if the patch only contains... code in vendor-specific PCIe switch, SmartNIC, or GPU drivers... that require physical PCIe hardware cards not emulated in standard QEMU". So, is `sparx5` a PCIe switch driver? Let's check the source.
Right, let's look at `drivers/net/ethernet/microchip/sparx5/sparx5_main.c`. It's a platform driver, based on `static struct platform_driver mchp_sparx5_driver = { ... }`, and it binds to `microchip,sparx5-switch`. Okay, a platform driver. Can this be emulated or reached in QEMU? Usually, platform drivers for specific SoCs are not emulated in standard QEMU unless there is a specific machine model for the hardware, though that isn't impossible, of course.
The instructions specifically mention unreachable code paths in virtualized environments: "Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware". And also: "Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU".
I need to confirm if `sparx5` is fuzzed or if it needs actual hardware. I'd like to use `default_api:grepper` to see if there are any syzkaller descriptions for it, but the instructions say I can't.
Since this is an Ethernet switch SoC (Microchip Sparx5), it's a platform device, not a PCI device. While that's slightly different, it's still a specific SoC, and that means it's likely a similar situation. Can it be probed via some software emulation, or is it strictly tied to physical hardware and requires direct memory-mapped register access? Platform devices *can* sometimes be instantiated through device tree overlays, but if the driver directly accesses physical memory without emulation, it'll crash or fail to probe. Let's start by looking at `mchp_sparx5_probe` to see if it makes direct hardware accesses.
|
| 4/2 | 2026/08/10 11:33 | tool | grepper |
0mArgs:{
"Expression": "mchp_sparx5_probe",
"PathPrefix": "drivers/net/ethernet/microchip/sparx5/sparx5_main.c"
}Results: {
"Output": "drivers/net/ethernet/microchip/sparx5/sparx5_main.c=738=static void sparx5_forwarding_init(struct sparx5 *sparx5)\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-783-\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c:784:static int mchp_sparx5_probe(struct platform_device *pdev)\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-785-{\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c=1123=static struct platform_driver mchp_sparx5_driver = {\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c:1124:\t.probe = mchp_sparx5_probe,\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-1125-\t.remove = mchp_sparx5_remove,\n"
} |
| 5/2 | 2026/08/10 11:33 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 105165 output: 50 thoughts: 18 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Analyzing the Purpose of `mchp_sparx5_probe`** Alright, let's dive into the `mchp_sparx5_probe` function. My initial thought is to quickly ascertain its function. Given the naming convention, "mchp" likely indicates Microchip, a major player in embedded systems, specifically focusing on microcontrollers and related devices. "sparx5" probably refers to a particular Microchip component or a family of components. I'd anticipate the "probe" part to be indicative of a diagnostic or debugging utility. Therefore, I hypothesize that `mchp_sparx5_probe` is very likely an initialization or probing routine used within a driver for a Microchip component of the SPARX5 family, maybe a specific sensor, interface, or peripheral. My expert instinct would be to look for it within the relevant driver code. My next steps would involve looking into its call site, any associated data structures, and the overall context within the larger system. |
| 6/2 | 2026/08/10 11:33 | tool | read-file |
0mArgs:{
"File": "drivers/net/ethernet/microchip/sparx5/sparx5_main.c",
"FirstLine": 784,
"LineCount": 100
}Results: {
"Contents": " 784:\tstatic int mchp_sparx5_probe(struct platform_device *pdev)\n 785:\t{\n 786:\t\tstruct initial_port_config *configs, *config;\n 787:\t\tstruct device_node *np = pdev-\u003edev.of_node;\n 788:\t\tstruct device_node *ports, *portnp;\n 789:\t\tconst struct sparx5_ops *ops;\n 790:\t\tstruct reset_control *reset;\n 791:\t\tstruct sparx5 *sparx5;\n 792:\t\tint idx = 0, err = 0;\n 793:\t\n 794:\t\tif (!np \u0026\u0026 !pdev-\u003edev.platform_data)\n 795:\t\t\treturn -ENODEV;\n 796:\t\n 797:\t\tsparx5 = devm_kzalloc(\u0026pdev-\u003edev, sizeof(*sparx5), GFP_KERNEL);\n 798:\t\tif (!sparx5)\n 799:\t\t\treturn -ENOMEM;\n 800:\t\n 801:\t\tplatform_set_drvdata(pdev, sparx5);\n 802:\t\tsparx5-\u003epdev = pdev;\n 803:\t\tsparx5-\u003edev = \u0026pdev-\u003edev;\n 804:\t\tspin_lock_init(\u0026sparx5-\u003etx_lock);\n 805:\t\n 806:\t\tsparx5-\u003edata = device_get_match_data(sparx5-\u003edev);\n 807:\t\tif (!sparx5-\u003edata)\n 808:\t\t\treturn -EINVAL;\n 809:\t\n 810:\t\tregs = sparx5-\u003edata-\u003eregs;\n 811:\t\tops = sparx5-\u003edata-\u003eops;\n 812:\t\n 813:\t\t/* Do switch core reset if available */\n 814:\t\treset = devm_reset_control_get_optional_shared(\u0026pdev-\u003edev, \"switch\");\n 815:\t\tif (IS_ERR(reset))\n 816:\t\t\treturn dev_err_probe(\u0026pdev-\u003edev, PTR_ERR(reset),\n 817:\t\t\t\t\t \"Failed to get switch reset controller.\\n\");\n 818:\t\treset_control_reset(reset);\n 819:\t\n 820:\t\t/* Default values, some from DT */\n 821:\t\tsparx5-\u003ecoreclock = SPX5_CORE_CLOCK_DEFAULT;\n 822:\t\n 823:\t\tsparx5-\u003edebugfs_root = debugfs_create_dir(\"sparx5\", NULL);\n 824:\t\n 825:\t\tports = of_get_child_by_name(np, \"ethernet-ports\");\n 826:\t\tif (!ports) {\n 827:\t\t\tdev_err(sparx5-\u003edev, \"no ethernet-ports child node found\\n\");\n 828:\t\t\treturn -ENODEV;\n 829:\t\t}\n 830:\t\tsparx5-\u003eport_count = of_get_child_count(ports);\n 831:\t\n 832:\t\tconfigs = kzalloc_objs(struct initial_port_config, sparx5-\u003eport_count);\n 833:\t\tif (!configs) {\n 834:\t\t\terr = -ENOMEM;\n 835:\t\t\tgoto cleanup_pnode;\n 836:\t\t}\n 837:\t\n 838:\t\tfor_each_available_child_of_node(ports, portnp) {\n 839:\t\t\tstruct sparx5_port_config *conf;\n 840:\t\t\tstruct phy *serdes = NULL;\n 841:\t\t\tu32 portno;\n 842:\t\n 843:\t\t\terr = of_property_read_u32(portnp, \"reg\", \u0026portno);\n 844:\t\t\tif (err) {\n 845:\t\t\t\tdev_err(sparx5-\u003edev, \"port reg property error\\n\");\n 846:\t\t\t\tcontinue;\n 847:\t\t\t}\n 848:\t\t\tconfig = \u0026configs[idx];\n 849:\t\t\tconf = \u0026config-\u003econf;\n 850:\t\t\tconf-\u003espeed = SPEED_UNKNOWN;\n 851:\t\t\tconf-\u003ebandwidth = SPEED_UNKNOWN;\n 852:\t\t\terr = of_get_phy_mode(portnp, \u0026conf-\u003ephy_mode);\n 853:\t\t\tif (err) {\n 854:\t\t\t\tdev_err(sparx5-\u003edev, \"port %u: missing phy-mode\\n\",\n 855:\t\t\t\t\tportno);\n 856:\t\t\t\tcontinue;\n 857:\t\t\t}\n 858:\t\t\terr = of_property_read_u32(portnp, \"microchip,bandwidth\",\n 859:\t\t\t\t\t\t \u0026conf-\u003ebandwidth);\n 860:\t\t\tif (err) {\n 861:\t\t\t\tdev_err(sparx5-\u003edev, \"port %u: missing bandwidth\\n\",\n 862:\t\t\t\t\tportno);\n 863:\t\t\t\tcontinue;\n 864:\t\t\t}\n 865:\t\t\terr = of_property_read_u32(portnp, \"microchip,sd-sgpio\", \u0026conf-\u003esd_sgpio);\n 866:\t\t\tif (err)\n 867:\t\t\t\tconf-\u003esd_sgpio = ~0;\n 868:\t\t\telse\n 869:\t\t\t\tsparx5-\u003esd_sgpio_remapping = true;\n 870:\t\t\t/* There is no SerDes node for RGMII ports. */\n 871:\t\t\tif (!ops-\u003eis_port_rgmii(portno)) {\n 872:\t\t\t\tserdes = devm_of_phy_get(sparx5-\u003edev, portnp, NULL);\n 873:\t\t\t\tif (IS_ERR(serdes)) {\n 874:\t\t\t\t\terr = dev_err_probe(sparx5-\u003edev,\n 875:\t\t\t\t\t\t\t PTR_ERR(serdes),\n 876:\t\t\t\t\t\t\t \"port %u: missing serdes\\n\",\n 877:\t\t\t\t\t\t\t portno);\n 878:\t\t\t\t\tof_node_put(portnp);\n 879:\t\t\t\t\tgoto cleanup_config;\n 880:\t\t\t\t}\n 881:\t\t\t}\n 882:\t\t\tconfig-\u003eportno = portno;\n 883:\t\t\tconfig-\u003enode = portnp;\n"
} |
| 7/2 | 2026/08/10 11:33 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 107088 output: 39 thoughts: 839 Thoughts: **Reviewing the Sparx5 Patch: A Fuzzing Decision** Okay, so I'm presented with a patch that's already committed, specifically within the `drivers/net/ethernet/microchip/sparx5/` directory. My job is to determine if this change warrants fuzzing. The immediate context screams "vendor-specific switch driver". Let's think this through. The code seems to use the device tree, grabbing configuration details via `of_get_child_by_name`. Now, the instructions are very clear: avoid fuzzing code that's tied to hardware not emulated in standard environments. It cites specific examples like mlxsw, pds_core, and amdgpu, all things reliant on very specific hardware. My first thought is, is `sparx5` one of these "untouchables"? Yes, it is a Microchip Sparx5 switch driver, specifically an Ethernet switch SoC. The documentation notes the lack of QEMU emulation. The fact that the commit author is `syz-cluster` is interesting. The instructions state the patch is already applied. So, syzkaller must have fuzzed it, or it was somehow deemed reachable. My focus should be on the instructions: "Return WorthFuzzing=false if the patch only contains code in vendor-specific PCIe switch, SmartNIC, or GPU drivers... that require physical PCIe hardware cards not emulated in standard QEMU". Okay, is `sparx5` a PCIe switch? No, it's a platform device. But it's still a vendor-specific switch driver. I need to confirm if it *requires* physical hardware. The tool says I can't search syzkaller syzlang descriptions. I can't look for `syz_*` calls. So I'm limited in my tools. Thinking about it more abstractly, is this reachable? It's a platform driver, so it relies on the presence of a device tree node. Can syzkaller create those? Upstream, syzkaller's device tree injection isn't generally supported, so that's a hurdle. However, the driver accesses memory-mapped registers (`regs = sparx5->data->regs;` and then `spx5_wr` etc.). These registers represent physical memory. If this physical memory isn't backed by something in the emulation, accessing it results in a crash or probe failure. I'm going to have to check how `regs` is mapped to assess if this is truly the case. |
| 8/2 | 2026/08/10 11:33 | tool | grepper |
0mArgs:{
"Expression": "devm_ioremap",
"PathPrefix": "drivers/net/ethernet/microchip/sparx5/"
}Results: {
"Output": "drivers/net/ethernet/microchip/sparx5/sparx5_main.c=268=static int sparx5_create_targets(struct sparx5 *sparx5)\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-293-\t\t}\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c:294:\t\tiomem[idx] = devm_ioremap(sparx5-\u003edev,\ndrivers/net/ethernet/microchip/sparx5/sparx5_main.c-295-\t\t\t\t\t iores[idx]-\u003estart,\n"
} |
| 9/2 | 2026/08/10 11:33 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 108674 output: 27 thoughts: 258 Thoughts: **My Assessment of the Driver Patch** Okay, so I'm looking at this patch and my initial thought is the use of `devm_ioremap`. That's a red flag. If this driver gets loaded in a virtual machine environment without the actual hardware present, `devm_ioremap` *could* succeed if the physical address is valid. However, trying to read or write to that memory region would definitely trigger a bus error, a Data Abort on ARM specifically. Wait, is this for ARM? Yep, Sparx5, ARM64 SoC. Given that this driver *requires* the physical Sparx5 SoC and isn't emulated in standard QEMU, it pretty clearly falls into the category of those vendor-specific switch drivers that need the actual hardware to function. It's not a PCIe driver, it's a platform driver, but the rule applies here. It's essentially a vendor-specific switch driver that is going to be dependent on physical hardware that isn't emulated in standard QEMU. I should double-check for some software emulation, but I'm pretty sure. |
| 10/2 | 2026/08/10 11:33 | tool | grepper |
0mArgs:{
"Expression": "sparx5",
"PathPrefix": "drivers/net/"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 11846 lines.\nUse more precise expression if possible.\n\ndrivers/net/ethernet/microchip/Kconfig=61=source \"drivers/net/ethernet/microchip/lan966x/Kconfig\"\ndrivers/net/ethernet/microchip/Kconfig:62:source \"drivers/net/ethernet/microchip/sparx5/Kconfig\"\ndrivers/net/ethernet/microchip/Kconfig-63-source \"drivers/net/ethernet/microchip/vcap/Kconfig\"\n--\ndrivers/net/ethernet/microchip/Makefile=13=obj-$(CONFIG_LAN966X_SWITCH) += lan966x/\ndrivers/net/ethernet/microchip/Makefile:14:obj-$(CONFIG_SPARX5_SWITCH) += sparx5/\ndrivers/net/ethernet/microchip/Makefile-15-obj-$(CONFIG_VCAP) += vcap/\n--\ndrivers/net/ethernet/microchip/sparx5/Makefile-5-\ndrivers/net/ethernet/microchip/sparx5/Makefile:6:obj-$(CONFIG_SPARX5_SWITCH) += sparx5-switch.o\ndrivers/net/ethernet/microchip/sparx5/Makefile-7-\ndrivers/net/ethernet/microchip/sparx5/Makefile:8:sparx5-switch-y := sparx5_main.o sparx5_packet.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:9: sparx5_netdev.o sparx5_phylink.o sparx5_port.o sparx5_mactable.o sparx5_vlan.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:10: sparx5_switchdev.o sparx5_calendar.o sparx5_ethtool.o sparx5_fdma.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:11: sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:12: sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:13: sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile:14: sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_router.o\ndrivers/net/ethernet/microchip/sparx5/Makefile-15-\ndrivers/net/ethernet/microchip/sparx5/Makefile:16:sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o\ndrivers/net/ethernet/microchip/sparx5/Makefile:17:sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o\ndrivers/net/ethernet/microchip/sparx5/Makefile-18-\ndrivers/net/ethernet/microchip/sparx5/Makefile:19:sparx5-switch-$(CONFIG_LAN969X_SWITCH) += lan969x/lan969x_regs.o \\\ndrivers/net/ethernet/microchip/sparx5/Makefile-20-\t\t\t\t\t lan969x/lan969x.o \\\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-11-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:12:static const struct sparx5_main_io_resource lan969x_main_iomap[] = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-13-\t{ TARGET_CPU, 0xc0000, 0 }, /* 0xe00c0000 */\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-100-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:101:static struct sparx5_sdlb_group lan969x_sdlb_groups[LAN969X_SDLB_GRP_CNT] = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-102-\t{ 1000000000, 8192 / 2, 64 }, /* 1 G */\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c=109=static u32 lan969x_hsch_max_group_rate[LAN969X_HSCH_LEAK_GRP_CNT] = {\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-112-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:113:static struct sparx5_sdlb_group *lan969x_get_sdlb_group(int idx)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-114-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c=118=static u32 lan969x_get_hsch_max_group_rate(int grp)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-122-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:123:static u32 lan969x_get_dev_mode_bit(struct sparx5 *sparx5, int port)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-124-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-142-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:143:static u32 lan969x_port_dev_mapping(struct sparx5 *sparx5, int port)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-144-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-186-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:187:static int lan969x_port_mux_set(struct sparx5 *sparx5, struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:188:\t\t\t\tstruct sparx5_port_config *conf)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-189-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-198-\t\tinst = (portno - portno % 4) / 4;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:199:\t\tspx5_rmw(BIT(inst), BIT(inst), sparx5, PORT_CONF_QSGMII_ENA);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-200-\t\tbreak;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c=207=static irqreturn_t lan969x_ptp_irq_handler(int irq, void *args)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-209-\tint budget = SPARX5_MAX_PTP_ID;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:210:\tstruct sparx5 *sparx5 = args;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-211-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-214-\t\tstruct skb_shared_hwtstamps shhwtstamps;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:215:\t\tstruct sparx5_port *port;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-216-\t\tstruct timespec64 ts;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-220-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:221:\t\tval = spx5_rd(sparx5, PTP_TWOSTEP_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-222-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-235-\t\t/* Retrieve its associated skb */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:236:\t\tport = sparx5-\u003eports[txport];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-237-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-238-\t\t/* Retrieve the delay */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:239:\t\tdelay = spx5_rd(sparx5, PTP_TWOSTEP_STAMP_NSEC);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-240-\t\tdelay = PTP_TWOSTEP_STAMP_NSEC_NS_GET(delay);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-246-\t\t\t PTP_TWOSTEP_CTRL_PTP_NXT,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:247:\t\t\t sparx5, PTP_TWOSTEP_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-248-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:249:\t\tval = spx5_rd(sparx5, PTP_TWOSTEP_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-250-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-255-\t\t/* Read RX timestamping to get the ID */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:256:\t\tid = spx5_rd(sparx5, PTP_TWOSTEP_STAMP_NSEC);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-257-\t\tid \u003c\u003c= 8;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:258:\t\tid |= spx5_rd(sparx5, PTP_TWOSTEP_STAMP_SUBNS);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-259-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-273-\t\t\t PTP_TWOSTEP_CTRL_PTP_NXT,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:274:\t\t\t sparx5, PTP_TWOSTEP_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-275-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-278-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:279:\t\tspin_lock_irqsave(\u0026sparx5-\u003eptp_ts_id_lock, flags);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:280:\t\tsparx5-\u003eptp_skbs--;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:281:\t\tspin_unlock_irqrestore(\u0026sparx5-\u003eptp_ts_id_lock, flags);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-282-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-283-\t\t/* Get the h/w timestamp */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:284:\t\tsparx5_get_hwtimestamp(sparx5, \u0026ts, delay);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-285-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-295-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:296:static const struct sparx5_regs lan969x_regs = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-297-\t.tsize = lan969x_tsize,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-306-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:307:static const struct sparx5_consts lan969x_consts = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-308-\t.n_ports = 30,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-331-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:332:static const struct sparx5_ops lan969x_ops = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-333-\t.is_port_2g5 = \u0026lan969x_port_is_2g5,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-351-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c:352:const struct sparx5_match_data lan969x_desc = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c-353-\t.iomap = lan969x_main_iomap,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-9-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:10:#include \"../sparx5/sparx5_main.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:11:#include \"../sparx5/sparx5_regs.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:12:#include \"../sparx5/sparx5_vcap_impl.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-13-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-14-/* lan969x.c */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:15:extern const struct sparx5_match_data lan969x_desc;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-16-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h=19=extern const struct vcap_info lan969x_vcaps[];\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-21-/* lan969x_vcap_impl.c */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:22:extern const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-23-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h=62=static inline bool lan969x_port_is_rgmii(int portno)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-67-/* lan969x_calendar.c */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:68:int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:69:\t\t\t struct sparx5_calendar_data *data);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-70-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-71-/* lan969x_rgmii.c */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:72:int lan969x_port_config_rgmii(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:73:\t\t\t struct sparx5_port_config *conf);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-74-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-75-/* lan969x_fdma.c */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:76:int lan969x_fdma_init(struct sparx5 *sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:77:int lan969x_fdma_deinit(struct sparx5 *sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-78-int lan969x_fdma_napi_poll(struct napi_struct *napi, int weight);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h:79:int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x.h-80-\t\t struct net_device *dev);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c=69=static int lan969x_dsm_cal_get_speed(enum lan969x_dsm_cal_dev dev)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-76-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:77:int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:78:\t\t\t struct sparx5_calendar_data *data)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-79-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-85-\t/* Maximum bandwidth for this taxi */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:86:\ttaxi_bw = (128 * 1000000) / sparx5_clk_period(sparx5-\u003ecoreclock);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-87-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-92-\t\tu32 portno = data-\u003etaxi_ports[i];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:93:\t\tenum sparx5_cal_bw bw;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-94-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:95:\t\tbw = sparx5_get_port_cal_speed(sparx5, portno);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-96-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:97:\t\tif (portno \u003c sparx5-\u003edata-\u003econsts-\u003en_ports_all)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c:98:\t\t\tdata-\u003etaxi_speeds[i] = sparx5_cal_speed_to_value(bw);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c-99-\t\telse\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-7-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:8:#include \"../sparx5_main.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:9:#include \"../sparx5_main_regs.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:10:#include \"../sparx5_port.h\"\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-11-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-14-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:15:#define FDMA_PRIV(fdma) ((struct sparx5 *)((fdma)-\u003epriv))\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-16-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c=25=static int lan969x_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-27-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:28:\tstruct sparx5_rx *rx = \u0026FDMA_PRIV(fdma)-\u003erx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-29-\tstruct page *page;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-41-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:42:static int lan969x_fdma_get_next_dcb(struct sparx5_tx *tx)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-43-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-52-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:53:static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-54-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:55:\tstruct fdma *fdma = \u0026sparx5-\u003etx.fdma;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:56:\tstruct sparx5_tx_buf *db;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-57-\tunsigned long flags;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-59-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:60:\tspin_lock_irqsave(\u0026sparx5-\u003etx_lock, flags);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-61-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-62-\tfor (i = 0; i \u003c fdma-\u003en_dcbs; ++i) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:63:\t\tdb = \u0026sparx5-\u003etx.dbs[i];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-64-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-72-\t\tdb-\u003edev-\u003estats.tx_packets++;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:73:\t\tsparx5-\u003etx.packets++;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-74-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:75:\t\tdma_unmap_single(sparx5-\u003edev,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-76-\t\t\t\t db-\u003edma_addr,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-85-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:86:\tspin_unlock_irqrestore(\u0026sparx5-\u003etx_lock, flags);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-87-}\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-88-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:89:static void lan969x_fdma_free_pages(struct sparx5_rx *rx)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-90-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-99-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:100:static struct sk_buff *lan969x_fdma_rx_get_frame(struct sparx5 *sparx5,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:101:\t\t\t\t\t\t struct sparx5_rx *rx)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-102-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:103:\tconst struct sparx5_consts *consts = sparx5-\u003edata-\u003econsts;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-104-\tstruct fdma *fdma = \u0026rx-\u003efdma;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:105:\tstruct sparx5_port *port;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-106-\tstruct frame_info fi;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-113-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:114:\tsparx5_ifh_parse(sparx5, page_address(page), \u0026fi);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:115:\tport = fi.src_port \u003c consts-\u003en_ports ? sparx5-\u003eports[fi.src_port] :\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-116-\t\t\t\t\t NULL;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-132-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:133:\tsparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-134-\tskb-\u003eprotocol = eth_type_trans(skb, skb-\u003edev);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-135-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:136:\tif (test_bit(port-\u003eportno, sparx5-\u003ebridge_mask))\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-137-\t\tskb-\u003eoffload_fwd_mark = 1;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-149-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:150:static int lan969x_fdma_rx_alloc(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-151-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:152:\tstruct sparx5_rx *rx = \u0026sparx5-\u003erx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-153-\tstruct fdma *fdma = \u0026rx-\u003efdma;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-160-\t\t.nid = NUMA_NO_NODE,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:161:\t\t.dev = sparx5-\u003edev,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-162-\t\t.dma_dir = DMA_FROM_DEVICE,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-171-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:172:\terr = fdma_alloc_coherent(sparx5-\u003edev, fdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-173-\tif (err)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-182-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:183:static int lan969x_fdma_tx_alloc(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-184-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:185:\tstruct sparx5_tx *tx = \u0026sparx5-\u003etx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-186-\tstruct fdma *fdma = \u0026tx-\u003efdma;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-188-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:189:\ttx-\u003edbs = kzalloc_objs(struct sparx5_tx_buf, fdma-\u003en_dcbs);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-190-\tif (!tx-\u003edbs)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-192-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:193:\terr = fdma_alloc_coherent(sparx5-\u003edev, fdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-194-\tif (err) {\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-205-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:206:static void lan969x_fdma_rx_init(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-207-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:208:\tstruct fdma *fdma = \u0026sparx5-\u003erx.fdma;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-209-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-212-\tfdma-\u003en_dbs = 1;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:213:\tfdma-\u003epriv = sparx5;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-214-\tfdma-\u003esize = fdma_get_size(fdma);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-219-\t/* Fetch a netdev for SKB and NAPI use, any will do */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:220:\tfor (int idx = 0; idx \u003c sparx5-\u003edata-\u003econsts-\u003en_ports; ++idx) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:221:\t\tstruct sparx5_port *port = sparx5-\u003eports[idx];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-222-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-223-\t\tif (port \u0026\u0026 port-\u003endev) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:224:\t\t\tsparx5-\u003erx.ndev = port-\u003endev;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-225-\t\t\tbreak;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-229-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:230:static void lan969x_fdma_tx_init(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-231-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:232:\tstruct fdma *fdma = \u0026sparx5-\u003etx.fdma;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-233-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-236-\tfdma-\u003en_dbs = 1;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:237:\tfdma-\u003epriv = sparx5;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-238-\tfdma-\u003esize = fdma_get_size(fdma);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c=244=int lan969x_fdma_napi_poll(struct napi_struct *napi, int weight)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-245-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:246:\tstruct sparx5_rx *rx = container_of(napi, struct sparx5_rx, napi);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:247:\tstruct sparx5 *sparx5 = container_of(rx, struct sparx5, rx);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-248-\tint old_dcb, dcb_reload, counter = 0;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-253-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:254:\tlan969x_fdma_tx_clear_buf(sparx5, weight);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-255-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-260-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:261:\t\tskb = lan969x_fdma_rx_get_frame(sparx5, rx);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-262-\t\tif (!skb)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-288-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:289:\t\tsparx5_fdma_reload(sparx5, fdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-290-\t}\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-292-\tif (counter \u003c weight \u0026\u0026 napi_complete_done(napi, counter))\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:293:\t\tspx5_wr(0xff, sparx5, FDMA_INTR_DB_ENA);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-294-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-297-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:298:int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-299-\t\t struct net_device *dev)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-301-\tint next_dcb, needed_headroom, needed_tailroom, err;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:302:\tstruct sparx5_tx *tx = \u0026sparx5-\u003etx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-303-\tstruct fdma *fdma = \u0026tx-\u003efdma;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:304:\tstruct sparx5_tx_buf *db_buf;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-305-\tu64 status;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-324-\tdb_buf = \u0026tx-\u003edbs[next_dcb];\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:325:\tdb_buf-\u003edma_addr = dma_map_single(sparx5-\u003edev,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-326-\t\t\t\t\t skb-\u003edata,\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-328-\t\t\t\t\t DMA_TO_DEVICE);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:329:\tif (dma_mapping_error(sparx5-\u003edev, db_buf-\u003edma_addr))\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-330-\t\treturn -ENOMEM;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-349-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:350:\tsparx5_fdma_reload(sparx5, fdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-351-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-354-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:355:int lan969x_fdma_init(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-356-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:357:\tstruct sparx5_rx *rx = \u0026sparx5-\u003erx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-358-\tint err;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-359-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:360:\tlan969x_fdma_rx_init(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:361:\tlan969x_fdma_tx_init(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:362:\tsparx5_fdma_injection_mode(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-363-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:364:\terr = dma_set_mask_and_coherent(sparx5-\u003edev, DMA_BIT_MASK(64));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-365-\tif (err) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:366:\t\tdev_err(sparx5-\u003edev, \"Failed to set 64-bit FDMA mask\");\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-367-\t\treturn err;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-369-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:370:\terr = lan969x_fdma_rx_alloc(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-371-\tif (err) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:372:\t\tdev_err(sparx5-\u003edev, \"Failed to allocate RX buffers: %d\\n\",\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-373-\t\t\terr);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-376-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:377:\terr = lan969x_fdma_tx_alloc(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-378-\tif (err) {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:379:\t\tfdma_free_coherent(sparx5-\u003edev, \u0026rx-\u003efdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:380:\t\tdev_err(sparx5-\u003edev, \"Failed to allocate TX buffers: %d\\n\",\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-381-\t\t\terr);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-385-\t/* Reset FDMA state */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:386:\tspx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:387:\tspx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-388-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-391-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:392:int lan969x_fdma_deinit(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-393-{\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:394:\tstruct sparx5_rx *rx = \u0026sparx5-\u003erx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:395:\tstruct sparx5_tx *tx = \u0026sparx5-\u003etx;\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-396-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:397:\tsparx5_fdma_stop(sparx5);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:398:\tfdma_free_coherent(sparx5-\u003edev, \u0026tx-\u003efdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c:399:\tfdma_free_coherent(sparx5-\u003edev, \u0026rx-\u003efdma);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c-400-\tlan969x_fdma_free_pages(rx);\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c=43=static int lan969x_rgmii_get_speed_sel(int speed)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-50-/* Get the clock delay selector based on the clock delay in picoseconds. */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:51:static int lan969x_rgmii_get_clk_delay_sel(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-52-\t\t\t\t\t u32 delay_ps, u32 *clk_delay_sel)\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-77-\tdefault:\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:78:\t\tdev_err(port-\u003esparx5-\u003edev, \"Invalid RGMII delay: %u\", delay_ps);\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-79-\t\treturn -EINVAL;\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-85-/* Configure the RGMII tx clock frequency. */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:86:static void lan969x_rgmii_tx_clk_config(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:87:\t\t\t\t\tstruct sparx5_port_config *conf)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-88-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-100-\t\t HSIO_WRAP_RGMII_CFG_RGMII_RX_RST,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:101:\t\t port-\u003esparx5, HSIO_WRAP_RGMII_CFG(idx));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-102-}\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-104-/* Configure the RGMII port device. */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:105:static void lan969x_rgmii_port_device_config(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:106:\t\t\t\t\t struct sparx5_port_config *conf)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-107-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-122-\t\tDEVRGMII_MAC_ENA_CFG_TX_ENA_SET(1),\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:123:\t\tport-\u003esparx5, DEVRGMII_MAC_ENA_CFG(idx));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-124-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-128-\t\tDEVRGMII_MAC_IFG_CFG_RX_IFG2_SET(LAN969X_RGMII_IFG_RX2),\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:129:\t\tport-\u003esparx5, DEVRGMII_MAC_IFG_CFG(idx));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-130-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-132-\tspx5_wr(DEVRGMII_DEV_RST_CTRL_SPEED_SEL_SET(speed_sel),\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:133:\t\tport-\u003esparx5, DEVRGMII_DEV_RST_CTRL(idx));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-134-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-139-\t\tDEVRGMII_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(dotag),\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:140:\t\tport-\u003esparx5,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-141-\t\tDEVRGMII_MAC_TAGS_CFG(idx));\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-153- */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:154:static int lan969x_rgmii_delay_config(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:155:\t\t\t\t struct sparx5_port_config *conf)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-156-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-183-\t\t HSIO_WRAP_DLL_CFG_DLL_CLK_SEL,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:184:\t\t port-\u003esparx5, HSIO_WRAP_DLL_CFG(idx, 0));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-185-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-194-\t\t HSIO_WRAP_DLL_CFG_DLL_CLK_SEL,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:195:\t\t port-\u003esparx5, HSIO_WRAP_DLL_CFG(idx, 1));\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-196-\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-200-/* Configure GPIO's to be used as RGMII interface. */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:201:static void lan969x_rgmii_gpio_config(struct sparx5_port *port)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-202-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-205-\t/* Enable the RGMII on the GPIOs. */\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:206:\tspx5_wr(HSIO_WRAP_XMII_CFG_GPIO_XMII_CFG_SET(1), port-\u003esparx5,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-207-\t\tHSIO_WRAP_XMII_CFG(!idx));\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-209-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:210:int lan969x_port_config_rgmii(struct sparx5_port *port,\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c:211:\t\t\t struct sparx5_port_config *conf)\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_rgmii.c-212-{\n--\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c-5-\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c:6:const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[] = {\ndrivers/net/ethernet/microchip/sparx5/lan969x/lan969x_vcap_impl.c-7-\t{\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-9-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:10:#include \"sparx5_main_regs.h\"\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:11:#include \"sparx5_main.h\"\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-12-\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-25-/* Maps from taxis to port numbers */\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:26:static u32 sparx5_taxi_ports[SPX5_DSM_CAL_TAXIS][SPX5_DSM_CAL_MAX_DEVS_PER_TAXI] = {\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-27-\t{57, 12, 0, 1, 2, 16, 17, 18, 19, 20, 21, 22, 23},\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-36-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:37:static u32 sparx5_target_bandwidth(struct sparx5 *sparx5)\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-38-{\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:39:\tswitch (sparx5-\u003etarget_ct) {\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-40-\tcase SPX5_TARGET_CT_7546:\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-75-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:76:static u32 sparx5_clk_to_bandwidth(enum sparx5_core_clockfreq cclock)\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-77-{\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-87-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:88:u32 sparx5_cal_speed_to_value(enum sparx5_cal_bw speed)\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-89-{\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-101-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:102:static u32 sparx5_bandwidth_to_calendar(u32 bw)\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-103-{\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-117-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:118:enum sparx5_cal_bw sparx5_get_port_cal_speed(struct sparx5 *sparx5, u32 portno)\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-119-{\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:120:\tstruct sparx5_port *port;\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-121-\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:122:\tif (portno \u003e= sparx5-\u003edata-\u003econsts-\u003en_ports) {\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-123-\t\t/* Internal ports */\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-124-\t\tif (portno ==\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:125:\t\t\t sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0) ||\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-126-\t\t portno ==\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:127:\t\t\t sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1)) {\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-128-\t\t\t/* Equals 1.25G */\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-130-\t\t} else if (portno ==\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:131:\t\t\t sparx5_get_internal_port(sparx5, SPX5_PORT_VD0)) {\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-132-\t\t\t/* IPMC only idle BW */\n--\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c-134-\t\t} else if (portno ==\ndrivers/net/ethernet/microchip/sparx5/sparx5_calendar.c:135:\t\t\t sparx5_get_internal_port(sparx5, SPX5_PORT_VD1)) {\n\n"
} |
| 11/2 | 2026/08/10 11:33 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 127902 output: 96 thoughts: 410 Thoughts: **WorthFuzzing=false: Sparx5 Switch Driver** Okay, let's break this down. This driver is a classic hardware-specific switch driver, a Microchip Sparx5 driver. I can see it's using `devm_ioremap` and `spx5_wr`/`spx5_rd` for memory access, which are clear indicators of direct hardware interaction. The instructions are very explicit: we shouldn't consider patches if they're limited to vendor-specific drivers that need actual hardware that isn't emulated by standard QEMU. Even though it's a platform driver and not a PCIe one, the principle still applies. This Sparx5 driver *absolutely* falls into that category. It's a vendor-specific switch, and it needs the physical Sparx5 hardware to function. QEMU and GCE can't help me here. This code is unreachable in a virtualized environment. Just to be thorough, I should check for any software emulation, just in case. Something like a `mac80211_hwsim` equivalent for this Sparx5 driver would be ideal. But, of course, there isn't one. And I need to consider that, while Syzkaller may have applied the patch, it won't be able to exercise the hardware-specific code paths. The instructions reiterate that we're explicitly excluding anything impossible to reach in virtualization, even with software-emulated hardware. So, the conclusion is clear. The patch adds L3 routing offload support to a Microchip Sparx5 switch driver. This needs physical Sparx5 hardware. I can't reach it in QEMU or GCE. It's a `WorthFuzzing=false` situation. Time to use the `set-results` tool. |
| 12/2 | 2026/08/10 11:34 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds L3 routing offload support for the Microchip Sparx5 switch driver. This is a vendor-specific hardware switch driver that requires physical hardware (Sparx5 SoC) and is not emulated in standard QEMU. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds L3 routing offload support for the Microchip Sparx5 switch driver. This is a vendor-specific hardware switch driver that requires physical hardware (Sparx5 SoC) and is not emulated in standard QEMU. Therefore, the code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
} |
| 13/1 | 2026/08/10 11:34 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|