[RFC PATCH 3/4] net: dsa: implement a shared FDB dump procedure
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: 2021-08-21 21:01:16
Subsystem:
networking [dsa], networking [general], the rest · Maintainers:
Andrew Lunn, Vladimir Oltean, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
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 --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) {
--
2.25.1