diff mbox series

[meta-arago,scarthgap,v2] meta-arago-distro: ethtool: Add patch to dump CPSW registers for K3 SoCs

Message ID 20250711022944.2633417-1-a-limaye@ti.com
State Not Applicable
Delegated to: Ryan Eatmon
Headers show
Series [meta-arago,scarthgap,v2] meta-arago-distro: ethtool: Add patch to dump CPSW registers for K3 SoCs | expand

Commit Message

Aniket Limaye July 11, 2025, 2:29 a.m. UTC
Add patch to support for TI K3 CPSW register and ALE table dump.

Signed-off-by: Aniket Limaye <a-limaye@ti.com>
---
v2: Fixed encodings in patchfile
---
 .../ethtool/ethtool-arago.inc                 |   8 +
 ...k3-cpsw-registers-and-ale-table-dump.patch | 574 ++++++++++++++++++
 .../ethtool/ethtool_%.bbappend                |   4 +
 3 files changed, 586 insertions(+)
 create mode 100644 meta-arago-distro/recipes-extended/ethtool/ethtool-arago.inc
 create mode 100644 meta-arago-distro/recipes-extended/ethtool/ethtool/pretty-add-support-for-ti-k3-cpsw-registers-and-ale-table-dump.patch
 create mode 100644 meta-arago-distro/recipes-extended/ethtool/ethtool_%.bbappend
diff mbox series

Patch

diff --git a/meta-arago-distro/recipes-extended/ethtool/ethtool-arago.inc b/meta-arago-distro/recipes-extended/ethtool/ethtool-arago.inc
new file mode 100644
index 00000000..5fd05a85
--- /dev/null
+++ b/meta-arago-distro/recipes-extended/ethtool/ethtool-arago.inc
@@ -0,0 +1,8 @@ 
+PR:append = ".arago0"
+
+FILESEXTRAPATHS:prepend := "${THISDIR}/ethtool:"
+
+SRC_URI:append = " \
+                  file://pretty-add-support-for-ti-k3-cpsw-registers-and-ale-table-dump.patch \
+                  "
+
diff --git a/meta-arago-distro/recipes-extended/ethtool/ethtool/pretty-add-support-for-ti-k3-cpsw-registers-and-ale-table-dump.patch b/meta-arago-distro/recipes-extended/ethtool/ethtool/pretty-add-support-for-ti-k3-cpsw-registers-and-ale-table-dump.patch
new file mode 100644
index 00000000..a14089b4
--- /dev/null
+++ b/meta-arago-distro/recipes-extended/ethtool/ethtool/pretty-add-support-for-ti-k3-cpsw-registers-and-ale-table-dump.patch
@@ -0,0 +1,574 @@ 
+From: Chintan Vankar <c-vankar@ti.com>
+Subject: [PATCH] pretty: Add support for TI K3 CPSW registers and ALE table dump
+Date: Thu, 3 Jul 2025 12:02:46 +0530
+
+Add support to dump CPSW registers and ALE table for the CPSW instances on
+K3 SoCs that are configured using the am65-cpsw-nuss.c device-driver in
+Linux.
+
+Upstream-Status: Submitted [https://lore.kernel.org/all/20250705134807.3514891-1-c-vankar@ti.com/]
+Signed-off-by: Chintan Vankar <c-vankar@ti.com>
+---
+ Makefile.am      |   2 +-
+ am65-cpsw-nuss.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++
+ ethtool.c        |   1 +
+ internal.h       |   3 +
+ 4 files changed, 515 insertions(+), 1 deletion(-)
+ create mode 100644 am65-cpsw-nuss.c
+
+diff --git a/Makefile.am b/Makefile.am
+index b9e06ad..fe6afcb 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -23,7 +23,7 @@ ethtool_SOURCES += \
+ 		  smsc911x.c at76c50x-usb.c sfc.c stmmac.c	\
+ 		  sff-common.c sff-common.h sfpid.c sfpdiag.c	\
+ 		  ixgbevf.c tse.c vmxnet3.c qsfp.c qsfp.h fjes.c lan78xx.c \
+-		  igc.c cmis.c cmis.h bnxt.c cpsw.c lan743x.c hns3.c
++		  igc.c cmis.c cmis.h bnxt.c cpsw.c lan743x.c hns3.c am65-cpsw-nuss.c
+ endif
+ 
+ if ENABLE_BASH_COMPLETION
+diff --git a/am65-cpsw-nuss.c b/am65-cpsw-nuss.c
+new file mode 100644
+index 0000000..de8e3e9
+--- /dev/null
++++ b/am65-cpsw-nuss.c
+@@ -0,0 +1,510 @@
++// SPDX-License-Identifier: GPL-2.0-only OR MIT
++/* Code to dump registers and ALE table for the CPSW instances on K3 SoCs that are configured using
++ * the am65-cpsw-nuss device-driver in Linux.
++ *
++ * Copyright (C) 2025 Texas Instruments
++ * Author: Chintan Vankar <c-vankar@ti.com>
++ */
++
++#include <stdio.h>
++#include <string.h>
++
++#include "internal.h"
++
++#define ALE_ENTRY_BITS          74
++#define ALE_ENTRY_WORDS         DIV_ROUND_UP(ALE_ENTRY_BITS, 32)
++
++#define ALE_ENTRY_FREE		0x0
++#define ALE_ENTRY_ADDR		0x1
++#define ALE_ENTRY_VLAN		0x2
++#define ALE_ENTRY_VLAN_ADDR	0x3
++
++#define BIT(nr)			(1 << (nr))
++#define BITMASK(bits)		(BIT(bits) - 1)
++
++/* ALE word specifiers */
++#define NUM_ALE_WORDS	2
++#define ALE_WORD_LEN	32
++
++/* MAC address specifiers */
++#define MAC_START_BIT	40
++#define MAC_OCTET_LEN	8
++#define NUM_MAC_OCTET	6
++
++/* RTL version specifiers */
++#define RTL_VERSION_MASK	0xF800
++#define CPSW2G_RTL_VERSION	0x3800
++#define CPSW3G_RTL_VERSION	0x0
++
++/* OUI address uses format xx:xx:xx, use OUI shift as 16 bits and MASK as 0xFF to parse the same*/
++#define OUI_ADDR_SHIFT		16
++#define OUI_ADDR_MASK		0xFF
++
++/* VLAN entry specifiers */
++#define VLAN_INNER_ENTRY	0x0
++#define VLAN_OUTER_ENTRY	0x2
++#define VLAN_ETHERTYPE_ENTRY	0x4
++#define VLAN_IPV4_ENTRY		0x6
++#define VLAN_IPV6_ENTRY_MASK	0x1
++
++/* VLAN Inner/Outer table entry MASKs and SHIFTs*/
++#define NOLEARN_FLAG_SHIFT		2
++#define NOLEARN_FLAG_MASK		0x1FF
++#define INGRESS_CHECK_SHIFT		1
++#define INGRESS_CHECK_MASK		0x1
++#define VLAN_ID_SHIFT			16
++#define VLAN_ID_MASK			0xFFF
++#define NOFRAG_FLAG_SHIFT_2G		12
++#define NOFRAG_FLAG_MASK_2G		0x1
++#define NOFRAG_FLAG_SHIFT		15
++#define NOFRAG_FLAG_MASK		0x1
++#define REG_MASK_SHIFT			4
++#define REG_MASK_MASK			0x1FF
++#define PKT_EGRESS_W1_MASK		0x1
++#define PKT_EGRESS_W1_OFFSET		512
++#define PKT_EGRESS_SHIFT		24
++#define PKT_EGRESS_MASK_2G		0x3
++#define PKT_EGRESS_MASK			0x1FF
++#define UNREG_MASK_SHIFT_2G		20
++#define UNREG_MASK_MASK_2G		0x7
++#define UNREG_MASK_SHIFT		12
++#define UNREG_MASK_MASK			0x1FF
++#define NXT_HDR_CTRL_SHIFT_2G		19
++#define NXT_HDR_CTRL_MASK_2G		0x1
++#define NXT_HDR_CTRL_SHIFT		23
++#define NXT_HDR_CTRL_MASK		0x1
++#define VLAN_MEMBER_LIST_MASK_2G	0x3
++#define VLAN_MEMBER_LIST_MASK		0x1FF
++
++/* VLAN IPv4 entry MASKs and SHIFTs*/
++#define IPV4_ADDR_OCT1_SHIFT	24
++#define IPV4_ADDR_OCT2_SHIFT	16
++#define IPV4_ADDR_OCT3_SHIFT	8
++#define IPV4_ADDR_MASK		0xFF
++
++/* VLAN IPv6 entry MASKs and SHIFTs*/
++#define IPV6_HIGH_ENTRY_FLAG	0x40
++#define IPV6_IGNMCBITS_MASK	0xFF
++#define IPV6_HADDR_W1_SHIFT	12
++#define IPV6_HADDR_W1_MASK_1	0xFFFF
++#define IPV6_HADDR_W1_MASK_2	0xFFF
++#define IPV6_HADDR_W0_SHIFT_1	28
++#define IPV6_HADDR_W0_MASK_1	0xF
++#define IPV6_HADDR_W0_SHIFT_2	12
++#define IPV6_HADDR_W0_MASK_2	0xFFFF
++#define IPV6_LADDR_W2_SHIFT	4
++#define IPV6_LADDR_W2_MAKS	0xF
++#define IPV6_LADDR_W1_SHIFT	16
++#define IPV6_LADDR_W1_MASK_1	0xFFF
++#define IPV6_LADDR_W1_MASK	0xFFFF
++#define IPV6_LADDR_W0_SHIFT	16
++#define IPV6_LADDR_W0_MASK	0xFFFF
++
++/**
++ * Since there are different instances of CPSW (namely cpsw2g, cpsw3g, cpsw5g and cpsw9g)
++ * some register offsets differ to get some parameters for ALE table, parse rtl_version
++ * from ALE_MOD_VER register to determine which instance is being used.
++ */
++u32 rtl_version;
++
++static inline int cpsw_ale_get_field(u32 *ale_entry, u32 start, u32 bits)
++{
++	int idx;
++
++	idx    = start / ALE_WORD_LEN;
++	start -= idx * ALE_WORD_LEN;
++
++	/**
++	 * ALE words are stored in order word2, word1 and word0, flip the word to parse in numeric
++	 * order
++	 */
++	idx    = NUM_ALE_WORDS - idx; /* flip */
++	return (ale_entry[idx] >> start) & BITMASK(bits);
++}
++
++#define DEFINE_ALE_FIELD(name, start, bits)				\
++static inline int cpsw_ale_get_##name(u32 *ale_entry)			\
++{									\
++	return cpsw_ale_get_field(ale_entry, start, bits);		\
++}
++
++DEFINE_ALE_FIELD(entry_type,		60,	2)
++DEFINE_ALE_FIELD(vlan_id,		48,	12)
++DEFINE_ALE_FIELD(mcast_state,		62,	2)
++DEFINE_ALE_FIELD(port_mask,		66,	9)
++DEFINE_ALE_FIELD(super,			65,	1)
++DEFINE_ALE_FIELD(agable,		62,	1)
++DEFINE_ALE_FIELD(touched,		63,	1)
++DEFINE_ALE_FIELD(ucast_type,		62,	2)
++DEFINE_ALE_FIELD(port_num,		66,	4)
++DEFINE_ALE_FIELD(port_num_2g,		66,	1)
++DEFINE_ALE_FIELD(port_num_3g,		66,	2)
++DEFINE_ALE_FIELD(blocked,		65,	1)
++DEFINE_ALE_FIELD(secure,		64,	1)
++DEFINE_ALE_FIELD(oui_entry,		62,	2)
++DEFINE_ALE_FIELD(oui_addr,		4,	24)
++DEFINE_ALE_FIELD(mcast,			40,	1)
++DEFINE_ALE_FIELD(vlan_entry_type,	62,	3)
++DEFINE_ALE_FIELD(ethertype,		0,	16)
++DEFINE_ALE_FIELD(ipv4_addr,		0,	32)
++DEFINE_ALE_FIELD(ingress_bits,		65,	5)
++DEFINE_ALE_FIELD(ipv6_addr_low,		0,	60)
++DEFINE_ALE_FIELD(ipv6_addr_mid,		63,	8)
++DEFINE_ALE_FIELD(ipv6_addr_high,	0,	60)
++DEFINE_ALE_FIELD(entry_word0,		0,	32)
++DEFINE_ALE_FIELD(entry_word1,		32,	32)
++DEFINE_ALE_FIELD(entry_word2,		64,	12)
++
++static inline void cpsw_ale_get_addr(u32 *ale_entry, u8 *addr)
++{
++	int i;
++
++	for (i = 0; i < NUM_MAC_OCTET; i++)
++		addr[i] = cpsw_ale_get_field(ale_entry, MAC_START_BIT - MAC_OCTET_LEN * i,
++					     MAC_OCTET_LEN);
++}
++
++struct k3_cpsw_regdump_hdr {
++	u32 module_id;
++	u32 len;
++};
++
++enum {
++	K3_CPSW_REGDUMP_MOD_NUSS = 1,
++	K3_CPSW_REGDUMP_MOD_RGMII_STATUS = 2,
++	K3_CPSW_REGDUMP_MOD_MDIO = 3,
++	K3_CPSW_REGDUMP_MOD_CPSW = 4,
++	K3_CPSW_REGDUMP_MOD_CPSW_P0 = 5,
++	K3_CPSW_REGDUMP_MOD_CPSW_PN = 6,
++	K3_CPSW_REGDUMP_MOD_CPSW_CPTS = 7,
++	K3_CPSW_REGDUMP_MOD_CPSW_ALE = 8,
++	K3_CPSW_REGDUMP_MOD_CPSW_ALE_TBL = 9,
++	K3_CPSW_REGDUMP_MOD_LAST,
++};
++
++static const char *mod_names[K3_CPSW_REGDUMP_MOD_LAST] = {
++	[K3_CPSW_REGDUMP_MOD_NUSS] = "cpsw-nuss",
++	[K3_CPSW_REGDUMP_MOD_RGMII_STATUS] = "cpsw-nuss-rgmii-status",
++	[K3_CPSW_REGDUMP_MOD_MDIO] = "cpsw-nuss-mdio",
++	[K3_CPSW_REGDUMP_MOD_CPSW] = "cpsw-nu",
++	[K3_CPSW_REGDUMP_MOD_CPSW_P0] = "cpsw-nu-p0",
++	[K3_CPSW_REGDUMP_MOD_CPSW_PN] = "cpsw-nu-pn",
++	[K3_CPSW_REGDUMP_MOD_CPSW_CPTS] = "cpsw-nu-cpts",
++	[K3_CPSW_REGDUMP_MOD_CPSW_ALE] = "cpsw-nu-ale",
++	[K3_CPSW_REGDUMP_MOD_CPSW_ALE_TBL] = "cpsw-nu-ale-tbl",
++};
++
++static void cpsw_ale_dump_oui_entry(int index, u32 *ale_entry)
++{
++	u32 oui_addr;
++
++	oui_addr = cpsw_ale_get_oui_addr(ale_entry);
++
++	fprintf(stdout, "%d: Type: OUI Unicast\n, \tOUI = %02x:%02x:%02x\n",
++		index, (oui_addr >> OUI_ADDR_SHIFT) & OUI_ADDR_MASK,
++		(oui_addr >> OUI_ADDR_SHIFT) & OUI_ADDR_MASK, oui_addr & OUI_ADDR_MASK);
++}
++
++static void cpsw_ale_dump_addr(int index, u32 *ale_entry)
++{
++	u8 addr[NUM_MAC_OCTET];
++
++	cpsw_ale_get_addr(ale_entry, addr);
++
++	if (cpsw_ale_get_mcast(ale_entry)) {
++		static const char * const str_mcast_state[] = {"Forwarding",
++							       "Blocking/Forwarding/Learning",
++							       "Learning/Forwarding",
++							       "Forwarding"};
++		u16 port_mask = cpsw_ale_get_port_mask(ale_entry);
++		u8 state = cpsw_ale_get_mcast_state(ale_entry);
++		u8 super = cpsw_ale_get_super(ale_entry);
++
++		fprintf(stdout, "%d: Type: Multicast\n \tAddress = %02x:%02x:%02x:%02x:%02x:%02x, Multicast_State = %s, %sSuper, port_mask = 0x%x\n",
++			index, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
++			str_mcast_state[state], super ? "" : "No ", port_mask);
++	} else {
++		static const char * const s_ucast_type[] = {"Persistent", "Untouched", "OUI",
++							    "Touched"};
++		u8 ucast_type = cpsw_ale_get_ucast_type(ale_entry);
++		u8 port_num = cpsw_ale_get_port_num(ale_entry);
++		u8 blocked = cpsw_ale_get_blocked(ale_entry);
++		u8 touched = cpsw_ale_get_touched(ale_entry);
++		u8 secure = cpsw_ale_get_secure(ale_entry);
++		u8 agable = cpsw_ale_get_agable(ale_entry);
++
++		fprintf(stdout, "%d: Type: Unicast\n \tUpdated Address = %02x:%02x:%02x:%02x:%02x:%02x, Unicast Type = %s, Port_num = 0x%x, Secure: %d, Blocked: %d, Touch = %d, Agable = %d\n",
++			index, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
++			s_ucast_type[ucast_type], port_num, secure, blocked, touched, agable);
++	}
++}
++
++static void cpsw_ale_dump_inner_vlan_entry(int index, u32 *ale_entry)
++{
++	u32 vlan_entry_word0 = cpsw_ale_get_entry_word0(ale_entry);
++	u32 vlan_entry_word1 = cpsw_ale_get_entry_word1(ale_entry);
++	u16 vlan_entry_word2 = cpsw_ale_get_entry_word2(ale_entry);
++
++	fprintf(stdout, "%d: Type: Inner VLAN\n \tNolearn Mask = 0x%x, Ingress Check = %d\n",
++		index, (vlan_entry_word2 >> NOLEARN_FLAG_SHIFT) & NOLEARN_FLAG_MASK,
++		(vlan_entry_word2 >> INGRESS_CHECK_SHIFT) & INGRESS_CHECK_MASK);
++
++	if (rtl_version == CPSW2G_RTL_VERSION) {
++		fprintf(stdout, "\tVLAN ID = %d, No Frag = %d, Registered Mask = 0x%x\n",
++			(vlan_entry_word1 >> VLAN_ID_SHIFT) & VLAN_ID_MASK,
++			(vlan_entry_word1 >> NOFRAG_FLAG_SHIFT_2G) & NOFRAG_FLAG_MASK_2G,
++			(vlan_entry_word1 >> REG_MASK_SHIFT) & REG_MASK_MASK);
++
++		fprintf(stdout, "\tForce Untagged Packet Egress = 0x%x, Unregistered Mask = 0x%x, Limit Next Header Control = %d, Members = 0x%x\n",
++			(vlan_entry_word0 >> PKT_EGRESS_SHIFT) & PKT_EGRESS_MASK_2G,
++			(vlan_entry_word0 >> UNREG_MASK_SHIFT_2G) & UNREG_MASK_MASK_2G,
++			(vlan_entry_word0 >> NXT_HDR_CTRL_SHIFT_2G) & NXT_HDR_CTRL_MASK_2G,
++			(vlan_entry_word0 & VLAN_MEMBER_LIST_MASK_2G));
++	} else {
++		fprintf(stdout, "\tVLAN ID = %d, Registered Mask = 0x%x, No Frag = %d\n",
++			(vlan_entry_word1 >> VLAN_ID_SHIFT) & VLAN_ID_MASK,
++			(vlan_entry_word1 >> REG_MASK_SHIFT) & REG_MASK_MASK,
++			(vlan_entry_word1 >> NOFRAG_FLAG_SHIFT) & NOFRAG_FLAG_MASK);
++
++		fprintf(stdout, "\tForce Untagged Packet Egress = 0x%x, Limit Next Header Control = %d, Unregistered Mask = 0x%x, Members = 0x%x\n",
++			(vlan_entry_word1 & PKT_EGRESS_W1_MASK) * PKT_EGRESS_W1_OFFSET +
++			((vlan_entry_word0 >> PKT_EGRESS_SHIFT) & PKT_EGRESS_MASK),
++			(vlan_entry_word0 >> NXT_HDR_CTRL_SHIFT) & NXT_HDR_CTRL_MASK,
++			(vlan_entry_word0 >> UNREG_MASK_SHIFT) & UNREG_MASK_MASK,
++			(vlan_entry_word0 & VLAN_MEMBER_LIST_MASK));
++	}
++}
++
++static void cpsw_ale_dump_outer_vlan_entry(int index, u32 *ale_entry)
++{
++	u32 vlan_entry_word0 = cpsw_ale_get_entry_word0(ale_entry);
++	u32 vlan_entry_word1 = cpsw_ale_get_entry_word1(ale_entry);
++	u16 vlan_entry_word2 = cpsw_ale_get_entry_word2(ale_entry);
++
++	fprintf(stdout, "%d: Type: Outer VLAN\n \tNolearn Mask = 0x%x, Ingress Check = %d\n",
++		index, (vlan_entry_word2 >> NOLEARN_FLAG_SHIFT) & NOLEARN_FLAG_MASK,
++		(vlan_entry_word2 >> INGRESS_CHECK_SHIFT) & INGRESS_CHECK_MASK);
++
++	if (rtl_version == CPSW2G_RTL_VERSION) {
++		fprintf(stdout, "\tVLAN ID = %d, No Frag = %d, Registered Mask = 0x%x\n",
++			(vlan_entry_word1 >> VLAN_ID_SHIFT) & VLAN_ID_MASK,
++			(vlan_entry_word1 >> NOFRAG_FLAG_SHIFT_2G) & NOFRAG_FLAG_MASK_2G,
++			(vlan_entry_word1 >> REG_MASK_SHIFT) & REG_MASK_MASK);
++
++		fprintf(stdout, "\tForce Untagged Packet Egress = 0x%x, Unregistered Mask = 0x%x, Limit Next Header Control = %d, Members = 0x%x\n",
++			(vlan_entry_word0 >> PKT_EGRESS_SHIFT) & PKT_EGRESS_MASK_2G,
++			(vlan_entry_word0 >> UNREG_MASK_SHIFT_2G) & UNREG_MASK_MASK_2G,
++			(vlan_entry_word0 >> NXT_HDR_CTRL_SHIFT_2G) & NXT_HDR_CTRL_MASK_2G,
++			(vlan_entry_word0 & VLAN_MEMBER_LIST_MASK_2G));
++	} else {
++		fprintf(stdout, "\tVLAN ID = %d, No Frag = %d, Registered Mask = 0x%x\n",
++			(vlan_entry_word1 >> VLAN_ID_SHIFT) & VLAN_ID_MASK,
++			(vlan_entry_word1 >> NOFRAG_FLAG_SHIFT) & NOFRAG_FLAG_MASK,
++			(vlan_entry_word1 >> REG_MASK_SHIFT) & REG_MASK_MASK);
++
++		fprintf(stdout, "\tForce Untagged Packet Egress = 0x%x, Limit Next Header Control = %d, Unregistered Mask = 0x%x Members = 0x%x\n",
++			(vlan_entry_word1 & PKT_EGRESS_W1_MASK) * PKT_EGRESS_W1_OFFSET +
++			((vlan_entry_word0 >> PKT_EGRESS_SHIFT) & PKT_EGRESS_MASK),
++			(vlan_entry_word0 >> NXT_HDR_CTRL_SHIFT) & NXT_HDR_CTRL_MASK,
++			(vlan_entry_word0 >> UNREG_MASK_SHIFT) & UNREG_MASK_MASK,
++			(vlan_entry_word0 & VLAN_MEMBER_LIST_MASK));
++	}
++}
++
++static void cpsw_ale_dump_ethertype_entry(int index, u32 *ale_entry)
++{
++	u16 ethertype = cpsw_ale_get_ethertype(ale_entry);
++
++	fprintf(stdout, "%d: Type: VLAN Ethertype\n \tEthertype = 0x%x\n", index, ethertype);
++}
++
++static void cpsw_ale_dump_ipv4_entry(int index, u32 *ale_entry)
++{
++	u8 ingress_bits = cpsw_ale_get_ingress_bits(ale_entry);
++	u32 ipv4_addr = cpsw_ale_get_ipv4_addr(ale_entry);
++
++	fprintf(stdout, "%d: Type: VLAN IPv4\n \tIngress Bits: 0x%x IPv4 Address = %u.%u.%u.%u\n",
++		index, ingress_bits, ipv4_addr >> IPV4_ADDR_OCT1_SHIFT & IPV4_ADDR_MASK,
++		ipv4_addr >> IPV4_ADDR_OCT2_SHIFT & IPV4_ADDR_MASK,
++		ipv4_addr >> IPV4_ADDR_OCT3_SHIFT & IPV4_ADDR_MASK, ipv4_addr & IPV4_ADDR_MASK);
++}
++
++static void cpsw_ale_dump_ipv6_entry(int index, u32 *ale_entry)
++{
++	u32 vlan_entry_word0 = cpsw_ale_get_entry_word0(ale_entry);
++	u32 vlan_entry_word1 = cpsw_ale_get_entry_word1(ale_entry);
++	u16 vlan_entry_word2 = cpsw_ale_get_entry_word2(ale_entry);
++
++	if (index & IPV6_HIGH_ENTRY_FLAG) {
++		fprintf(stdout, "%d: Type: VLAN IPv6 Higher Entry (Lower Bit entry at %04u)\n \tIgnored Multicast bits: 0x%x, IPv6 Address (Bits [127:68]) = %04x:%03x%01x:%04x:%03x\n",
++			index, (index & (~IPV6_HIGH_ENTRY_FLAG)),
++			vlan_entry_word2 & IPV6_IGNMCBITS_MASK,
++			(vlan_entry_word1 >> IPV6_HADDR_W1_SHIFT) & IPV6_HADDR_W1_MASK_1,
++			vlan_entry_word1 & IPV6_HADDR_W1_MASK_2,
++			(vlan_entry_word0 >> IPV6_HADDR_W0_SHIFT_1) & IPV6_HADDR_W0_MASK_1,
++			(vlan_entry_word0 >> IPV6_HADDR_W0_SHIFT_2) & IPV6_HADDR_W0_MASK_2,
++			vlan_entry_word0 & IPV6_HADDR_W0_MASK_2);
++	} else {
++		fprintf(stdout, "%d: Type: VLAN IPv6 Lower Entry (Higher Bit entry at %04u)\n \tIPv6 Address (Bits [127:68]) = %01x:%01x%03x:%04x:%04x:%04x\n",
++			index, (index | IPV6_HIGH_ENTRY_FLAG),
++			(vlan_entry_word2 >> IPV6_LADDR_W2_SHIFT) & IPV6_LADDR_W2_MAKS,
++			vlan_entry_word2 & IPV6_LADDR_W2_MAKS,
++			(vlan_entry_word1 >> IPV6_LADDR_W1_SHIFT) & IPV6_LADDR_W1_MASK_1,
++			vlan_entry_word1 & IPV6_LADDR_W1_MASK,
++			(vlan_entry_word0 >> IPV6_LADDR_W0_SHIFT) & IPV6_LADDR_W0_MASK,
++			vlan_entry_word0 & IPV6_LADDR_W0_MASK);
++	}
++}
++
++static void cpsw_ale_dump_vlan_addr(int index, u32 *ale_entry)
++{
++	u8 addr[NUM_MAC_OCTET];
++	int vlan = cpsw_ale_get_vlan_id(ale_entry);
++
++	cpsw_ale_get_addr(ale_entry, addr);
++	if (cpsw_ale_get_mcast(ale_entry)) {
++		static const char * const str_mcast_state[] = {"Forwarding",
++							       "Blocking/Forwarding/Learning",
++							       "Learning/Forwarding",
++							       "Forwarding"};
++		u16 port_mask = cpsw_ale_get_port_mask(ale_entry);
++		u8 state = cpsw_ale_get_mcast_state(ale_entry);
++		u8 super = cpsw_ale_get_super(ale_entry);
++
++		fprintf(stdout, "%d: Type: Multicast\n \tVID = %d, Address = %02x:%02x:%02x:%02x:%02x:%02x, Multicast_state = %s, %s Super, port_mask = 0x%x\n",
++			index, vlan, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
++			str_mcast_state[state], super ? "" : "No ", port_mask);
++	} else {
++		static const char * const s_ucast_type[] = {"Persistent", "Untouched", "OUI",
++							    "Touched"};
++		u8 ucast_type = cpsw_ale_get_ucast_type(ale_entry);
++		u8 blocked = cpsw_ale_get_blocked(ale_entry);
++		u8 touched = cpsw_ale_get_touched(ale_entry);
++		u8 secure = cpsw_ale_get_secure(ale_entry);
++		u8 agable = cpsw_ale_get_agable(ale_entry);
++
++		int port_num;
++
++		if (rtl_version == CPSW2G_RTL_VERSION)
++			port_num = cpsw_ale_get_port_num_2g(ale_entry);
++		else if (rtl_version == CPSW3G_RTL_VERSION)
++			port_num = cpsw_ale_get_port_num_3g(ale_entry);
++		else
++			port_num = cpsw_ale_get_port_num(ale_entry);
++
++		fprintf(stdout, "%d: Type: Unicast\n \tVID = %d, Address = %02x:%02x:%02x:%02x:%02x:%02x, Unicast_type = %s, port_num = 0x%x, Secure = %d, Blocked = %d, Touch = %d, Agable = %d\n",
++			index, vlan, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
++			s_ucast_type[ucast_type], port_num, secure, blocked, touched, agable);
++	}
++}
++
++void cpsw_dump_ale(struct k3_cpsw_regdump_hdr *ale_hdr, u32 *ale_pos)
++{
++	int i, ale_entries;
++
++	if (!ale_hdr)
++		return;
++
++	ale_entries = (ale_hdr->len - sizeof(struct k3_cpsw_regdump_hdr)) /
++		      ALE_ENTRY_WORDS / sizeof(u32);
++
++	printf("Number of ALE entries: %d\n", ale_entries);
++	ale_pos += 2;
++	for (i = 0; i < ale_entries; i++) {
++		int type;
++
++		type = cpsw_ale_get_entry_type(ale_pos);
++
++		switch (type) {
++		case ALE_ENTRY_FREE:
++			break;
++
++		case ALE_ENTRY_ADDR:
++			u32 oui_entry = cpsw_ale_get_oui_addr(ale_pos);
++
++			if (oui_entry == 0x2)
++				cpsw_ale_dump_oui_entry(i, ale_pos);
++			else
++				cpsw_ale_dump_addr(i, ale_pos);
++			break;
++
++		case ALE_ENTRY_VLAN:
++			u32 vlan_entry_type = cpsw_ale_get_vlan_entry_type(ale_pos);
++
++			if (vlan_entry_type == VLAN_INNER_ENTRY)
++				cpsw_ale_dump_inner_vlan_entry(i, ale_pos);
++			else if (vlan_entry_type == VLAN_OUTER_ENTRY)
++				cpsw_ale_dump_outer_vlan_entry(i, ale_pos);
++			else if (vlan_entry_type == VLAN_ETHERTYPE_ENTRY)
++				cpsw_ale_dump_ethertype_entry(i, ale_pos);
++			else if (vlan_entry_type == VLAN_IPV4_ENTRY)
++				cpsw_ale_dump_ipv4_entry(i, ale_pos);
++			else if (vlan_entry_type & VLAN_IPV6_ENTRY_MASK)
++				cpsw_ale_dump_ipv6_entry(i, ale_pos);
++			break;
++
++		case ALE_ENTRY_VLAN_ADDR:
++			cpsw_ale_dump_vlan_addr(i, ale_pos);
++			break;
++
++		default:
++			break;
++		}
++
++		ale_pos += ALE_ENTRY_WORDS;
++	}
++}
++
++int am65_cpsw_dump_regs(struct  ethtool_drvinfo *info __maybe_unused,
++			struct ethtool_regs *regs)
++{
++	struct k3_cpsw_regdump_hdr *dump_hdr, *ale_hdr = NULL;
++	u32 *reg = (u32 *)regs->data, *ale_pos;
++	u32 mod_id;
++	int i, regdump_len = info->regdump_len;
++
++	fprintf(stdout, "K3 CPSW dump version: %d, len: %d\n",
++		regs->version, info->regdump_len);
++	fprintf(stdout, "(Missing registers in memory space can be considered as zero valued)\n");
++
++	/* Line break before register dump */
++	fprintf(stdout, "--------------------------------------------------------------------\n");
++	i = 0;
++	do {
++		u32 *tmp, j;
++		u32 num_items;
++
++		dump_hdr = (struct k3_cpsw_regdump_hdr *)reg;
++		mod_id = dump_hdr->module_id;
++
++		num_items = dump_hdr->len / sizeof(u32);
++
++		if (mod_id == K3_CPSW_REGDUMP_MOD_CPSW_ALE)
++			rtl_version = reg[3] & RTL_VERSION_MASK;
++
++		if (mod_id == K3_CPSW_REGDUMP_MOD_CPSW_ALE_TBL) {
++			ale_hdr = dump_hdr;
++			ale_pos = reg;
++			break;
++		}
++
++		fprintf(stdout, "%s regdump: number of Registers:(%d)\n",
++			mod_names[mod_id], num_items - 2);
++		tmp = reg;
++		/* Values are stored in pair as reg_offset-reg_val, hence parse the same way*/
++		for (j = 2; j < num_items; j += 2) {
++			if (tmp[j + 1] != 0x0)
++				fprintf(stdout, "%08x:reg(%08X)\n", tmp[j], tmp[j + 1]);
++		}
++
++		reg += num_items;
++		i += dump_hdr->len;
++	} while (i < regdump_len);
++
++	/* Adding a boundary in between Register dump and ALE table */
++	fprintf(stdout, "--------------------------\n");
++
++	cpsw_dump_ale(ale_hdr, ale_pos);
++
++	return 0;
++};
+diff --git a/ethtool.c b/ethtool.c
+index 3ac15a7..a383eb6 100644
+--- a/ethtool.c
++++ b/ethtool.c
+@@ -1162,6 +1162,7 @@ static const struct {
+ 	{ "fsl_enetc", fsl_enetc_dump_regs },
+ 	{ "fsl_enetc_vf", fsl_enetc_dump_regs },
+ 	{ "hns3", hns3_dump_regs },
++	{ "am65-cpsw-nuss", am65_cpsw_dump_regs },
+ };
+ #endif
+ 
+diff --git a/internal.h b/internal.h
+index 4b994f5..81212b4 100644
+--- a/internal.h
++++ b/internal.h
+@@ -410,4 +410,7 @@ int cpsw_dump_regs(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
+ /* Microchip Ethernet Controller */
+ int lan743x_dump_regs(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
+ 
++/* TI K3 CPSW Ethernet Switch */
++int am65_cpsw_dump_regs(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
++
+ #endif /* ETHTOOL_INTERNAL_H__ */
+-- 
+2.34.1
+
diff --git a/meta-arago-distro/recipes-extended/ethtool/ethtool_%.bbappend b/meta-arago-distro/recipes-extended/ethtool/ethtool_%.bbappend
new file mode 100644
index 00000000..3b388151
--- /dev/null
+++ b/meta-arago-distro/recipes-extended/ethtool/ethtool_%.bbappend
@@ -0,0 +1,4 @@ 
+ETHTOOL_ARAGO = ""
+ETHTOOL_ARAGO:arago = "ethtool-arago.inc"
+
+require ${ETHTOOL_ARAGO}