Browse Source

net: add netlink interface to create prp device

This patch adds a netlink interface to create a prp device similar in
line with hsr. Also add a notifier for the same.

Signed-off-by: Murali Karicheri <m-karicheri2@ti.com>
Murali Karicheri 6 years ago
parent
commit
68de124913

+ 14 - 0
include/uapi/linux/if_link.h

@@ -999,4 +999,18 @@ struct ifla_rmnet_flags {
 	__u32	mask;
 };
 
+/* PRP section */
+
+enum {
+	IFLA_PRP_UNSPEC,
+	IFLA_PRP_SLAVE1,
+	IFLA_PRP_SLAVE2,
+	IFLA_PRP_MULTICAST_SPEC,        /* Last byte of supervision addr */
+	IFLA_PRP_SUPERVISION_ADDR,      /* Supervision frame multicast addr */
+	IFLA_PRP_SEQ_NR,
+	__IFLA_PRP_MAX,
+};
+
+#define IFLA_PRP_MAX (__IFLA_PRP_MAX - 1)
+
 #endif /* _UAPI_LINUX_IF_LINK_H */

+ 48 - 0
include/uapi/linux/prp_netlink.h

@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * prp_netlink.h: This is based on hsr_netlink.h from Arvid Brodin,
+ * arvid.brodin@alten.se
+ *
+ * Copyright (C) 2017 Texas Instruments Incorporated
+ *
+ * Author(s):
+ *	2017 Murali Karicheri <m-karicheri2@ti.com>
+ */
+
+#ifndef __UAPI_PRP_NETLINK_H
+#define __UAPI_PRP_NETLINK_H
+
+/* Generic Netlink PRP family definition
+ */
+
+/* attributes */
+enum {
+	PRP_A_UNSPEC,
+	PRP_A_NODE_ADDR,
+	PRP_A_IFINDEX,
+	PRP_A_IF1_AGE,
+	PRP_A_IF2_AGE,
+	PRP_A_NODE_ADDR_B,
+	PRP_A_IF1_SEQ,
+	PRP_A_IF2_SEQ,
+	PRP_A_IF1_IFINDEX,
+	PRP_A_IF2_IFINDEX,
+	PRP_A_ADDR_B_IFINDEX,
+	__PRP_A_MAX,
+};
+#define PRP_A_MAX (__PRP_A_MAX - 1)
+
+
+/* commands */
+enum {
+	PRP_C_UNSPEC,
+	PRP_C_NODE_DOWN,
+	PRP_C_GET_NODE_STATUS,
+	PRP_C_SET_NODE_STATUS,
+	PRP_C_GET_NODE_LIST,
+	PRP_C_SET_NODE_LIST,
+	__PRP_C_MAX,
+};
+#define PRP_C_MAX (__PRP_C_MAX - 1)
+
+#endif /* __UAPI_PRP_NETLINK_H */

+ 3 - 3
net/hsr-prp/Makefile

@@ -4,7 +4,7 @@
 
 obj-$(CONFIG_HSR_PRP)	+= hsr-prp.o
 
-hsr-prp-y		:= hsr_prp_main.o hsr_main.o hsr_prp_framereg.o \
-			   hsr_prp_device.o hsr_netlink.o hsr_prp_slave.o \
-			   hsr_prp_forward.o
+hsr-prp-y		:= hsr_prp_main.o hsr_main.o prp_main.o \
+			   hsr_prp_framereg.o hsr_prp_device.o hsr_netlink.o \
+			   prp_netlink.o hsr_prp_slave.o hsr_prp_forward.o
 hsr-prp-$(CONFIG_DEBUG_FS) += hsr_prp_debugfs.o

+ 2 - 2
net/hsr-prp/hsr_main.c

@@ -16,7 +16,7 @@ static int __init hsr_init(void)
 
 	BUILD_BUG_ON(sizeof(struct hsr_tag) != HSR_PRP_HLEN);
 
-	res = hsr_prp_register_notifier();
+	res = hsr_prp_register_notifier(HSR);
 	if (!res)
 		res = hsr_netlink_init();
 
@@ -25,7 +25,7 @@ static int __init hsr_init(void)
 
 static void __exit hsr_exit(void)
 {
-	hsr_prp_unregister_notifier();
+	hsr_prp_unregister_notifier(HSR);
 	hsr_netlink_exit();
 }
 

+ 1 - 1
net/hsr-prp/hsr_netlink.c

@@ -111,7 +111,7 @@ static struct rtnl_link_ops hsr_link_ops __read_mostly = {
 	.maxtype	= IFLA_HSR_MAX,
 	.policy		= hsr_policy,
 	.priv_size	= sizeof(struct hsr_prp_priv),
-	.setup		= hsr_prp_dev_setup,
+	.setup		= hsr_dev_setup,
 	.newlink	= hsr_newlink,
 	.fill_info	= hsr_fill_info,
 };

+ 49 - 25
net/hsr-prp/hsr_prp_device.c

@@ -134,13 +134,16 @@ static int hsr_prp_dev_change_mtu(struct net_device *dev, int new_mtu)
 {
 	struct hsr_prp_priv *priv;
 	struct hsr_prp_port *master;
+	int max;
 
 	priv = netdev_priv(dev);
 	master = hsr_prp_get_port(priv, HSR_PRP_PT_MASTER);
 
-	if (new_mtu > hsr_prp_get_max_mtu(priv)) {
-		netdev_info(master->dev, "A HSR/PRP master's MTU cannot be greater than the smallest MTU of its slaves minus the HSR Tag length (%d octets).\n",
-			    HSR_PRP_HLEN);
+	max = hsr_prp_get_max_mtu(priv);
+	if (new_mtu > max) {
+		netdev_info(master->dev,
+			    "HSR/PRP: Invalid MTU, expected (<= %d), Got %d.\n",
+			    max, new_mtu);
 		return -EINVAL;
 	}
 
@@ -173,7 +176,8 @@ static int hsr_prp_dev_open(struct net_device *dev)
 			designation = '?';
 		}
 		if (!is_slave_up(port->dev))
-			netdev_warn(dev, "Slave %c (%s) is not up; please bring it up to get a fully working HSR/PRP network\n",
+			netdev_warn(dev,
+				    "HSR/PRP: Please bringup Slave %c (%s)\n",
 				    designation, port->dev->name);
 	}
 	rcu_read_unlock();
@@ -326,7 +330,7 @@ static void hsr_prp_announce(struct timer_list *t)
 	rcu_read_lock();
 	master = hsr_prp_get_port(priv, HSR_PRP_PT_MASTER);
 
-	if (priv->announce_count < 3 && priv->prot_version == 0) {
+	if (priv->announce_count < 3 && priv->prot_version == HSR_V0) {
 		send_supervision_frame(master, HSR_TLV_ANNOUNCE,
 				       priv->prot_version);
 		priv->announce_count++;
@@ -348,12 +352,12 @@ static void hsr_prp_announce(struct timer_list *t)
 /* According to comments in the declaration of struct net_device, this function
  * is "Called from unregister, can be used to call free_netdev". Ok then...
  */
-static void hsr_prp_dev_destroy(struct net_device *hsr_dev)
+static void hsr_prp_dev_destroy(struct net_device *ndev)
 {
 	struct hsr_prp_priv *priv;
 	struct hsr_prp_port *port;
 
-	priv = netdev_priv(hsr_dev);
+	priv = netdev_priv(ndev);
 
 	hsr_prp_debugfs_term(priv);
 
@@ -376,11 +380,7 @@ static const struct net_device_ops hsr_prp_device_ops = {
 	.ndo_fix_features = hsr_prp_fix_features,
 };
 
-static struct device_type hsr_type = {
-	.name = "hsr",
-};
-
-void hsr_prp_dev_setup(struct net_device *dev)
+static void hsr_prp_dev_setup(struct net_device *dev, struct device_type *type)
 {
 	eth_hw_addr_random(dev);
 
@@ -388,7 +388,7 @@ void hsr_prp_dev_setup(struct net_device *dev)
 	dev->min_mtu = 0;
 	dev->header_ops = &hsr_prp_header_ops;
 	dev->netdev_ops = &hsr_prp_device_ops;
-	SET_NETDEV_DEVTYPE(dev, &hsr_type);
+	SET_NETDEV_DEVTYPE(dev, type);
 	dev->priv_flags |= IFF_NO_QUEUE;
 
 	dev->needs_free_netdev = true;
@@ -412,6 +412,24 @@ void hsr_prp_dev_setup(struct net_device *dev)
 	dev->features |= NETIF_F_NETNS_LOCAL;
 }
 
+static struct device_type hsr_type = {
+	.name = "hsr",
+};
+
+void hsr_dev_setup(struct net_device *dev)
+{
+	hsr_prp_dev_setup(dev, &hsr_type);
+}
+
+static struct device_type prp_type = {
+	.name = "prp",
+};
+
+void prp_dev_setup(struct net_device *dev)
+{
+	hsr_prp_dev_setup(dev, &prp_type);
+}
+
 /* Return true if dev is a HSR master; return false otherwise.
  */
 inline bool is_hsr_prp_master(struct net_device *dev)
@@ -424,7 +442,7 @@ static const unsigned char def_multicast_addr[ETH_ALEN] __aligned(2) = {
 	0x01, 0x15, 0x4e, 0x00, 0x01, 0x00
 };
 
-int hsr_prp_dev_finalize(struct net_device *hsr_dev,
+int hsr_prp_dev_finalize(struct net_device *hsr_prp_dev,
 			 struct net_device *slave[2],
 			 unsigned char multicast_spec, u8 protocol_version)
 {
@@ -432,19 +450,26 @@ int hsr_prp_dev_finalize(struct net_device *hsr_dev,
 	struct hsr_prp_port *port;
 	int res;
 
-	priv = netdev_priv(hsr_dev);
+	/* PRP not supported yet */
+	if (protocol_version == PRP_V1)
+		return -EPROTONOSUPPORT;
+
+	priv = netdev_priv(hsr_prp_dev);
 	INIT_LIST_HEAD(&priv->ports);
 	INIT_LIST_HEAD(&priv->node_db);
 	INIT_LIST_HEAD(&priv->self_node_db);
 
-	ether_addr_copy(hsr_dev->dev_addr, slave[0]->dev_addr);
+	ether_addr_copy(hsr_prp_dev->dev_addr, slave[0]->dev_addr);
 
 	/* Make sure we recognize frames from ourselves in hsr_rcv() */
-	res = hsr_prp_create_self_node(&priv->self_node_db, hsr_dev->dev_addr,
+	res = hsr_prp_create_self_node(&priv->self_node_db,
+				       hsr_prp_dev->dev_addr,
 				       slave[1]->dev_addr);
 	if (res < 0)
 		return res;
 
+	priv->prot_version = protocol_version;
+
 	spin_lock_init(&priv->seqnr_lock);
 	/* Overflow soon to find bugs easier: */
 	priv->sequence_nr = HSR_PRP_SEQNR_START;
@@ -456,26 +481,24 @@ int hsr_prp_dev_finalize(struct net_device *hsr_dev,
 	ether_addr_copy(priv->sup_multicast_addr, def_multicast_addr);
 	priv->sup_multicast_addr[ETH_ALEN - 1] = multicast_spec;
 
-	priv->prot_version = protocol_version;
-
 	/* FIXME: should I modify the value of these?
 	 *
-	 * - hsr_dev->flags - i.e.
+	 * - hsr_prp_dev->flags - i.e.
 	 *			IFF_MASTER/SLAVE?
-	 * - hsr_dev->priv_flags - i.e.
+	 * - hsr_prp_dev->priv_flags - i.e.
 	 *			IFF_EBRIDGE?
 	 *			IFF_TX_SKB_SHARING?
 	 *			IFF_HSR_MASTER/SLAVE?
 	 */
 
 	/* Make sure the 1st call to netif_carrier_on() gets through */
-	netif_carrier_off(hsr_dev);
+	netif_carrier_off(hsr_prp_dev);
 
-	res = hsr_prp_add_port(priv, hsr_dev, HSR_PRP_PT_MASTER);
+	res = hsr_prp_add_port(priv, hsr_prp_dev, HSR_PRP_PT_MASTER);
 	if (res)
 		goto err_add_port;
 
-	res = register_netdevice(hsr_dev);
+	res = register_netdevice(hsr_prp_dev);
 	if (res)
 		goto fail;
 
@@ -488,7 +511,8 @@ int hsr_prp_dev_finalize(struct net_device *hsr_dev,
 
 	mod_timer(&priv->prune_timer, jiffies +
 		  msecs_to_jiffies(HSR_PRP_PRUNE_PERIOD));
-	res = hsr_prp_debugfs_init(priv, hsr_dev);
+
+	res = hsr_prp_debugfs_init(priv, hsr_prp_dev);
 	if (res)
 		goto fail;
 

+ 2 - 1
net/hsr-prp/hsr_prp_device.h

@@ -11,7 +11,8 @@
 #include <linux/netdevice.h>
 #include "hsr_prp_main.h"
 
-void hsr_prp_dev_setup(struct net_device *dev);
+void hsr_dev_setup(struct net_device *dev);
+void prp_dev_setup(struct net_device *dev);
 int hsr_prp_dev_finalize(struct net_device *dev, struct net_device *slave[2],
 			 unsigned char multicast_spec, u8 protocol_version);
 void hsr_prp_check_carrier_and_operstate(struct hsr_prp_priv *priv);

+ 26 - 6
net/hsr-prp/hsr_prp_main.c

@@ -15,6 +15,17 @@
 #include "hsr_prp_framereg.h"
 #include "hsr_prp_slave.h"
 
+static int netdev_notify(struct notifier_block *nb, unsigned long event,
+			 void *ptr);
+
+static struct notifier_block hsr_nb = {
+	.notifier_call = netdev_notify,	/* Slave event notifications */
+};
+
+static struct notifier_block prp_nb = {
+	.notifier_call = netdev_notify,	/* Slave event notifications */
+};
+
 static int netdev_notify(struct notifier_block *nb, unsigned long event,
 			 void *ptr)
 {
@@ -39,6 +50,11 @@ static int netdev_notify(struct notifier_block *nb, unsigned long event,
 		priv = port->priv;
 	}
 
+	if (priv->prot_version <= HSR_V1 && nb != &hsr_nb)
+		return NOTIFY_DONE;
+	else if (priv->prot_version == PRP_V1 && nb != &prp_nb)
+		return NOTIFY_DONE;
+
 	switch (event) {
 	case NETDEV_UP:		/* Administrative state DOWN */
 	case NETDEV_DOWN:	/* Administrative state UP */
@@ -103,16 +119,20 @@ struct hsr_prp_port *hsr_prp_get_port(struct hsr_prp_priv *priv,
 	return NULL;
 }
 
-static struct notifier_block hsr_nb = {
-	.notifier_call = netdev_notify,	/* Slave event notifications */
-};
-
-int hsr_prp_register_notifier(void)
+int hsr_prp_register_notifier(u8 proto)
 {
+	if (proto == PRP)
+		return register_netdevice_notifier(&prp_nb);
+
 	return register_netdevice_notifier(&hsr_nb);
 }
 
-void hsr_prp_unregister_notifier(void)
+void hsr_prp_unregister_notifier(u8 proto)
 {
+	if (proto == PRP) {
+		unregister_netdevice_notifier(&prp_nb);
+		return;
+	}
+
 	unregister_netdevice_notifier(&hsr_nb);
 }

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

@@ -151,6 +151,8 @@ struct hsr_prp_port {
 	enum hsr_prp_port_type	type;
 };
 
+#define HSR	0
+#define PRP	1
 struct hsr_prp_priv {
 	struct rcu_head		rcu_head;
 	struct list_head	ports;
@@ -163,6 +165,9 @@ struct hsr_prp_priv {
 	u16 sup_sequence_nr;	/* For HSRv1 separate seq_nr for supervision */
 	u8 prot_version;		/* Indicate if HSRv0 or HSRv1. */
 	spinlock_t seqnr_lock;			/* locking for sequence_nr */
+#define HSR_V0	0
+#define HSR_V1	1
+#define PRP_V1	2
 	unsigned char		sup_multicast_addr[ETH_ALEN];
 #ifdef	CONFIG_DEBUG_FS
 	struct dentry *node_tbl_root;
@@ -185,8 +190,9 @@ static inline u16 hsr_get_skb_sequence_nr(struct sk_buff *skb)
 	return ntohs(hsr_ethhdr->hsr_tag.sequence_nr);
 }
 
-int hsr_prp_register_notifier(void);
-void hsr_prp_unregister_notifier(void);
+int hsr_prp_register_notifier(u8 proto);
+void hsr_prp_unregister_notifier(u8 proto);
+
 #if IS_ENABLED(CONFIG_DEBUG_FS)
 int hsr_prp_debugfs_init(struct hsr_prp_priv *priv, struct net_device *ndev);
 void hsr_prp_debugfs_term(struct hsr_prp_priv *priv);

+ 33 - 0
net/hsr-prp/prp_main.c

@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * prp_main.c: hsr initialization code. This is based on hsr_main.c
+ *
+ * Copyright (C) 2017 Texas Instruments Incorporated
+ *
+ * Author(s):
+ *	Murali Karicheri <m-karicheri2@ti.com>
+ */
+#include <linux/netdevice.h>
+
+#include "hsr_prp_main.h"
+#include "prp_netlink.h"
+
+static int __init prp_init(void)
+{
+	int res;
+
+	res = hsr_prp_register_notifier(PRP);
+	if (!res)
+		res = prp_netlink_init();
+
+	return res;
+}
+
+static void __exit prp_exit(void)
+{
+	hsr_prp_unregister_notifier(PRP);
+	prp_netlink_exit();
+}
+
+module_init(prp_init);
+MODULE_LICENSE("GPL");

+ 443 - 0
net/hsr-prp/prp_netlink.c

@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * prp_netlink.c:  Routines for handling Netlink messages for PRP.
+ * This is based on hsr_netlink.c from Arvid Brodin, arvid.brodin@alten.se
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Author(s):
+ *	Murali Karicheri <m-karicheri2@ti.com>
+ */
+
+#include <linux/kernel.h>
+#include <net/rtnetlink.h>
+#include <net/genetlink.h>
+
+#include "prp_netlink.h"
+#include "hsr_prp_main.h"
+#include "hsr_prp_device.h"
+#include "hsr_prp_framereg.h"
+
+static const struct nla_policy prp_policy[IFLA_PRP_MAX + 1] = {
+	[IFLA_PRP_SLAVE1]		= { .type = NLA_U32 },
+	[IFLA_PRP_SLAVE2]		= { .type = NLA_U32 },
+	[IFLA_PRP_MULTICAST_SPEC]	= { .type = NLA_U8 },
+	[IFLA_PRP_SUPERVISION_ADDR]	= { .len = ETH_ALEN },
+	[IFLA_PRP_SEQ_NR]		= { .type = NLA_U16 },
+};
+
+/* Here, it seems a netdevice has already been allocated for us, and the
+ * hsr_prp_dev_setup routine has been executed. Nice!
+ */
+static int prp_newlink(struct net *src_net, struct net_device *dev,
+		       struct nlattr *tb[], struct nlattr *data[],
+		       struct netlink_ext_ack *extack)
+{
+	struct net_device *link[2];
+	unsigned char multicast_spec;
+
+	if (!data) {
+		netdev_info(dev, "PRP: No slave devices specified\n");
+		return -EINVAL;
+	}
+	if (!data[IFLA_PRP_SLAVE1]) {
+		netdev_info(dev, "PRP: Slave1 device not specified\n");
+		return -EINVAL;
+	}
+	link[0] = __dev_get_by_index(src_net,
+				     nla_get_u32(data[IFLA_PRP_SLAVE1]));
+	if (!data[IFLA_PRP_SLAVE2]) {
+		netdev_info(dev, "PRP: Slave2 device not specified\n");
+		return -EINVAL;
+	}
+	link[1] = __dev_get_by_index(src_net,
+				     nla_get_u32(data[IFLA_PRP_SLAVE2]));
+
+	if (!link[0] || !link[1])
+		return -ENODEV;
+	if (link[0] == link[1])
+		return -EINVAL;
+
+	if (!data[IFLA_PRP_MULTICAST_SPEC])
+		multicast_spec = 0;
+	else
+		multicast_spec = nla_get_u8(data[IFLA_PRP_MULTICAST_SPEC]);
+
+	return hsr_prp_dev_finalize(dev, link, multicast_spec, PRP_V1);
+}
+
+static int prp_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+	struct hsr_prp_priv *priv;
+	struct hsr_prp_port *port;
+	int res;
+
+	priv = netdev_priv(dev);
+
+	res = 0;
+
+	rcu_read_lock();
+	port = hsr_prp_get_port(priv, HSR_PRP_PT_SLAVE_A);
+	if (port)
+		res = nla_put_u32(skb, IFLA_PRP_SLAVE1, port->dev->ifindex);
+	rcu_read_unlock();
+	if (res)
+		goto nla_put_failure;
+
+	rcu_read_lock();
+	port = hsr_prp_get_port(priv, HSR_PRP_PT_SLAVE_B);
+	if (port)
+		res = nla_put_u32(skb, IFLA_PRP_SLAVE2, port->dev->ifindex);
+	rcu_read_unlock();
+	if (res)
+		goto nla_put_failure;
+
+	if (nla_put(skb, IFLA_PRP_SUPERVISION_ADDR, ETH_ALEN,
+		    priv->sup_multicast_addr) ||
+	    nla_put_u16(skb, IFLA_PRP_SEQ_NR, priv->sequence_nr))
+		goto nla_put_failure;
+
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+
+static struct rtnl_link_ops prp_link_ops __read_mostly = {
+	.kind		= "prp",
+	.maxtype	= IFLA_PRP_MAX,
+	.policy		= prp_policy,
+	.priv_size	= sizeof(struct hsr_prp_priv),
+	.setup		= prp_dev_setup,
+	.newlink	= prp_newlink,
+	.fill_info	= prp_fill_info,
+};
+
+/* NLA_BINARY missing in libnl; use NLA_UNSPEC in userspace instead. */
+static const struct nla_policy prp_genl_policy[PRP_A_MAX + 1] = {
+	[PRP_A_NODE_ADDR] = { .type = NLA_BINARY, .len = ETH_ALEN },
+	[PRP_A_NODE_ADDR_B] = { .type = NLA_BINARY, .len = ETH_ALEN },
+	[PRP_A_IFINDEX] = { .type = NLA_U32 },
+	[PRP_A_IF1_AGE] = { .type = NLA_U32 },
+	[PRP_A_IF2_AGE] = { .type = NLA_U32 },
+	[PRP_A_IF1_SEQ] = { .type = NLA_U16 },
+	[PRP_A_IF2_SEQ] = { .type = NLA_U16 },
+};
+
+static struct genl_family prp_genl_family;
+
+static const struct genl_multicast_group prp_mcgrps[] = {
+	{ .name = "prp-network", },
+};
+
+/* This is called when we haven't heard from the node with MAC address addr for
+ * some time (just before the node is removed from the node table/list).
+ */
+void prp_nl_nodedown(struct hsr_prp_priv *priv, unsigned char addr[ETH_ALEN])
+{
+	struct sk_buff *skb;
+	void *msg_head;
+	struct hsr_prp_port *master;
+	int res;
+
+	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+	if (!skb)
+		goto fail;
+
+	msg_head = genlmsg_put(skb, 0, 0, &prp_genl_family, 0, PRP_C_NODE_DOWN);
+	if (!msg_head)
+		goto nla_put_failure;
+
+	res = nla_put(skb, PRP_A_NODE_ADDR, ETH_ALEN, addr);
+	if (res < 0)
+		goto nla_put_failure;
+
+	genlmsg_end(skb, msg_head);
+	genlmsg_multicast(&prp_genl_family, skb, 0, 0, GFP_ATOMIC);
+
+	return;
+
+nla_put_failure:
+	kfree_skb(skb);
+
+fail:
+	rcu_read_lock();
+	master = hsr_prp_get_port(priv, HSR_PRP_PT_MASTER);
+	netdev_warn(master->dev, "Could not send PRP node down\n");
+	rcu_read_unlock();
+}
+
+/* PRP_C_GET_NODE_STATUS lets userspace query the internal PRP node table
+ * about the status of a specific node in the network, defined by its MAC
+ * address.
+ *
+ * Input: hsr ifindex, node mac address
+ * Output: hsr ifindex, node mac address (copied from request),
+ *	   age of latest frame from node over slave 1, slave 2 [ms]
+ */
+static int prp_get_node_status(struct sk_buff *skb_in, struct genl_info *info)
+{
+	/* For receiving */
+	struct nlattr *na;
+	struct net_device *hsr_dev;
+
+	/* For sending */
+	struct sk_buff *skb_out;
+	void *msg_head;
+	struct hsr_prp_priv *priv;
+	struct hsr_prp_port *port;
+	unsigned char hsr_node_addr_b[ETH_ALEN];
+	int hsr_node_if1_age;
+	u16 hsr_node_if1_seq;
+	int hsr_node_if2_age;
+	u16 hsr_node_if2_seq;
+	int addr_b_ifindex;
+	int res;
+
+	if (!info)
+		goto invalid;
+
+	na = info->attrs[PRP_A_IFINDEX];
+	if (!na)
+		goto invalid;
+	na = info->attrs[PRP_A_NODE_ADDR];
+	if (!na)
+		goto invalid;
+
+	hsr_dev = __dev_get_by_index(genl_info_net(info),
+				     nla_get_u32(info->attrs[PRP_A_IFINDEX]));
+	if (!hsr_dev)
+		goto invalid;
+	if (!is_hsr_prp_master(hsr_dev))
+		goto invalid;
+
+	/* Send reply */
+	skb_out = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!skb_out) {
+		res = -ENOMEM;
+		goto fail;
+	}
+
+	msg_head = genlmsg_put(skb_out, NETLINK_CB(skb_in).portid,
+			       info->snd_seq, &prp_genl_family, 0,
+			       PRP_C_SET_NODE_STATUS);
+	if (!msg_head) {
+		res = -ENOMEM;
+		goto nla_put_failure;
+	}
+
+	res = nla_put_u32(skb_out, PRP_A_IFINDEX, hsr_dev->ifindex);
+	if (res < 0)
+		goto nla_put_failure;
+
+	priv = netdev_priv(hsr_dev);
+	res = hsr_prp_get_node_data(priv,
+				    (unsigned char *)
+				    nla_data(info->attrs[PRP_A_NODE_ADDR]),
+				    hsr_node_addr_b, &addr_b_ifindex,
+				    &hsr_node_if1_age, &hsr_node_if1_seq,
+				    &hsr_node_if2_age, &hsr_node_if2_seq);
+	if (res < 0)
+		goto nla_put_failure;
+
+	res = nla_put(skb_out, PRP_A_NODE_ADDR, ETH_ALEN,
+		      nla_data(info->attrs[PRP_A_NODE_ADDR]));
+	if (res < 0)
+		goto nla_put_failure;
+
+	if (addr_b_ifindex > -1) {
+		res = nla_put(skb_out, PRP_A_NODE_ADDR_B, ETH_ALEN,
+			      hsr_node_addr_b);
+		if (res < 0)
+			goto nla_put_failure;
+
+		res = nla_put_u32(skb_out, PRP_A_ADDR_B_IFINDEX,
+				  addr_b_ifindex);
+		if (res < 0)
+			goto nla_put_failure;
+	}
+
+	res = nla_put_u32(skb_out, PRP_A_IF1_AGE, hsr_node_if1_age);
+	if (res < 0)
+		goto nla_put_failure;
+	res = nla_put_u16(skb_out, PRP_A_IF1_SEQ, hsr_node_if1_seq);
+	if (res < 0)
+		goto nla_put_failure;
+	rcu_read_lock();
+	port = hsr_prp_get_port(priv, HSR_PRP_PT_SLAVE_A);
+	if (port)
+		res = nla_put_u32(skb_out, PRP_A_IF1_IFINDEX,
+				  port->dev->ifindex);
+	rcu_read_unlock();
+	if (res < 0)
+		goto nla_put_failure;
+
+	res = nla_put_u32(skb_out, PRP_A_IF2_AGE, hsr_node_if2_age);
+	if (res < 0)
+		goto nla_put_failure;
+	res = nla_put_u16(skb_out, PRP_A_IF2_SEQ, hsr_node_if2_seq);
+	if (res < 0)
+		goto nla_put_failure;
+	rcu_read_lock();
+	port = hsr_prp_get_port(priv, HSR_PRP_PT_SLAVE_B);
+	if (port)
+		res = nla_put_u32(skb_out, PRP_A_IF2_IFINDEX,
+				  port->dev->ifindex);
+	rcu_read_unlock();
+	if (res < 0)
+		goto nla_put_failure;
+
+	genlmsg_end(skb_out, msg_head);
+	genlmsg_unicast(genl_info_net(info), skb_out, info->snd_portid);
+
+	return 0;
+
+invalid:
+	netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL);
+	return 0;
+
+nla_put_failure:
+	kfree_skb(skb_out);
+	/* Fall through */
+
+fail:
+	return res;
+}
+
+/* Get a list of mac_address_a of all nodes known to this node (including self).
+ */
+static int prp_get_node_list(struct sk_buff *skb_in, struct genl_info *info)
+{
+	/* For receiving */
+	struct nlattr *na;
+	struct net_device *hsr_dev;
+
+	/* For sending */
+	struct sk_buff *skb_out;
+	void *msg_head;
+	struct hsr_prp_priv *priv;
+	void *pos;
+	unsigned char addr[ETH_ALEN];
+	int res;
+
+	if (!info)
+		goto invalid;
+
+	na = info->attrs[PRP_A_IFINDEX];
+	if (!na)
+		goto invalid;
+
+	hsr_dev = __dev_get_by_index(genl_info_net(info),
+				     nla_get_u32(info->attrs[PRP_A_IFINDEX]));
+	if (!hsr_dev)
+		goto invalid;
+	if (!is_hsr_prp_master(hsr_dev))
+		goto invalid;
+
+	/* Send reply */
+	skb_out = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!skb_out) {
+		res = -ENOMEM;
+		goto fail;
+	}
+
+	msg_head = genlmsg_put(skb_out, NETLINK_CB(skb_in).portid,
+			       info->snd_seq, &prp_genl_family, 0,
+			       PRP_C_SET_NODE_LIST);
+	if (!msg_head) {
+		res = -ENOMEM;
+		goto nla_put_failure;
+	}
+
+	res = nla_put_u32(skb_out, PRP_A_IFINDEX, hsr_dev->ifindex);
+	if (res < 0)
+		goto nla_put_failure;
+
+	priv = netdev_priv(hsr_dev);
+
+	rcu_read_lock();
+	pos = hsr_prp_get_next_node(priv, NULL, addr);
+	while (pos) {
+		res = nla_put(skb_out, PRP_A_NODE_ADDR, ETH_ALEN, addr);
+		if (res < 0) {
+			rcu_read_unlock();
+			goto nla_put_failure;
+		}
+		pos = hsr_prp_get_next_node(priv, pos, addr);
+	}
+	rcu_read_unlock();
+
+	genlmsg_end(skb_out, msg_head);
+	genlmsg_unicast(genl_info_net(info), skb_out, info->snd_portid);
+
+	return 0;
+
+invalid:
+	netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL);
+	return 0;
+
+nla_put_failure:
+	kfree_skb(skb_out);
+	/* Fall through */
+
+fail:
+	return res;
+}
+
+static const struct genl_ops prp_ops[] = {
+	{
+		.cmd = PRP_C_GET_NODE_STATUS,
+		.flags = 0,
+		.policy = prp_genl_policy,
+		.doit = prp_get_node_status,
+		.dumpit = NULL,
+	},
+	{
+		.cmd = PRP_C_GET_NODE_LIST,
+		.flags = 0,
+		.policy = prp_genl_policy,
+		.doit = prp_get_node_list,
+		.dumpit = NULL,
+	},
+};
+
+static struct genl_family prp_genl_family __ro_after_init = {
+	.hdrsize = 0,
+	.name = "PRP",
+	.version = 1,
+	.maxattr = PRP_A_MAX,
+	.module = THIS_MODULE,
+	.ops = prp_ops,
+	.n_ops = ARRAY_SIZE(prp_ops),
+	.mcgrps = prp_mcgrps,
+	.n_mcgrps = ARRAY_SIZE(prp_mcgrps),
+};
+
+int __init prp_netlink_init(void)
+{
+	int rc;
+
+	rc = rtnl_link_register(&prp_link_ops);
+	if (rc)
+		goto fail_rtnl_link_register;
+
+	rc = genl_register_family(&prp_genl_family);
+	if (rc)
+		goto fail_genl_register_family;
+
+	return 0;
+
+fail_genl_register_family:
+	rtnl_link_unregister(&prp_link_ops);
+fail_rtnl_link_register:
+
+	return rc;
+}
+
+void __exit prp_netlink_exit(void)
+{
+	genl_unregister_family(&prp_genl_family);
+	rtnl_link_unregister(&prp_link_ops);
+}
+
+MODULE_ALIAS_RTNL_LINK("prp");

+ 27 - 0
net/hsr-prp/prp_netlink.h

@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * prp_netlink.h:
+ * This is based on hsr_netlink.h from Arvid Brodin, arvid.brodin@alten.se
+ *
+ * Copyright (C) 2017-2018 Texas Instruments Incorporated
+ *
+ * Author(s):
+ *	Murali Karicheri <m-karicheri2@ti.com>
+ */
+
+#ifndef __PRP_NETLINK_H
+#define __PRP_NETLINK_H
+
+#include <linux/if_ether.h>
+#include <linux/module.h>
+#include <uapi/linux/prp_netlink.h>
+
+struct hsr_prp_priv;
+struct hsr_prp_port;
+
+int __init prp_netlink_init(void);
+void __exit prp_netlink_exit(void);
+
+void prp_nl_nodedown(struct hsr_prp_priv *priv, unsigned char addr[ETH_ALEN]);
+
+#endif /* __PRP_NETLINK_H */