diff mbox series

[RFC,3/4] net: dsa: implement a shared FDB dump procedure

Message ID 20210821210018.1314952-4-vladimir.oltean@nxp.com
State New
Headers show
Series Faster ndo_fdb_dump for drivers with shared FDB | expand

Commit Message

Vladimir Oltean Aug. 21, 2021, 9 p.m. UTC
Create a list of FDB entries per switch that will be:

- populated during the ndo_fdb_dump prepare phase
- looked up during the ndo_fdb_dump commit phase
- freed during the ndo_fdb_dump finish phase

Also a bool ds->shared_fdb_dump_in_progress to denote whether we should
perform the shared FDB dump or the normal FDB dump procedure (since the
shared FDB dump needs more memory, we prefer the per-port procedure for
dumps that target a single port).

Introduce a new dsa_switch_ops method for the shared FDB dump. This is
"switch_fdb_dump" and lacks a "port" argument - instead, the switch is
supposed to provide the port for each FDB entry it finds.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
 include/net/dsa.h  |  17 ++++
 net/dsa/dsa2.c     |   2 +
 net/dsa/dsa_priv.h |   1 +
 net/dsa/slave.c    | 194 ++++++++++++++++++++++++++++++++++++++-------
 net/dsa/switch.c   |   8 ++
 5 files changed, 195 insertions(+), 27 deletions(-)
diff mbox series

Patch

diff --git a/include/net/dsa.h b/include/net/dsa.h
index 0c2cba45fa79..23b675f843f4 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -312,8 +312,17 @@  struct dsa_mac_addr {
 	struct list_head list;
 };
 
+struct dsa_fdb_entry {
+	unsigned char addr[ETH_ALEN];
+	u16 vid;
+	bool is_static;
+	struct net_device *dev;
+	struct list_head list;
+};
+
 struct dsa_switch {
 	bool setup;
+	bool shared_fdb_dump_in_progress;
 
 	struct device *dev;
 
@@ -355,6 +364,9 @@  struct dsa_switch {
 	/* Storage for drivers using tag_8021q */
 	struct dsa_8021q_context *tag_8021q_ctx;
 
+	/* Storage for shared FDB dumps */
+	struct list_head	fdb_list;
+
 	/* devlink used to represent this switch device */
 	struct devlink		*devlink;
 
@@ -565,6 +577,9 @@  struct net_device *dsa_port_to_bridge_port(const struct dsa_port *dp)
 
 typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid,
 			      bool is_static, void *data);
+typedef int dsa_switch_fdb_dump_cb_t(struct dsa_switch *ds, int port,
+				     const unsigned char *addr, u16 vid,
+				     bool is_static);
 struct dsa_switch_ops {
 	/*
 	 * Tagging protocol helpers called for the CPU ports and DSA links.
@@ -737,6 +752,8 @@  struct dsa_switch_ops {
 				const unsigned char *addr, u16 vid);
 	int	(*port_fdb_dump)(struct dsa_switch *ds, int port,
 				 dsa_fdb_dump_cb_t *cb, void *data);
+	int	(*switch_fdb_dump)(struct dsa_switch *ds,
+				   dsa_switch_fdb_dump_cb_t *cb);
 
 	/*
 	 * Multicast database
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index dcd67801eca4..99b5aad46b02 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -801,6 +801,8 @@  static int dsa_switch_setup(struct dsa_switch *ds)
 			goto teardown;
 	}
 
+	INIT_LIST_HEAD(&ds->fdb_list);
+
 	ds->setup = true;
 
 	return 0;
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index b7a269e0513f..c8306b1f1c11 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -533,6 +533,7 @@  static inline void *dsa_etype_header_pos_tx(struct sk_buff *skb)
 /* switch.c */
 int dsa_switch_register_notifier(struct dsa_switch *ds);
 void dsa_switch_unregister_notifier(struct dsa_switch *ds);
+int dsa_switch_fdb_dump(struct dsa_switch *ds, dsa_switch_fdb_dump_cb_t *cb);
 
 /* dsa2.c */
 void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag);
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index 9331093a84dd..ba864c5d1350 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -186,23 +186,18 @@  struct dsa_slave_dump_ctx {
 	int idx;
 };
 
-static int
-dsa_slave_port_fdb_do_dump(const unsigned char *addr, u16 vid,
-			   bool is_static, void *data)
-{
-	struct dsa_slave_dump_ctx *dump = data;
-	u32 portid = NETLINK_CB(dump->cb->skb).portid;
-	u32 seq = dump->cb->nlh->nlmsg_seq;
-	struct rtnl_fdb_dump_ctx *ctx;
+static int dsa_nlmsg_populate_fdb(struct sk_buff *skb,
+				  struct netlink_callback *cb,
+				  struct net_device *dev,
+				  const unsigned char *addr, u16 vid,
+				  bool is_static)
+{
+	u32 portid = NETLINK_CB(cb->skb).portid;
+	u32 seq = cb->nlh->nlmsg_seq;
 	struct nlmsghdr *nlh;
 	struct ndmsg *ndm;
 
-	ctx = (struct rtnl_fdb_dump_ctx *)dump->cb->ctx;
-
-	if (dump->idx < ctx->fidx)
-		goto skip;
-
-	nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
+	nlh = nlmsg_put(skb, portid, seq, RTM_NEWNEIGH,
 			sizeof(*ndm), NLM_F_MULTI);
 	if (!nlh)
 		return -EMSGSIZE;
@@ -213,32 +208,152 @@  dsa_slave_port_fdb_do_dump(const unsigned char *addr, u16 vid,
 	ndm->ndm_pad2    = 0;
 	ndm->ndm_flags   = NTF_SELF;
 	ndm->ndm_type    = 0;
-	ndm->ndm_ifindex = dump->dev->ifindex;
+	ndm->ndm_ifindex = dev->ifindex;
 	ndm->ndm_state   = is_static ? NUD_NOARP : NUD_REACHABLE;
 
-	if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, addr))
+	if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr))
 		goto nla_put_failure;
 
-	if (vid && nla_put_u16(dump->skb, NDA_VLAN, vid))
+	if (vid && nla_put_u16(skb, NDA_VLAN, vid))
 		goto nla_put_failure;
 
-	nlmsg_end(dump->skb, nlh);
+	nlmsg_end(skb, nlh);
 
-skip:
-	dump->idx++;
 	return 0;
 
 nla_put_failure:
-	nlmsg_cancel(dump->skb, nlh);
+	nlmsg_cancel(skb, nlh);
 	return -EMSGSIZE;
 }
 
+static int dsa_switch_shared_fdb_save_one(struct dsa_switch *ds, int port,
+					  const unsigned char *addr, u16 vid,
+					  bool is_static)
+{
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	struct dsa_fdb_entry *fdb;
+
+	if (!dsa_port_is_user(dp))
+		return 0;
+
+	/* Will be freed during the finish phase */
+	fdb = kzalloc(sizeof(*fdb), GFP_KERNEL);
+	if (!fdb)
+		return -ENOMEM;
+
+	ether_addr_copy(fdb->addr, addr);
+	fdb->vid = vid;
+	fdb->is_static = is_static;
+	fdb->dev = dp->slave;
+	list_add_tail(&fdb->list, &ds->fdb_list);
+
+	return 0;
+}
+
+/* If the switch does not support shared FDB dump, do nothing and do the work
+ * in the commit phase.
+ */
+static int dsa_shared_fdb_dump_prepare(struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct dsa_switch *ds = dp->ds;
+	int err;
+
+	if (!ds->ops->switch_fdb_dump)
+		return 0;
+
+	if (ds->shared_fdb_dump_in_progress)
+		return 0;
+
+	/* If this switch's FDB has not been dumped before during this
+	 * prepare/commit/finish cycle, dump it now and save the results.
+	 */
+	err = dsa_switch_fdb_dump(ds, dsa_switch_shared_fdb_save_one);
+	if (err)
+		return err;
+
+	ds->shared_fdb_dump_in_progress = true;
+
+	return 0;
+}
+
 static int
-dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
-		   struct net_device *dev, struct net_device *filter_dev,
-		   int *idx)
+dsa_shared_fdb_dump_commit(struct sk_buff *skb, struct netlink_callback *cb,
+			   struct net_device *dev, int *idx)
 {
 	struct rtnl_fdb_dump_ctx *ctx = (struct rtnl_fdb_dump_ctx *)cb->ctx;
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct dsa_switch *ds = dp->ds;
+	struct dsa_fdb_entry *fdb;
+	int err;
+
+	/* Dump the FDB entries corresponding to the requested port from the
+	 * saved results.
+	 */
+	list_for_each_entry(fdb, &ds->fdb_list, list) {
+		if (fdb->dev != dev)
+			continue;
+
+		if (*idx < ctx->fidx)
+			goto skip;
+
+		err = dsa_nlmsg_populate_fdb(skb, cb, dev, fdb->addr, fdb->vid,
+					     fdb->is_static);
+		if (err)
+			return err;
+
+skip:
+		*idx += 1;
+	}
+
+	return 0;
+}
+
+/* Tear down the context stored during the shared FDB dump */
+static void dsa_shared_fdb_dump_finish(struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct dsa_fdb_entry *fdb, *tmp;
+	struct dsa_switch *ds = dp->ds;
+
+	if (!ds->shared_fdb_dump_in_progress)
+		return;
+
+	list_for_each_entry_safe(fdb, tmp, &ds->fdb_list, list) {
+		list_del(&fdb->list);
+		kfree(fdb);
+	}
+
+	ds->shared_fdb_dump_in_progress = false;
+}
+
+static int
+dsa_slave_port_fdb_do_dump(const unsigned char *addr, u16 vid,
+			   bool is_static, void *data)
+{
+	struct dsa_slave_dump_ctx *dump = data;
+	struct rtnl_fdb_dump_ctx *ctx;
+	int err;
+
+	ctx = (struct rtnl_fdb_dump_ctx *)dump->cb->ctx;
+
+	if (dump->idx < ctx->fidx)
+		goto skip;
+
+	err = dsa_nlmsg_populate_fdb(dump->skb, dump->cb, dump->dev, addr, vid,
+				     is_static);
+	if (err)
+		return err;
+
+skip:
+	dump->idx++;
+	return 0;
+}
+
+static int
+dsa_slave_fdb_dump_single(struct sk_buff *skb, struct netlink_callback *cb,
+			  struct net_device *dev, int *idx)
+{
 	struct dsa_port *dp = dsa_slave_to_port(dev);
 	struct dsa_slave_dump_ctx dump = {
 		.dev = dev,
@@ -248,15 +363,40 @@  dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
 	};
 	int err;
 
-	if (ctx->state != RTNL_FDB_DUMP_COMMIT)
-		return 0;
-
 	err = dsa_port_fdb_dump(dp, dsa_slave_port_fdb_do_dump, &dump);
 	*idx = dump.idx;
 
 	return err;
 }
 
+static int
+dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
+		   struct net_device *dev, struct net_device *filter_dev,
+		   int *idx)
+{
+	struct rtnl_fdb_dump_ctx *ctx = (struct rtnl_fdb_dump_ctx *)cb->ctx;
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct dsa_switch *ds = dp->ds;
+	int err = 0;
+
+	switch (ctx->state) {
+	case RTNL_FDB_DUMP_PREPARE:
+		err = dsa_shared_fdb_dump_prepare(dev);
+		break;
+	case RTNL_FDB_DUMP_COMMIT:
+		if (ds->shared_fdb_dump_in_progress)
+			err = dsa_shared_fdb_dump_commit(skb, cb, dev, idx);
+		else
+			err = dsa_slave_fdb_dump_single(skb, cb, dev, idx);
+		break;
+	case RTNL_FDB_DUMP_FINISH:
+		dsa_shared_fdb_dump_finish(dev);
+		break;
+	}
+
+	return err;
+}
+
 static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
 	struct dsa_slave_priv *p = netdev_priv(dev);
diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index fd1a1c6bf9cf..a64613e1f99e 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -655,6 +655,14 @@  dsa_switch_mrp_del_ring_role(struct dsa_switch *ds,
 	return 0;
 }
 
+int dsa_switch_fdb_dump(struct dsa_switch *ds, dsa_switch_fdb_dump_cb_t *cb)
+{
+	if (!ds->ops->switch_fdb_dump)
+		return -EOPNOTSUPP;
+
+	return ds->ops->switch_fdb_dump(ds, cb);
+}
+
 static int dsa_switch_event(struct notifier_block *nb,
 			    unsigned long event, void *info)
 {