Add support for offloading of HSR/PRP (IEC 62439-3) tag insertion
tag removal, duplicate generation and forwarding on DSA switches.

Add DSA_NOTIFIER_HSR_JOIN and DSA_NOTIFIER_HSR_LEAVE which trigger calls
to .port_hsr_join and .port_hsr_leave in the DSA driver for the switch.

The DSA switch driver should then set netdev feature flags for the
HSR/PRP operation that it offloads.
    NETIF_F_HW_HSR_TAG_INS
    NETIF_F_HW_HSR_TAG_RM
    NETIF_F_HW_HSR_FWD
    NETIF_F_HW_HSR_DUP

Signed-off-by: George McCollister <george.mccollis...@gmail.com>
---
 include/net/dsa.h  | 13 +++++++++++++
 net/dsa/dsa_priv.h | 11 +++++++++++
 net/dsa/port.c     | 34 ++++++++++++++++++++++++++++++++++
 net/dsa/slave.c    | 13 +++++++++++++
 net/dsa/switch.c   | 24 ++++++++++++++++++++++++
 5 files changed, 95 insertions(+)

diff --git a/include/net/dsa.h b/include/net/dsa.h
index 2f5435d3d1db..584e2b5c02e0 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -167,6 +167,10 @@ struct dsa_switch_tree {
        list_for_each_entry((_dp), &(_dst)->ports, list)        \
                if ((_dp)->lag_dev == (_lag))
 
+#define dsa_hsr_foreach_port(_dp, _ds, _hsr)                   \
+       list_for_each_entry((_dp), &(_ds)->dst->ports, list)    \
+               if ((_dp)->ds == (_ds) && (_dp)->hsr_dev == (_hsr))
+
 static inline struct net_device *dsa_lag_dev(struct dsa_switch_tree *dst,
                                             unsigned int id)
 {
@@ -257,6 +261,7 @@ struct dsa_port {
        struct phylink_config   pl_config;
        struct net_device       *lag_dev;
        bool                    lag_tx_enabled;
+       struct net_device       *hsr_dev;
 
        struct list_head list;
 
@@ -753,6 +758,14 @@ struct dsa_switch_ops {
                                 struct netdev_lag_upper_info *info);
        int     (*port_lag_leave)(struct dsa_switch *ds, int port,
                                  struct net_device *lag);
+
+       /*
+        * HSR integration
+        */
+       int     (*port_hsr_join)(struct dsa_switch *ds, int port,
+                                struct net_device *hsr);
+       void    (*port_hsr_leave)(struct dsa_switch *ds, int port,
+                                 struct net_device *hsr);
 };
 
 #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)           \
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 2ce46bb87703..9006f62f69cd 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -20,6 +20,8 @@ enum {
        DSA_NOTIFIER_BRIDGE_LEAVE,
        DSA_NOTIFIER_FDB_ADD,
        DSA_NOTIFIER_FDB_DEL,
+       DSA_NOTIFIER_HSR_JOIN,
+       DSA_NOTIFIER_HSR_LEAVE,
        DSA_NOTIFIER_LAG_CHANGE,
        DSA_NOTIFIER_LAG_JOIN,
        DSA_NOTIFIER_LAG_LEAVE,
@@ -94,6 +96,13 @@ struct dsa_switchdev_event_work {
        u16 vid;
 };
 
+/* DSA_NOTIFIER_HSR_* */
+struct dsa_notifier_hsr_info {
+       struct net_device *hsr;
+       int sw_index;
+       int port;
+};
+
 struct dsa_slave_priv {
        /* Copy of CPU port xmit for faster access in slave transmit hot path */
        struct sk_buff *        (*xmit)(struct sk_buff *skb,
@@ -174,6 +183,8 @@ int dsa_port_vlan_del(struct dsa_port *dp,
                      const struct switchdev_obj_port_vlan *vlan);
 int dsa_port_link_register_of(struct dsa_port *dp);
 void dsa_port_link_unregister_of(struct dsa_port *dp);
+int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr);
+void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr);
 extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
 
 static inline bool dsa_port_offloads_netdev(struct dsa_port *dp,
diff --git a/net/dsa/port.c b/net/dsa/port.c
index f5b0f72ee7cd..09738af4d32e 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -870,3 +870,37 @@ int dsa_port_get_phy_sset_count(struct dsa_port *dp)
        return ret;
 }
 EXPORT_SYMBOL_GPL(dsa_port_get_phy_sset_count);
+
+int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr)
+{
+       struct dsa_notifier_hsr_info info = {
+               .sw_index = dp->ds->index,
+               .port = dp->index,
+               .hsr = hsr,
+       };
+       int err;
+
+       dp->hsr_dev = hsr;
+
+       err = dsa_port_notify(dp, DSA_NOTIFIER_HSR_JOIN, &info);
+       if (err)
+               dp->hsr_dev = NULL;
+
+       return err;
+}
+
+void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr)
+{
+       struct dsa_notifier_hsr_info info = {
+               .sw_index = dp->ds->index,
+               .port = dp->index,
+               .hsr = hsr,
+       };
+       int err;
+
+       dp->hsr_dev = NULL;
+
+       err = dsa_port_notify(dp, DSA_NOTIFIER_HSR_LEAVE, &info);
+       if (err)
+               pr_err("DSA: failed to notify DSA_NOTIFIER_HSR_LEAVE\n");
+}
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index f2fb433f3828..fc7e3ff11c5c 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -1924,6 +1924,19 @@ static int dsa_slave_changeupper(struct net_device *dev,
                        dsa_port_lag_leave(dp, info->upper_dev);
                        err = NOTIFY_OK;
                }
+       } else if (netif_is_hsr_master(info->upper_dev)) {
+               if (info->linking) {
+                       err = dsa_port_hsr_join(dp, info->upper_dev);
+                       if (err == -EOPNOTSUPP) {
+                               NL_SET_ERR_MSG_MOD(info->info.extack,
+                                                  "Offloading not supported");
+                               err = 0;
+                       }
+                       err = notifier_from_errno(err);
+               } else {
+                       dsa_port_hsr_leave(dp, info->upper_dev);
+                       err = NOTIFY_OK;
+               }
        }
 
        return err;
diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index cc0b25f3adea..c1e5083f2cfc 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -166,6 +166,24 @@ static int dsa_switch_fdb_del(struct dsa_switch *ds,
        return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
 }
 
+static int dsa_switch_hsr_join(struct dsa_switch *ds,
+                              struct dsa_notifier_hsr_info *info)
+{
+       if (ds->index == info->sw_index && ds->ops->port_hsr_join)
+               return ds->ops->port_hsr_join(ds, info->port, info->hsr);
+
+       return 0;
+}
+
+static int dsa_switch_hsr_leave(struct dsa_switch *ds,
+                               struct dsa_notifier_hsr_info *info)
+{
+       if (ds->index == info->sw_index && ds->ops->port_hsr_leave)
+               ds->ops->port_hsr_leave(ds, info->port, info->hsr);
+
+       return 0;
+}
+
 static int dsa_switch_lag_change(struct dsa_switch *ds,
                                 struct dsa_notifier_lag_info *info)
 {
@@ -319,6 +337,12 @@ static int dsa_switch_event(struct notifier_block *nb,
        case DSA_NOTIFIER_FDB_DEL:
                err = dsa_switch_fdb_del(ds, info);
                break;
+       case DSA_NOTIFIER_HSR_JOIN:
+               err = dsa_switch_hsr_join(ds, info);
+               break;
+       case DSA_NOTIFIER_HSR_LEAVE:
+               err = dsa_switch_hsr_leave(ds, info);
+               break;
        case DSA_NOTIFIER_LAG_CHANGE:
                err = dsa_switch_lag_change(ds, info);
                break;
-- 
2.11.0

Reply via email to