瀏覽代碼

net: hsr/prp: add procfs interface for lredev config/stats

Currently it is not clear what interface to be used for accepting
configuration set/get or lre stats request or node table dump
request from user space such as that from snmpd. Currently
ip/udp/tcp mibs of mibii uses procfs for stats and such. So this
patch introduces the procfs interface at the upper layer hsr/prp
device files to allow lre configuration or display stats or node
table entries. procfs files are created under the
/proc/<interface-name> folder so that multiple hsr/prp
devices can co-exists. This patch also moves the node_table dump
from debugfs to procfs so that same can be used for snmpd interface.

The format of print to console is kept same as the current debugfs
output format expected by snmpd so that same text parser can be
re-used at snmpd even after this migration. It is expected that
this interface may not be acceptable for the upstream version of
the driver. So this has to be re-visited later.

Signed-off-by: Murali Karicheri <m-karicheri2@ti.com>
Murali Karicheri 6 年之前
父節點
當前提交
23f6574ec5
共有 6 個文件被更改,包括 745 次插入145 次删除
  1. 1 0
      net/hsr-prp/Makefile
  2. 32 126
      net/hsr-prp/hsr_prp_debugfs.c
  3. 19 4
      net/hsr-prp/hsr_prp_device.c
  4. 25 13
      net/hsr-prp/hsr_prp_forward.c
  5. 37 2
      net/hsr-prp/hsr_prp_main.h
  6. 631 0
      net/hsr-prp/hsr_prp_proc.c

+ 1 - 0
net/hsr-prp/Makefile

@@ -9,3 +9,4 @@ hsr-prp-y		:= hsr_prp_main.o hsr_main.o prp_main.o \
 			   hsr_prp_netlink.o hsr_netlink.o prp_netlink.o \
 			   hsr_prp_slave.o hsr_prp_forward.o
 hsr-prp-$(CONFIG_DEBUG_FS) += hsr_prp_debugfs.o
+hsr-prp-$(CONFIG_PROC_FS) += hsr_prp_proc.o

+ 32 - 126
net/hsr-prp/hsr_prp_debugfs.c

@@ -20,138 +20,54 @@
 #include "hsr_prp_main.h"
 #include "hsr_prp_framereg.h"
 
-static void print_mac_address(struct seq_file *sfp, unsigned char *mac)
-{
-	seq_printf(sfp, "%02x:%02x:%02x:%02x:%02x:%02x:",
-		   mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-}
-
-/* hsr_prp_node_table_show - Formats and prints node_table entries */
-static int
-hsr_prp_node_table_show(struct seq_file *sfp, void *data)
-{
-	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
-	struct hsr_prp_node *node;
-
-	seq_puts(sfp, "Node Table entries\n");
-	seq_puts(sfp, "MAC-Address-A,   MAC-Address-B, time_in[A], ");
-	seq_puts(sfp, "time_in[B], Address-B port");
-	if (priv->prot_version == PRP_V1)
-		seq_puts(sfp, ", san_a, san_b\n");
-	else
-		seq_puts(sfp, "\n");
-	rcu_read_lock();
-	list_for_each_entry_rcu(node, &priv->node_db, mac_list) {
-		/* skip self node */
-		if (hsr_prp_addr_is_self(priv, node->macaddress_A))
-			continue;
-		print_mac_address(sfp, &node->macaddress_A[0]);
-		seq_puts(sfp, " ");
-		print_mac_address(sfp, &node->macaddress_B[0]);
-		seq_printf(sfp, "0x%lx, ", node->time_in[HSR_PRP_PT_SLAVE_A]);
-		seq_printf(sfp, "0x%lx ", node->time_in[HSR_PRP_PT_SLAVE_B]);
-		seq_printf(sfp, "0x%x", node->addr_B_port);
-
-		if (priv->prot_version == PRP_V1)
-			seq_printf(sfp, ", %x, %x\n", node->san_a, node->san_b);
-		else
-			seq_puts(sfp, "\n");
-	}
-	rcu_read_unlock();
-	return 0;
-}
-
-/* hsr_prp_node_table_open - Open the node_table file
- *
- * Description:
- * This routine opens a debugfs file node_table of specific hsr
- * or prp device
+/* hsr_prp_lre_info_show - Formats and prints debug info in the device
  */
 static int
-hsr_prp_node_table_open(struct inode *inode, struct file *filp)
-{
-	return single_open(filp, hsr_prp_node_table_show, inode->i_private);
-}
-
-static const struct file_operations hsr_prp_node_table_fops = {
-	.owner	= THIS_MODULE,
-	.open	= hsr_prp_node_table_open,
-	.read	= seq_read,
-	.llseek = seq_lseek,
-	.release = single_release,
-};
-
-/* hsr_prp_stats_show - Formats and prints stats in the device
- */
-static int
-hsr_prp_stats_show(struct seq_file *sfp, void *data)
+hsr_prp_lre_info_show(struct seq_file *sfp, void *data)
 {
 	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
-	struct hsr_prp_port *master;
-
-	rcu_read_lock();
-	master = hsr_prp_get_port(priv, HSR_PRP_PT_MASTER);
-	rcu_read_unlock();
-
-	seq_puts(sfp, "LRE Stats entries\n");
-	seq_printf(sfp, "cnt_tx_a = %d\n", priv->lre_stats.cnt_tx_a);
-	seq_printf(sfp, "cnt_tx_b = %d\n", priv->lre_stats.cnt_tx_b);
-	/* actually lre_tx_c is whatever sent to the application interface. So
-	 * same as rx_packets
-	 */
-	seq_printf(sfp, "cnt_tx_c = %d\n", priv->lre_stats.cnt_tx_c);
+	bool prp = priv->prot_version > HSR_V1;
+
+	seq_puts(sfp, "LRE debug information\n");
+	seq_printf(sfp, "Protocol : %s\n", prp ? "PRP" : "HSR");
+	seq_printf(sfp, "net_id: %d\n", priv->net_id);
+	seq_printf(sfp, "Rx Offloaded: %s\n",
+		   priv->rx_offloaded ? "Yes" : "No");
+	if (!prp)
+		seq_printf(sfp, "L2 fw Offloaded: %s\n",
+			   priv->l2_fwd_offloaded ? "Yes" : "No");
 	seq_printf(sfp, "cnt_tx_sup = %d\n", priv->dbg_stats.cnt_tx_sup);
-	seq_printf(sfp, "cnt_rx_wrong_lan_a = %d\n",
-		   priv->lre_stats.cnt_errwronglan_a);
-	seq_printf(sfp, "cnt_rx_wrong_lan_b = %d\n",
-		   priv->lre_stats.cnt_errwronglan_b);
-	seq_printf(sfp, "cnt_rx_a = %d\n", priv->lre_stats.cnt_rx_a);
-	seq_printf(sfp, "cnt_rx_b = %d\n", priv->lre_stats.cnt_rx_b);
-	/* actually lre_rx_c is whatever received from the application
-	 * interface,  So same as tx_packets
-	 */
-	seq_printf(sfp, "cnt_rx_c = %d\n", priv->lre_stats.cnt_rx_c);
-	seq_printf(sfp, "cnt_rx_errors_a = %d\n", priv->lre_stats.cnt_errors_a);
-	seq_printf(sfp, "cnt_rx_errors_b = %d\n", priv->lre_stats.cnt_errors_b);
-	if (priv->prot_version <= HSR_V1) {
-		seq_printf(sfp, "cnt_own_rx_a = %d\n",
-			   priv->lre_stats.cnt_own_rx_a);
-		seq_printf(sfp, "cnt_own_rx_b = %d\n",
-			   priv->lre_stats.cnt_own_rx_b);
-	}
 	seq_puts(sfp, "\n");
 	return 0;
 }
 
-/* hsr_prp_stats_open - open stats file
+/* hsr_prp_lre_info_open - open lre info file
  *
  * Description:
- * This routine opens a debugfs file stats of specific hsr or
+ * This routine opens a debugfs file lre_info of specific hsr or
  * prp device
  */
 static int
-hsr_prp_stats_open(struct inode *inode, struct file *filp)
+hsr_prp_lre_info_open(struct inode *inode, struct file *filp)
 {
-	return single_open(filp, hsr_prp_stats_show, inode->i_private);
+	return single_open(filp, hsr_prp_lre_info_show, inode->i_private);
 }
 
-static const struct file_operations hsr_prp_stats_fops = {
+static const struct file_operations hsr_prp_lre_info_fops = {
 	.owner	= THIS_MODULE,
-	.open	= hsr_prp_stats_open,
+	.open	= hsr_prp_lre_info_open,
 	.read	= seq_read,
 	.llseek = seq_lseek,
 	.release = single_release,
 };
 
-/* hsr_prp_debugfs_init - create hsr-prp node_table file for dumping
- * the node table and lre stats
+/* hsr_prp_debugfs_init - create debugfs to dump lre info
  *
  * Description:
- * When debugfs is configured this routine sets up the node_table file per
- * hsr device for dumping the node_table entries and stats file for
- * lre stats dump.
+ * dump lre info of hsr or prp device
  */
-int hsr_prp_debugfs_init(struct hsr_prp_priv *priv, struct net_device *ndev)
+int hsr_prp_debugfs_init(struct hsr_prp_priv *priv,
+			 struct net_device *ndev)
 {
 	int rc = -1;
 	struct dentry *de = NULL;
@@ -164,27 +80,19 @@ int hsr_prp_debugfs_init(struct hsr_prp_priv *priv, struct net_device *ndev)
 
 	priv->root_dir = de;
 
-	de = debugfs_create_file("node_table", S_IFREG | 0444, priv->root_dir,
-				 priv, &hsr_prp_node_table_fops);
-	if (!de) {
-		netdev_err(ndev, "Cannot create hsr node_table directory\n");
-		goto error_nt;
-	}
-	priv->node_tbl_file = de;
-
-	de = debugfs_create_file("stats", S_IFREG | 0444, priv->root_dir, priv,
-				 &hsr_prp_stats_fops);
+	de = debugfs_create_file("lre_info", S_IFREG | 0444,
+				 priv->root_dir, priv,
+				 &hsr_prp_lre_info_fops);
 	if (!de) {
-		netdev_err(ndev, "Cannot create hsr-prp stats directory\n");
-		goto error_stats;
+		netdev_err(ndev,
+			   "Cannot create hsr-prp lre_info file\n");
+		goto error;
 	}
-	priv->stats_file = de;
+	priv->lre_info_file = de;
 
 	return 0;
 
-error_stats:
-	debugfs_remove(priv->node_tbl_file);
-error_nt:
+error:
 	debugfs_remove(priv->root_dir);
 	return -ENODEV;
 } /* end of hst_prp_debugfs_init */
@@ -198,10 +106,8 @@ error_nt:
 void
 hsr_prp_debugfs_term(struct hsr_prp_priv *priv)
 {
-	debugfs_remove(priv->node_tbl_file);
-	priv->node_tbl_file = NULL;
-	debugfs_remove(priv->stats_file);
-	priv->stats_file = NULL;
+	debugfs_remove(priv->lre_info_file);
+	priv->lre_info_file = NULL;
 	debugfs_remove(priv->root_dir);
 	priv->root_dir = NULL;
 }

+ 19 - 4
net/hsr-prp/hsr_prp_device.c

@@ -502,8 +502,12 @@ static void hsr_prp_announce(struct timer_list *t)
 			send_supervision_frame(master, HSR_TLV_LIFE_CHECK,
 					       priv->prot_version);
 		else /* PRP */
-			send_supervision_frame(master, PRP_TLV_LIFE_CHECK_DD,
-					       priv->prot_version);
+			send_supervision_frame(master,
+					       (priv->dd_mode ==
+						IEC62439_3_DD) ?
+						PRP_TLV_LIFE_CHECK_DD :
+						PRP_TLV_LIFE_CHECK_DA,
+						priv->prot_version);
 
 		interval = msecs_to_jiffies(HSR_PRP_LIFE_CHECK_INTERVAL);
 	}
@@ -524,6 +528,7 @@ static void hsr_prp_dev_destroy(struct net_device *ndev)
 
 	priv = netdev_priv(ndev);
 
+	hsr_prp_remove_procfs(priv, ndev);
 	hsr_prp_debugfs_term(priv);
 
 	rtnl_lock();
@@ -750,9 +755,13 @@ int hsr_prp_dev_finalize(struct net_device *hsr_prp_dev,
 	priv->prot_version = protocol_version;
 	if (priv->prot_version == PRP_V1) {
 		/* For PRP, lan_id has most significant 3 bits holding
-		 * the net_id of PRP_LAN_ID
+		 * the net_id of PRP_LAN_ID and also duplicate discard
+		 * mode set.
 		 */
 		priv->net_id = PRP_LAN_ID << 1;
+		priv->dd_mode = IEC62439_3_DD;
+	} else {
+		priv->hsr_mode = IEC62439_3_HSR_MODE_H;
 	}
 
 	spin_lock_init(&priv->seqnr_lock);
@@ -843,12 +852,18 @@ int hsr_prp_dev_finalize(struct net_device *hsr_prp_dev,
 			      slave[1]->dev_addr))
 		goto fail;
 
-	res = hsr_prp_debugfs_init(priv, hsr_prp_dev);
+	res = hsr_prp_create_procfs(priv, hsr_prp_dev);
 	if (res)
 		goto fail;
 
+	res = hsr_prp_debugfs_init(priv, hsr_prp_dev);
+	if (res)
+		goto fail_procfs;
+
 	return 0;
 
+fail_procfs:
+	hsr_prp_remove_procfs(priv, hsr_prp_dev);
 fail:
 	hsr_prp_for_each_port(priv, port)
 		hsr_prp_del_port(port);

+ 25 - 13
net/hsr-prp/hsr_prp_forward.c

@@ -120,21 +120,24 @@ static struct sk_buff *create_stripped_skb_hsr(struct sk_buff *skb_in,
 static struct sk_buff *frame_get_stripped_skb(struct hsr_prp_frame_info *frame,
 					      struct hsr_prp_port *port)
 {
+	struct hsr_prp_priv *priv = port->priv;
+
 	if (!frame->skb_std) {
 		if (frame->skb_hsr) {
 			frame->skb_std =
 				create_stripped_skb_hsr(frame->skb_hsr, frame);
 		} else if (frame->skb_prp) {
 			/* trim the skb by len - HSR_PRP_HLEN to exclude
-			 * RCT
+			 * RCT if configured to remove RCT
 			 */
-			skb_trim(frame->skb_prp,
-				 frame->skb_prp->len - HSR_PRP_HLEN);
+			if (!priv->rx_offloaded &&
+			    priv->prp_tr == IEC62439_3_TR_REMOVE_RCT)
+				skb_trim(frame->skb_prp,
+					 frame->skb_prp->len - HSR_PRP_HLEN);
 			frame->skb_std =
 				__pskb_copy(frame->skb_prp,
 					    skb_headroom(frame->skb_prp),
-					    GFP_ATOMIC);
-
+							 GFP_ATOMIC);
 		} else {
 			/* Unexpected */
 			WARN_ONCE(1, "%s:%d: Unexpected frame received (port_src %s)\n",
@@ -272,6 +275,9 @@ static struct sk_buff *create_tagged_skb(struct sk_buff *skb_o,
 
 		prp_fill_rct(skb, frame, port);
 		return skb;
+	} else if ((port->priv->prot_version == HSR_V1) &&
+		   (port->priv->hsr_mode == IEC62439_3_HSR_MODE_T)) {
+		return skb_clone(skb_o, GFP_ATOMIC);
 	}
 
 	/* Create the new skb with enough headroom to fit the HSR tag */
@@ -572,19 +578,25 @@ static int fill_frame_info(struct hsr_prp_frame_info *frame,
 			if (port->type != HSR_PRP_PT_MASTER) {
 				frame->is_from_san = true;
 			} else {
-				/* Sequence nr for the master node */
-				spin_lock_irqsave(&port->priv->seqnr_lock,
-						  irqflags);
-				frame->sequence_nr = port->priv->sequence_nr;
-				port->priv->sequence_nr++;
-				spin_unlock_irqrestore(&port->priv->seqnr_lock,
-						       irqflags);
+				if (((priv->prot_version == HSR_V1) &&
+				     (priv->hsr_mode
+					!= IEC62439_3_HSR_MODE_T)) ||
+				     (priv->prot_version == PRP_V1) ||
+				     (priv->prot_version == HSR_V0))	{
+					/* Sequence nr for the master node */
+					spin_lock_irqsave(&priv->seqnr_lock,
+							  irqflags);
+					frame->sequence_nr = priv->sequence_nr;
+					priv->sequence_nr++;
+					spin_unlock_irqrestore(&priv->seqnr_lock,
+							       irqflags);
+				}
 			}
 		}
 	}
 
 	frame->port_rcv = port;
-	check_local_dest(port->priv, skb, frame);
+	check_local_dest(priv, skb, frame);
 
 	return 0;
 }

+ 37 - 2
net/hsr-prp/hsr_prp_main.h

@@ -222,11 +222,30 @@ struct hsr_prp_priv {
 	u8 net_id;		/* for PRP, it occupies most significant 3 bits
 				 * of lan_id
 				 */
+	/* value of hsr mode */
+	enum iec62439_3_hsr_modes hsr_mode;
+	/* PRP Transparent Reception */
+	enum iec62439_3_tr_modes prp_tr;
+	/* Duplicate discard mode */
+	enum iec62439_3_dd_modes dd_mode;
+	/* Clear Node Table command */
+	enum iec62439_3_clear_nt_cmd clear_nt_cmd;
+	u32 dlrmt;	/* duplicate list reside max time */
 	unsigned char		sup_multicast_addr[ETH_ALEN];
 #ifdef	CONFIG_DEBUG_FS
 	struct dentry *root_dir;
-	struct dentry *node_tbl_file;
-	struct dentry *stats_file;
+	struct dentry *lre_info_file;
+#endif
+#ifdef	CONFIG_PROC_FS
+	struct proc_dir_entry *dir;
+	struct proc_dir_entry *hsr_mode_file;
+	struct proc_dir_entry *dd_mode_file;
+	struct proc_dir_entry *prp_tr_file;
+	struct proc_dir_entry *clear_nt_file;
+	struct proc_dir_entry *dlrmt_file;
+	struct proc_dir_entry *lre_stats_file;
+	struct proc_dir_entry *node_table_file;
+	struct proc_dir_entry *disable_sv_file;
 #endif
 };
 
@@ -324,6 +343,22 @@ static inline int hsr_prp_debugfs_init(struct hsr_prp_priv *priv,
 static inline void hsr_prp_debugfs_term(struct hsr_prp_priv *priv)
 {}
 #endif
+
+#ifdef	CONFIG_PROC_FS
+int hsr_prp_create_procfs(struct hsr_prp_priv *priv, struct net_device *ndev);
+void hsr_prp_remove_procfs(struct hsr_prp_priv *priv, struct net_device *ndev);
+#else
+static inline int hsr_prp_create_procfs(struct hsr_prp_priv *priv,
+					struct net_device *ndev)
+{
+	return 0;
+}
+
+static inline void hsr_prp_remove_procfs(struct hsr_prp_priv *priv,
+					 struct net_device *ndev)
+{}
+#endif
+
 int hsr_prp_lredev_attr_set(struct hsr_prp_priv *priv,
 			    struct lredev_attr *attr);
 int hsr_prp_lredev_attr_get(struct hsr_prp_priv *priv,

+ 631 - 0
net/hsr-prp/hsr_prp_proc.c

@@ -0,0 +1,631 @@
+/*
+ * HSR/PRP Driver procfs file
+ *
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include "hsr_prp_main.h"
+
+#define BUF_SIZE 64
+#define LRE_STAT_OFS(m) offsetof(struct lre_stats, m)
+const char *hsr_prp_lre_stats[] = {
+	"lreTxA",
+	"lreTxB",
+	"lreTxC",
+	"lreErrWrongLanA",
+	"lreErrWrongLanB",
+	"lreErrWrongLanC",
+	"lreRxA",
+	"lreRxB",
+	"lreRxC",
+	"lreErrorsA",
+	"lreErrorsB",
+	"lreErrorsC",
+	"lreNodes",
+	"lreProxyNodes",
+	"lreUniqueRxA",
+	"lreUniqueRxB",
+	"lreUniqueRxC",
+	"lreDuplicateRxA",
+	"lreDuplicateRxB",
+	"lreDuplicateRxC",
+	"lreMultiRxA",
+	"lreMultiRxB",
+	"lreMultiRxC",
+	"lreOwnRxA",
+	"lreOwnRxB",
+};
+
+static int hsr_prp_lre_stats_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lre_stats lower_stats, *upper_stats;
+	int ret = 0, i;
+	u32 *ptr;
+
+	upper_stats = &priv->lre_stats;
+	if (priv->rx_offloaded) {
+		ret = hsr_prp_lredev_get_lre_stats(priv, &lower_stats);
+		if (ret < 0) {
+			seq_puts(sfp, "Error in retrieving the stats\n");
+			return 0;
+		}
+		ptr = (u32 *)&lower_stats;
+	} else {
+		ptr = (u32 *)upper_stats;
+	}
+
+	seq_puts(sfp, "LRE statistics:\n");
+	seq_printf(sfp, "Rx Offloaded: %d\n", priv->rx_offloaded);
+	for (i = 0; i < ARRAY_SIZE(hsr_prp_lre_stats); i++) {
+		/* for rx_c and tx_c, retrieve stats from hsr/prp device
+		 * lre stats. Rest of the stats are retrieved from
+		 * lower device.
+		 */
+		if (!strcmp("lreTxC", hsr_prp_lre_stats[i])) {
+			seq_printf(sfp, "\n     %s: %d",
+				   hsr_prp_lre_stats[i],
+				   upper_stats->cnt_tx_c);
+			continue;
+		}
+
+		if (!strcmp("lreRxC", hsr_prp_lre_stats[i])) {
+			seq_printf(sfp, "\n     %s: %d",
+				   hsr_prp_lre_stats[i],
+				   upper_stats->cnt_rx_c);
+			continue;
+		}
+		seq_printf(sfp, "\n     %s: %d", hsr_prp_lre_stats[i],
+			   *(ptr + i));
+	}
+	seq_puts(sfp, "\n");
+
+	return 0;
+}
+
+static int hsr_prp_lre_stats_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hsr_prp_lre_stats_show, PDE_DATA(inode));
+}
+
+static const struct file_operations hsr_prp_lre_stats_fops = {
+	.owner		= THIS_MODULE,
+	.open		= hsr_prp_lre_stats_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int hsr_prp_node_show(struct seq_file *sfp,
+			     int index,
+			     struct lre_node_table_entry *entry)
+{
+	seq_printf(sfp, "\nNode[%u]:\n", index);
+	seq_printf(sfp, "MAC ADDR: %02x:%02x:%02x:%02x:%02x:%02x\n",
+		   entry->mac_address[0],
+		   entry->mac_address[1],
+		   entry->mac_address[2],
+		   entry->mac_address[3],
+		   entry->mac_address[4],
+		   entry->mac_address[5]);
+
+	switch (entry->node_type) {
+	case IEC62439_3_DANP:
+		seq_puts(sfp, "DANP\n");
+		break;
+	case IEC62439_3_REDBOXP:
+		seq_puts(sfp, "REDBOXP\n");
+		break;
+	case IEC62439_3_VDANP:
+		seq_puts(sfp, "VDANP\n");
+		break;
+	case IEC62439_3_DANH:
+		seq_puts(sfp, "DANH\n");
+		break;
+	case IEC62439_3_REDBOXH:
+		seq_puts(sfp, "REDBOXH\n");
+		break;
+	case IEC62439_3_VDANH:
+		seq_puts(sfp, "VDANH\n");
+		break;
+	default:
+		seq_printf(sfp, "Unknown node type %u\n", entry->node_type);
+		break;
+	};
+
+	seq_printf(sfp, "Time Last Seen: RxA=%u RxB=%u\n",
+		   entry->time_last_seen_a,
+		   entry->time_last_seen_b);
+	return 0;
+}
+
+static int hsr_prp_node_table_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lre_node_table_entry *nt_table;
+	int ret = 0, count, i;
+
+	nt_table = kcalloc(LRE_MAX_NT_ENTRIES, sizeof(*nt_table), GFP_KERNEL);
+	if (!nt_table)
+		return -ENODEV;
+
+	count = hsr_prp_lredev_get_node_table(priv, nt_table,
+					      LRE_MAX_NT_ENTRIES);
+	if (count < 0)
+		count = 0;
+
+	seq_printf(sfp, "\nRemote nodes in network: %u\n", count);
+
+	if (!count)
+		return ret;
+
+	for (i = 0; i < count; i++)
+		hsr_prp_node_show(sfp, i, &nt_table[i]);
+	return ret;
+}
+
+static int hsr_prp_node_table_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hsr_prp_node_table_show, PDE_DATA(inode));
+}
+
+static const struct file_operations hsr_prp_node_table_fops = {
+	.owner		= THIS_MODULE,
+	.open		= hsr_prp_node_table_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static inline int get_set_param(struct hsr_prp_priv *priv,
+				const char __user *buffer, size_t count,
+				enum lredev_attr_id id)
+{
+	struct lredev_attr temp_attr;
+	char cmd_buffer[BUF_SIZE];
+	int ret = -EINVAL;
+	u32 val;
+
+	if (count > (sizeof(cmd_buffer) - 1))
+		goto err;
+
+	if (copy_from_user(cmd_buffer, buffer, count)) {
+		ret = -EFAULT;
+		goto err;
+	}
+	cmd_buffer[count] = '\0';
+	ret = kstrtou32(cmd_buffer, 0, &val);
+	if (ret < 0)
+		goto err;
+
+	/* TODO. Update mode. Check if anything else needed for
+	 * non offload case
+	 */
+	temp_attr.id = id;
+	switch (id) {
+	case LREDEV_ATTR_ID_HSR_MODE:
+		if (val > IEC62439_3_HSR_MODE_M) {
+			ret = -EINVAL;
+			goto err;
+		}
+		if (!priv->rx_offloaded) {
+			priv->hsr_mode = (enum iec62439_3_hsr_modes)val;
+			return 0;
+		}
+		temp_attr.mode = (enum iec62439_3_hsr_modes)val;
+		break;
+
+	case LREDEV_ATTR_ID_PRP_TR:
+		if (val > IEC62439_3_TR_PASS_RCT) {
+			ret = -EINVAL;
+			goto err;
+		}
+		if (!priv->rx_offloaded) {
+			priv->prp_tr = (enum iec62439_3_tr_modes)val;
+			goto out;
+		}
+		temp_attr.tr_mode = (enum iec62439_3_tr_modes)val;
+		break;
+
+	case LREDEV_ATTR_ID_DD_MODE:
+		if (val > IEC62439_3_DD) {
+			ret = -EINVAL;
+			goto err;
+		}
+		if (!priv->rx_offloaded) {
+			priv->dd_mode = (enum iec62439_3_dd_modes)val;
+			goto out;
+		}
+		temp_attr.dd_mode = (enum iec62439_3_dd_modes)val;
+		break;
+
+	case LREDEV_ATTR_ID_DLRMT:
+		if (!priv->rx_offloaded) {
+			priv->dlrmt = val;
+			goto out;
+		}
+		temp_attr.dl_reside_max_time = val;
+		break;
+
+	case LREDEV_ATTR_ID_CLEAR_NT:
+		if (val > IEC62439_3_CLEAR_NT) {
+			ret = -EINVAL;
+			goto err;
+		}
+		if (!priv->rx_offloaded) {
+			priv->clear_nt_cmd =
+				(enum iec62439_3_clear_nt_cmd)val;
+			goto out;
+		}
+		temp_attr.clear_nt_cmd = (enum iec62439_3_clear_nt_cmd)val;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* pass this to lower layer device, i.e slave-1 */
+	ret = hsr_prp_lredev_attr_set(priv, &temp_attr);
+	if (ret)
+		return ret;
+
+	/* update the local copy */
+	switch (id) {
+	case LREDEV_ATTR_ID_HSR_MODE:
+		priv->hsr_mode = temp_attr.mode;
+		break;
+	case LREDEV_ATTR_ID_PRP_TR:
+		priv->prp_tr = temp_attr.tr_mode;
+		break;
+	case LREDEV_ATTR_ID_DD_MODE:
+		priv->dd_mode = temp_attr.dd_mode;
+		break;
+	case LREDEV_ATTR_ID_CLEAR_NT:
+		priv->clear_nt_cmd = temp_attr.clear_nt_cmd;
+		break;
+	default: /* LREDEV_ATTR_ID_DLRMT */
+		priv->dlrmt = temp_attr.dl_reside_max_time;
+		break;
+	}
+out:
+	return 0;
+err:
+	return ret;
+}
+
+static int hsr_mode_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lredev_attr temp_attr;
+	int err;
+
+	if (!priv->rx_offloaded) {
+		seq_printf(sfp, "%u\n", priv->hsr_mode);
+		return 0;
+	}
+
+	temp_attr.id = LREDEV_ATTR_ID_HSR_MODE;
+	err = hsr_prp_lredev_attr_get(priv, &temp_attr);
+	if (err)
+		return err;
+
+	seq_printf(sfp, "%u\n", temp_attr.mode);
+	return 0;
+}
+
+static int hsr_mode_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hsr_mode_show, PDE_DATA(inode));
+}
+
+static ssize_t hsr_mode_store(struct file *file,
+			      const char __user *buffer,
+			      size_t count, loff_t *pos)
+{
+	struct hsr_prp_priv *priv =
+		(struct hsr_prp_priv *)PDE_DATA(file_inode(file));
+	int err;
+
+	err = get_set_param(priv, buffer, count, LREDEV_ATTR_ID_HSR_MODE);
+	if (err)
+		return err;
+
+	return  count;
+}
+
+static const struct file_operations hsr_mode_fops = {
+	.owner		= THIS_MODULE,
+	.open		= hsr_mode_open,
+	.read		= seq_read,
+	.write		= hsr_mode_store,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int prp_tr_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lredev_attr temp_attr;
+	int err;
+
+	if (!priv->rx_offloaded) {
+		seq_printf(sfp, "%u\n", priv->prp_tr);
+		return 0;
+	}
+
+	temp_attr.id = LREDEV_ATTR_ID_PRP_TR;
+	err = hsr_prp_lredev_attr_get(priv, &temp_attr);
+	if (err)
+		return err;
+
+	seq_printf(sfp, "%u\n", temp_attr.tr_mode);
+	return 0;
+}
+
+static int prp_tr_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, prp_tr_show, PDE_DATA(inode));
+}
+
+static ssize_t prp_tr_store(struct file *file,
+			    const char __user *buffer,
+			    size_t count, loff_t *pos)
+{
+	struct hsr_prp_priv *priv =
+		(struct hsr_prp_priv *)PDE_DATA(file_inode(file));
+	int err;
+
+	err = get_set_param(priv, buffer, count, LREDEV_ATTR_ID_PRP_TR);
+	if (err)
+		return err;
+
+	return  count;
+}
+
+static const struct file_operations prp_tr_fops = {
+	.owner		= THIS_MODULE,
+	.open		= prp_tr_open,
+	.read		= seq_read,
+	.write		= prp_tr_store,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int dlrmt_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lredev_attr temp_attr;
+	int err;
+
+	if (!priv->rx_offloaded) {
+		seq_printf(sfp, "%u\n", priv->dlrmt);
+		return 0;
+	}
+
+	temp_attr.id = LREDEV_ATTR_ID_DLRMT;
+	err = hsr_prp_lredev_attr_get(priv, &temp_attr);
+	if (err)
+		return err;
+
+	seq_printf(sfp, "%u\n", temp_attr.dl_reside_max_time);
+	return 0;
+}
+
+static int dlrmt_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dlrmt_show, PDE_DATA(inode));
+}
+
+static ssize_t dlrmt_store(struct file *file,
+			   const char __user *buffer,
+			   size_t count, loff_t *pos)
+{
+	struct hsr_prp_priv *priv =
+		(struct hsr_prp_priv *)PDE_DATA(file_inode(file));
+	int err;
+
+	err = get_set_param(priv, buffer, count, LREDEV_ATTR_ID_DLRMT);
+	if (err)
+		return err;
+
+	return  count;
+}
+
+static const struct file_operations dlrmt_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dlrmt_open,
+	.read		= seq_read,
+	.write		= dlrmt_store,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int dd_mode_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lredev_attr temp_attr;
+	int err;
+
+	if (!priv->rx_offloaded) {
+		seq_printf(sfp, "%u\n", priv->dd_mode);
+		return 0;
+	}
+
+	temp_attr.id = LREDEV_ATTR_ID_DD_MODE;
+	err = hsr_prp_lredev_attr_get(priv, &temp_attr);
+	if (err)
+		return err;
+
+	seq_printf(sfp, "%u\n", temp_attr.dd_mode);
+	return 0;
+}
+
+static int dd_mode_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dd_mode_show, PDE_DATA(inode));
+}
+
+static ssize_t dd_mode_store(struct file *file,
+			     const char __user *buffer,
+			     size_t count, loff_t *pos)
+{
+	struct hsr_prp_priv *priv =
+		(struct hsr_prp_priv *)PDE_DATA(file_inode(file));
+	int err;
+
+	err = get_set_param(priv, buffer, count, LREDEV_ATTR_ID_DD_MODE);
+	if (err)
+		return err;
+
+	return  count;
+}
+
+static const struct file_operations dd_mode_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dd_mode_open,
+	.read		= seq_read,
+	.write		= dd_mode_store,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int clear_nt_show(struct seq_file *sfp, void *v)
+{
+	struct hsr_prp_priv *priv = (struct hsr_prp_priv *)sfp->private;
+	struct lredev_attr temp_attr;
+	int err;
+
+	if (!priv->rx_offloaded) {
+		seq_printf(sfp, "%u\n", priv->clear_nt_cmd);
+		return 0;
+	}
+
+	temp_attr.id = LREDEV_ATTR_ID_CLEAR_NT;
+	err = hsr_prp_lredev_attr_get(priv, &temp_attr);
+	if (err)
+		return err;
+
+	seq_printf(sfp, "%u\n", temp_attr.clear_nt_cmd);
+	return 0;
+}
+
+static int clear_nt_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, clear_nt_show, PDE_DATA(inode));
+}
+
+static ssize_t clear_nt_store(struct file *file,
+			      const char __user *buffer,
+			      size_t count, loff_t *pos)
+{
+	struct hsr_prp_priv *priv =
+		(struct hsr_prp_priv *)PDE_DATA(file_inode(file));
+	int err;
+
+	err = get_set_param(priv, buffer, count, LREDEV_ATTR_ID_CLEAR_NT);
+	if (err)
+		return err;
+
+	return  count;
+}
+
+static const struct file_operations clear_nt_fops = {
+	.owner		= THIS_MODULE,
+	.open		= clear_nt_open,
+	.read		= seq_read,
+	.write		= clear_nt_store,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+int hsr_prp_create_procfs(struct hsr_prp_priv *priv, struct net_device *ndev)
+{
+	int ret = -ENODEV;
+
+	priv->dir = proc_mkdir(ndev->name, NULL);
+	if (!priv->dir)
+		return ret;
+
+	priv->lre_stats_file = proc_create_data("lre-stats", 0444, priv->dir,
+						&hsr_prp_lre_stats_fops,
+						(void *)priv);
+	if (!priv->lre_stats_file)
+		goto fail_lre_stats;
+
+	priv->node_table_file = proc_create_data("node-table", 0444, priv->dir,
+						 &hsr_prp_node_table_fops,
+						 (void *)priv);
+	if (!priv->node_table_file)
+		goto fail_node_table;
+
+	priv->hsr_mode_file = proc_create_data("hsr-mode", 0644, priv->dir,
+					       &hsr_mode_fops, (void *)priv);
+	if (!priv->hsr_mode_file)
+		goto fail_hsr_mode;
+
+	priv->dd_mode_file = proc_create_data("dd-mode", 0644, priv->dir,
+					      &dd_mode_fops, (void *)priv);
+	if (!priv->dd_mode_file)
+		goto fail_dd_mode;
+
+	priv->prp_tr_file = proc_create_data("prp-tr", 0644, priv->dir,
+					     &prp_tr_fops, (void *)priv);
+	if (!priv->prp_tr_file)
+		goto fail_prp_tr;
+
+	priv->clear_nt_file = proc_create_data("clear-nt", 0644, priv->dir,
+					       &clear_nt_fops, (void *)priv);
+	if (!priv->clear_nt_file)
+		goto fail_clear_nt;
+
+	priv->dlrmt_file = proc_create_data("dlrmt", 0644, priv->dir,
+					    &dlrmt_fops, (void *)priv);
+	if (!priv->dlrmt_file)
+		goto fail_dlrmt;
+
+	return 0;
+fail_dlrmt:
+	if (priv->clear_nt_file)
+		remove_proc_entry("clear-nt", priv->dir);
+fail_clear_nt:
+	if (priv->prp_tr_file)
+		remove_proc_entry("prp-tr", priv->dir);
+fail_prp_tr:
+	if (priv->dd_mode_file)
+		remove_proc_entry("dd-mode", priv->dir);
+fail_dd_mode:
+	if (priv->hsr_mode_file)
+		remove_proc_entry("hsr-mode", priv->dir);
+fail_hsr_mode:
+	if (priv->node_table_file)
+		remove_proc_entry("node-table", priv->dir);
+fail_node_table:
+	if (priv->lre_stats_file)
+		remove_proc_entry("lre-stats", priv->dir);
+fail_lre_stats:
+	remove_proc_entry(ndev->name, NULL);
+	return ret;
+}
+
+void hsr_prp_remove_procfs(struct hsr_prp_priv *priv, struct net_device *ndev)
+{
+	remove_proc_entry("dlrmt", priv->dir);
+	remove_proc_entry("clear-nt", priv->dir);
+	remove_proc_entry("prp-tr", priv->dir);
+	remove_proc_entry("dd-mode", priv->dir);
+	remove_proc_entry("hsr-mode", priv->dir);
+	remove_proc_entry("lre-stats", priv->dir);
+	remove_proc_entry("node-table", priv->dir);
+	remove_proc_entry(ndev->name, NULL);
+	priv->dir = NULL;
+}