[net-next v2 11/11] bridge: cfm: Added CFM switchdev utilization.

From: Henrik Bjoernlund
Date: Thu Oct 01 2020 - 06:33:32 EST


The CFM kernel implementation is now trying to offload functionallity
in HW by utilizing the switchdev interface.

MEP instances are created/deleted and CCM frames are transmitted in HW.
Also handling of received CCM frames and the defect calculation is dome
in HW.

Reviewed-by: Horatiu Vultur <horatiu.vultur@xxxxxxxxxxxxx>
Signed-off-by: Henrik Bjoernlund <henrik.bjoernlund@xxxxxxxxxxxxx>
---
net/bridge/Makefile | 2 +-
net/bridge/br_cfm.c | 270 ++++++++++++++++++++++++++++++----
net/bridge/br_cfm_netlink.c | 51 +++----
net/bridge/br_cfm_switchdev.c | 203 +++++++++++++++++++++++++
net/bridge/br_private_cfm.h | 63 +++++++-
5 files changed, 530 insertions(+), 59 deletions(-)
create mode 100644 net/bridge/br_cfm_switchdev.c

diff --git a/net/bridge/Makefile b/net/bridge/Makefile
index 4702702a74d3..5d0a399825ef 100644
--- a/net/bridge/Makefile
+++ b/net/bridge/Makefile
@@ -28,4 +28,4 @@ obj-$(CONFIG_NETFILTER) += netfilter/

bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o

-bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o br_cfm_netlink.o
+bridge-$(CONFIG_BRIDGE_CFM) += br_cfm_switchdev.o br_cfm.o br_cfm_netlink.o
diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
index fc8268cb76c1..bfaee33acffb 100644
--- a/net/bridge/br_cfm.c
+++ b/net/bridge/br_cfm.c
@@ -184,9 +184,11 @@ static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep,
}
skb->dev = b_port->dev;
rcu_read_unlock();
- /* The device cannot be deleted until the work_queue functions has
- * completed. This function is called from ccm_tx_work_expired()
- * that is a work_queue functions.
+ /* This function is called from ccm_tx_work_expired that
+ * is a work_queue function.
+ * It is also called from br_cfm_cc_rdi_set and br_cfm_cc_ccm_tx
+ * that has the RTNL.
+ * Due to this the device cannot be deleted.
*/

skb->protocol = htons(ETH_P_CFM);
@@ -500,6 +502,7 @@ int br_cfm_mep_create(struct net_bridge *br,
{
struct net_bridge_port *p;
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -546,6 +549,11 @@ int br_cfm_mep_create(struct net_bridge *br,
}
}

+ /* Try create MEP in Switchdev */
+ swd_ret = br_cfm_switchdev_mep_create(br, instance, create, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
mep = kzalloc(sizeof(*mep), GFP_KERNEL);
if (!mep)
return -ENOMEM;
@@ -555,20 +563,21 @@ int br_cfm_mep_create(struct net_bridge *br,
rcu_assign_pointer(mep->b_port, p);

INIT_HLIST_HEAD(&mep->peer_mep_list);
- INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
-
- if (hlist_empty(&br->mep_list))
+ if ((swd_ret == -EOPNOTSUPP) && hlist_empty(&br->mep_list))
br_add_frame(br, &cfm_frame_type);
-
hlist_add_tail_rcu(&mep->head, &br->mep_list);

+ INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
+
return 0;
}

-static void mep_delete_implementation(struct net_bridge *br,
- struct br_cfm_mep *mep)
+static int mep_delete_implementation(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ struct netlink_ext_ack *extack)
{
struct br_cfm_peer_mep *peer_mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -579,14 +588,23 @@ static void mep_delete_implementation(struct net_bridge *br,
kfree_rcu(peer_mep, rcu);
}

- cancel_delayed_work_sync(&mep->ccm_tx_dwork);
-
RCU_INIT_POINTER(mep->b_port, NULL);
hlist_del_rcu(&mep->head);
- kfree_rcu(mep, rcu);

+ /* Try delete MEP in Switchdev */
+ swd_ret = br_cfm_switchdev_mep_delete(br, mep, extack);
+ if (swd_ret != -EOPNOTSUPP)
+ goto free;
+
+ swd_ret = 0;
if (hlist_empty(&br->mep_list))
br_del_frame(br, &cfm_frame_type);
+ cancel_delayed_work_sync(&mep->ccm_tx_dwork);
+
+free:
+ kfree_rcu(mep, rcu);
+
+ return swd_ret;
}

int br_cfm_mep_delete(struct net_bridge *br,
@@ -604,9 +622,7 @@ int br_cfm_mep_delete(struct net_bridge *br,
return -ENOENT;
}

- mep_delete_implementation(br, mep);
-
- return 0;
+ return mep_delete_implementation(br, mep, extack);
}

int br_cfm_mep_config_set(struct net_bridge *br,
@@ -615,6 +631,7 @@ int br_cfm_mep_config_set(struct net_bridge *br,
struct netlink_ext_ack *extack)
{
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -637,6 +654,11 @@ int br_cfm_mep_config_set(struct net_bridge *br,
return -ENOENT;
}

+ /* Try configure MEP in Switchdev */
+ swd_ret = br_cfm_switchdev_mep_config_set(br, mep, config, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
mep->config = *config;

return 0;
@@ -649,6 +671,7 @@ int br_cfm_cc_config_set(struct net_bridge *br,
{
struct br_cfm_peer_mep *peer_mep;
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -663,6 +686,19 @@ int br_cfm_cc_config_set(struct net_bridge *br,
if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0)
return 0;

+ /* Try configure CC in Switchdev */
+ swd_ret = br_cfm_switchdev_cc_config_set(br, mep, config, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
+ mep->cc_config = *config;
+ mep->ccm_rx_snumber = 0;
+ mep->ccm_tx_snumber = 1;
+
+ /* Return if switchdev. CCM is not transmitted or received here */
+ if (!swd_ret)
+ return 0;
+
if (config->enable && !mep->cc_config.enable)
/* CC is enabled */
hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head)
@@ -673,10 +709,6 @@ int br_cfm_cc_config_set(struct net_bridge *br,
hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head)
cc_peer_disable(peer_mep);

- mep->cc_config = *config;
- mep->ccm_rx_snumber = 0;
- mep->ccm_tx_snumber = 1;
-
return 0;
}

@@ -686,6 +718,7 @@ int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
{
struct br_cfm_peer_mep *peer_mep;
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -709,19 +742,29 @@ int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
return -EEXIST;
}

+ /* Try add peer MEP in Switchdev */
+ swd_ret = br_cfm_switchdev_cc_peer_mep_add(br, mep, mepid, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL);
if (!peer_mep)
return -ENOMEM;

peer_mep->mepid = mepid;
peer_mep->mep = mep;
+
+ hlist_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list);
+
+ /* Return if switchdev. CCM is not transmitted or received here */
+ if (!swd_ret)
+ return 0;
+
INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired);

if (mep->cc_config.enable)
cc_peer_enable(peer_mep);

- hlist_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list);
-
return 0;
}

@@ -731,6 +774,7 @@ int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
{
struct br_cfm_peer_mep *peer_mep;
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -748,18 +792,42 @@ int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
return -ENOENT;
}

+ hlist_del_rcu(&peer_mep->head);
+
+ /* Try remove peer MEP in Switchdev */
+ swd_ret = br_cfm_switchdev_cc_peer_mep_remove(br, mep, mepid, extack);
+ if (swd_ret != -EOPNOTSUPP)
+ goto free;
+
+ swd_ret = 0;
cc_peer_disable(peer_mep);

- hlist_del_rcu(&peer_mep->head);
+free:
kfree_rcu(peer_mep, rcu);

- return 0;
+ return swd_ret;
+}
+
+static int swd_ccm_tx(struct net_bridge *br, struct br_cfm_mep *mep,
+ struct netlink_ext_ack *extack)
+{
+ struct sk_buff *skb;
+
+ skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info);
+ if (!skb)
+ return -ENOMEM;
+ return br_cfm_switchdev_cc_ccm_tx(br, mep, skb,
+ mep->cc_ccm_tx_info.period,
+ mep->cc_config.exp_interval,
+ extack);
}

int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
const bool rdi, struct netlink_ext_ack *extack)
{
struct br_cfm_mep *mep;
+ bool rdi_changed;
+ int swd_ret;

ASSERT_RTNL();

@@ -770,8 +838,18 @@ int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
return -ENOENT;
}

+ rdi_changed = (mep->rdi != rdi) ? true : false;
mep->rdi = rdi;

+ if (mep->ccm_tx_swd && rdi_changed) {
+ /* Transmitting using Switchdev is ongoing.
+ * Restart with new RDI status
+ */
+ swd_ret = swd_ccm_tx(br, mep, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+ }
+
return 0;
}

@@ -780,6 +858,7 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
struct netlink_ext_ack *extack)
{
struct br_cfm_mep *mep;
+ int swd_ret;

ASSERT_RTNL();

@@ -792,11 +871,21 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,

if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) {
/* No change in tx_info. */
+
if (mep->cc_ccm_tx_info.period == 0)
/* Transmission is not enabled - just return */
return 0;

- /* Transmission is ongoing, the end time is recalculated */
+ /* Transmission is ongoing */
+
+ if (mep->ccm_tx_swd) {
+ /* Switchdev transmission started. Re-start transmission */
+ swd_ret = swd_ccm_tx(br, mep, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+ }
+
+ /* The period end time is recalculated */
mep->ccm_tx_end = jiffies +
usecs_to_jiffies(tx_info->period * 1000000);
return 0;
@@ -807,9 +896,16 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
goto save;

if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) {
- /* Some change in info and transmission is ongoing
- * The end time is recalculated
- */
+ /* Some change in info and transmission is ongoing */
+
+ if (mep->ccm_tx_swd) {
+ /* Switchdev transmission started. Re-start transmission */
+ swd_ret = swd_ccm_tx(br, mep, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+ }
+
+ /* The period end time is recalculated */
mep->ccm_tx_end = jiffies +
usecs_to_jiffies(tx_info->period * 1000000);

@@ -817,12 +913,31 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
}

if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) {
+ /* Stop transmission */
+
+ /* Try stop transmission in Switchdev */
+ (void)br_cfm_switchdev_cc_ccm_tx(br, mep, NULL, 0, 0, extack);
+
cancel_delayed_work_sync(&mep->ccm_tx_dwork);
goto save;
}

- /* Start delayed work to transmit CCM frames. It is done with zero delay
- * to send first frame immediately
+ /* Try start transmitting using Switchdev */
+ swd_ret = swd_ccm_tx(br, mep, extack);
+ if (swd_ret && swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+ if (!swd_ret) {
+ /* Switchdev transmission started */
+ mep->ccm_tx_swd = true;
+ goto save;
+ }
+
+ /* Switchdev CCM tx is not supported */
+ swd_ret = 0;
+ mep->ccm_tx_swd = false;
+
+ /* Start delayed work to transmit CCM frames. It is done with zero
+ * delay to send first frame immediately.
*/
mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000);
queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 0);
@@ -830,6 +945,78 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
save:
mep->cc_ccm_tx_info = *tx_info;

+ return swd_ret;
+}
+
+int br_cfm_mep_status_get(struct net_bridge *br, const u32 instance,
+ bool clear, struct br_cfm_mep_status *const status,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_mep *mep;
+ int swd_ret;
+
+ memset(status, 0, sizeof(*status));
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance does not exists");
+ return -ENOENT;
+ }
+
+ /* Try get MEP status in Switchdev */
+ swd_ret = br_cfm_switchdev_mep_status_get(br, mep, clear, status, extack);
+ if (swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
+ *status = mep->status;
+ if (clear) {
+ mep->status.opcode_unexp_seen = false;
+ mep->status.version_unexp_seen = false;
+ mep->status.rx_level_low_seen = false;
+ }
+
+ return 0;
+}
+
+int br_cfm_cc_peer_status_get(struct net_bridge *br, const u32 instance,
+ u32 mepid, bool clear,
+ struct br_cfm_cc_peer_status *const status,
+ struct netlink_ext_ack *extack)
+{
+ struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+ int swd_ret;
+
+ memset(status, 0, sizeof(*status));
+
+ mep = br_mep_find(br, instance);
+ if (!mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "MEP instance does not exists");
+ return -ENOENT;
+ }
+
+ peer_mep = br_peer_mep_find(mep, mepid);
+ if (!peer_mep) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Peer MEP-ID does not exists");
+ return -ENOENT;
+ }
+
+ /* Try get Peer MEP status in Switchdev */
+ swd_ret = br_cfm_switchdev_cc_peer_status_get(br, mep, mepid, clear,
+ status, extack);
+ if (swd_ret != -EOPNOTSUPP)
+ return swd_ret;
+
+ *status = peer_mep->cc_status;
+ if (clear) {
+ peer_mep->cc_status.seen = false;
+ peer_mep->cc_status.tlv_seen = false;
+ peer_mep->cc_status.seq_unexp_seen = false;
+ }
+
return 0;
}

@@ -878,5 +1065,28 @@ void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port)

hlist_for_each_entry(mep, &br->mep_list, head)
if (mep->create.ifindex == port->dev->ifindex)
- mep_delete_implementation(br, mep);
+ (void)mep_delete_implementation(br, mep, NULL);
+}
+
+/* Notification function called from CFM driver */
+void br_cfm_notification(struct net_device *dev, const struct br_cfm_notif_info *const notif_info)
+{
+ struct net_bridge *br = netdev_priv(dev);
+ struct br_cfm_peer_mep *peer_mep;
+ struct net_bridge_port *b_port;
+ struct br_cfm_mep *mep;
+
+ mep = br_mep_find(br, notif_info->instance);
+ if (!mep)
+ return;
+
+ peer_mep = br_peer_mep_find(mep, notif_info->peer_mep);
+ if (!peer_mep)
+ return;
+
+ rcu_read_lock();
+ b_port = rcu_dereference(mep->b_port);
+ if (b_port)
+ br_cfm_notify(RTM_NEWLINK, b_port);
+ rcu_read_unlock();
}
diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c
index 5f81262c9caa..3d8561e59ace 100644
--- a/net/bridge/br_cfm_netlink.c
+++ b/net/bridge/br_cfm_netlink.c
@@ -622,42 +622,40 @@ int br_cfm_status_fill_info(struct sk_buff *skb,
struct net_bridge *br,
bool getlink)
{
- struct nlattr *tb;
- struct br_cfm_mep *mep;
+ struct br_cfm_cc_peer_status cc_peer_status;
+ struct br_cfm_mep_status mep_status;
struct br_cfm_peer_mep *peer_mep;
+ struct br_cfm_mep *mep;
+ struct nlattr *tb;

hlist_for_each_entry_rcu(mep, &br->mep_list, head) {
tb = nla_nest_start(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INFO);
if (!tb)
goto nla_info_failure;

+ if (br_cfm_mep_status_get(br, mep->instance, getlink,
+ &mep_status, NULL))
+ goto nla_info_failure;
+
if (nla_put_u32(skb, IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE,
mep->instance))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN,
- mep->status.opcode_unexp_seen))
+ mep_status.opcode_unexp_seen))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN,
- mep->status.version_unexp_seen))
+ mep_status.version_unexp_seen))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN,
- mep->status.rx_level_low_seen))
+ mep_status.rx_level_low_seen))
goto nla_put_failure;

- /* Only clear if this is a GETLINK */
- if (getlink) {
- /* Clear all 'seen' indications */
- mep->status.opcode_unexp_seen = false;
- mep->status.version_unexp_seen = false;
- mep->status.rx_level_low_seen = false;
- }
-
nla_nest_end(skb, tb);

hlist_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head) {
@@ -666,6 +664,12 @@ int br_cfm_status_fill_info(struct sk_buff *skb,
if (!tb)
goto nla_info_failure;

+ if (br_cfm_cc_peer_status_get(br, mep->instance,
+ peer_mep->mepid,
+ getlink,
+ &cc_peer_status, NULL))
+ goto nla_info_failure;
+
if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE,
mep->instance))
@@ -678,45 +682,38 @@ int br_cfm_status_fill_info(struct sk_buff *skb,

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT,
- peer_mep->cc_status.ccm_defect))
+ cc_peer_status.ccm_defect))
goto nla_put_failure;

if (nla_put_u32(skb, IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI,
- peer_mep->cc_status.rdi))
+ cc_peer_status.rdi))
goto nla_put_failure;

if (nla_put_u8(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE,
- peer_mep->cc_status.port_tlv_value))
+ cc_peer_status.port_tlv_value))
goto nla_put_failure;

if (nla_put_u8(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE,
- peer_mep->cc_status.if_tlv_value))
+ cc_peer_status.if_tlv_value))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN,
- peer_mep->cc_status.seen))
+ cc_peer_status.seen))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN,
- peer_mep->cc_status.tlv_seen))
+ cc_peer_status.tlv_seen))
goto nla_put_failure;

if (nla_put_u32(skb,
IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN,
- peer_mep->cc_status.seq_unexp_seen))
+ cc_peer_status.seq_unexp_seen))
goto nla_put_failure;

- if (getlink) { /* Only clear if this is a GETLINK */
- /* Clear all 'seen' indications */
- peer_mep->cc_status.seen = false;
- peer_mep->cc_status.tlv_seen = false;
- peer_mep->cc_status.seq_unexp_seen = false;
- }
-
nla_nest_end(skb, tb);
}
}
diff --git a/net/bridge/br_cfm_switchdev.c b/net/bridge/br_cfm_switchdev.c
new file mode 100644
index 000000000000..d7441d57d113
--- /dev/null
+++ b/net/bridge/br_cfm_switchdev.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <net/switchdev.h>
+
+#include "br_private_cfm.h"
+
+int br_cfm_switchdev_mep_create(struct net_bridge *br,
+ const u32 instance,
+ struct br_cfm_mep_create *const create,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_mep cfm_obj;
+ struct net_bridge_port *port;
+ bool found = false;
+
+ list_for_each_entry(port, &br->port_list, list)
+ if (port->dev->ifindex == create->ifindex) {
+ found = true;
+ break;
+ }
+ if (!found)
+ return -EINVAL;
+
+ cfm_obj.obj.orig_dev = br->dev;
+ cfm_obj.obj.id = SWITCHDEV_OBJ_ID_MEP_CFM;
+ cfm_obj.obj.flags = 0;
+ cfm_obj.domain = create->domain;
+ cfm_obj.direction = create->direction;
+ cfm_obj.port = port->dev;
+
+ return switchdev_port_obj_add(br->dev, &cfm_obj.obj, extack);
+}
+
+int br_cfm_switchdev_mep_delete(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_mep cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_MEP_CFM,
+ .obj.flags = 0,
+ .domain = 0,
+ .direction = 0,
+ .port = NULL,
+ };
+
+ return switchdev_port_obj_del(br->dev, &cfm_obj.obj);
+}
+
+int br_cfm_switchdev_mep_config_set(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ const struct br_cfm_mep_config *const config,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_mep_config_set cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_MEP_CONFIG_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .unicast_mac = config->unicast_mac,
+ .mdlevel = config->mdlevel,
+ .mepid = config->mepid,
+ };
+
+ return switchdev_port_obj_add(br->dev, &cfm_obj.obj, extack);
+}
+
+int br_cfm_switchdev_cc_config_set(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ const struct br_cfm_cc_config *const config,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_cc_config_set cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_CC_CONFIG_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .interval = config->exp_interval,
+ .maid = config->exp_maid,
+ .enable = config->enable,
+ };
+
+ return switchdev_port_obj_add(br->dev, &cfm_obj.obj, extack);
+}
+
+int br_cfm_switchdev_cc_peer_mep_add(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_cc_peer_mep cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_CC_PEER_MEP_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .peer_mep_id = peer_mep_id,
+ };
+
+ return switchdev_port_obj_add(br->dev, &cfm_obj.obj, extack);
+}
+
+int br_cfm_switchdev_cc_peer_mep_remove(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_cc_peer_mep cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_CC_PEER_MEP_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .peer_mep_id = peer_mep_id,
+ };
+
+ return switchdev_port_obj_del(br->dev, &cfm_obj.obj);
+}
+
+int br_cfm_switchdev_cc_ccm_tx(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ struct sk_buff *skb,
+ u32 period,
+ enum br_cfm_ccm_interval interval,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_cc_ccm_tx cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_CC_CCM_TX_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .skb = skb,
+ .interval = interval,
+ .period = period,
+ };
+
+ return switchdev_port_obj_add(br->dev, &cfm_obj.obj, extack);
+}
+
+int br_cfm_switchdev_mep_status_get(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ bool clear,
+ struct br_cfm_mep_status *const status,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_mep_status_get cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_MEP_STATUS_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .clear = clear,
+ .opcode_unexp_seen = false,
+ .version_unexp_seen = false,
+ .rx_level_low_seen = false,
+ };
+ int err;
+
+ err = switchdev_port_obj_get(br->dev, &cfm_obj.obj, extack);
+ if (err)
+ return err;
+
+ status->opcode_unexp_seen = cfm_obj.opcode_unexp_seen;
+ status->version_unexp_seen = cfm_obj.version_unexp_seen;
+ status->rx_level_low_seen = cfm_obj.rx_level_low_seen;
+
+ return 0;
+}
+
+int br_cfm_switchdev_cc_peer_status_get(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ bool clear,
+ struct br_cfm_cc_peer_status *const status,
+ struct netlink_ext_ack *extack)
+{
+ struct switchdev_obj_cfm_cc_peer_status_get cfm_obj = {
+ .obj.orig_dev = br->dev,
+ .obj.id = SWITCHDEV_OBJ_ID_PEER_MEP_STATUS_CFM,
+ .obj.flags = 0,
+ .instance = mep->instance,
+ .clear = clear,
+ .port_tlv_value = 0,
+ .if_tlv_value = 0,
+ .ccm_defect = false,
+ .rdi = false,
+ .seen = false,
+ .tlv_seen = false,
+ .seq_unexp_seen = false,
+ };
+ int err;
+
+ err = switchdev_port_obj_get(br->dev, &cfm_obj.obj, extack);
+ if (err)
+ return err;
+
+ status->port_tlv_value = cfm_obj.port_tlv_value;
+ status->if_tlv_value = cfm_obj.if_tlv_value;
+ status->ccm_defect = cfm_obj.ccm_defect;
+ status->rdi = cfm_obj.rdi;
+ status->seen = cfm_obj.seen;
+ status->tlv_seen = cfm_obj.tlv_seen;
+ status->seq_unexp_seen = cfm_obj.seq_unexp_seen;
+
+ return 0;
+}
diff --git a/net/bridge/br_private_cfm.h b/net/bridge/br_private_cfm.h
index 6a2294c0ea79..a91d0b59c27f 100644
--- a/net/bridge/br_private_cfm.h
+++ b/net/bridge/br_private_cfm.h
@@ -6,6 +6,7 @@
#include "br_private.h"
#include <uapi/linux/cfm_bridge.h>

+/* br_cfm.c */
struct br_cfm_mep_create {
enum br_cfm_domain domain; /* Domain for this MEP */
enum br_cfm_mep_direction direction; /* Up or Down MEP direction */
@@ -55,13 +56,13 @@ int br_cfm_cc_config_set(struct net_bridge *br,
int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
u32 peer_mep_id,
struct netlink_ext_ack *extack);
+
int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
u32 peer_mep_id,
struct netlink_ext_ack *extack);

/* Transmitted CCM Remote Defect Indication status set.
* This RDI is inserted in transmitted CCM PDUs if CCM transmission is enabled.
- * See br_cfm_cc_ccm_tx() with interval != BR_CFM_CCM_INTERVAL_NONE
*/
int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance,
const bool rdi, struct netlink_ext_ack *extack);
@@ -96,6 +97,10 @@ struct br_cfm_mep_status {
bool rx_level_low_seen; /* Rx of OAM PDU with level low */
};

+int br_cfm_mep_status_get(struct net_bridge *br, const u32 instance,
+ bool clear, struct br_cfm_mep_status *const status,
+ struct netlink_ext_ack *extack);
+
struct br_cfm_cc_peer_status {
/* This CCM related status is based on the latest received CCM PDU. */
u8 port_tlv_value; /* Port Status TLV value */
@@ -114,6 +119,11 @@ struct br_cfm_cc_peer_status {
bool seq_unexp_seen;
};

+int br_cfm_cc_peer_status_get(struct net_bridge *br, const u32 instance,
+ u32 mepid, bool clear,
+ struct br_cfm_cc_peer_status *const status,
+ struct netlink_ext_ack *extack);
+
struct br_cfm_mep {
/* list header of MEP instances */
struct hlist_node head;
@@ -131,6 +141,7 @@ struct br_cfm_mep {
u32 ccm_rx_snumber;
struct br_cfm_mep_status status;
bool rdi;
+ bool ccm_tx_swd;
struct rcu_head rcu;
};

@@ -144,4 +155,54 @@ struct br_cfm_peer_mep {
struct rcu_head rcu;
};

+/* br_cfm_switchdev.c */
+int br_cfm_switchdev_mep_create(struct net_bridge *br,
+ const u32 instance,
+ struct br_cfm_mep_create *const create,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_mep_delete(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_mep_config_set(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ const struct br_cfm_mep_config *const config,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_cc_config_set(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ const struct br_cfm_cc_config *const config,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_cc_peer_mep_add(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_cc_peer_mep_remove(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_cc_ccm_tx(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ struct sk_buff *skb,
+ u32 period,
+ enum br_cfm_ccm_interval interval,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_mep_status_get(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ bool clear,
+ struct br_cfm_mep_status *const status,
+ struct netlink_ext_ack *extack);
+
+int br_cfm_switchdev_cc_peer_status_get(struct net_bridge *br,
+ struct br_cfm_mep *mep,
+ u32 peer_mep_id,
+ bool clear,
+ struct br_cfm_cc_peer_status *const status,
+ struct netlink_ext_ack *extack);
+
#endif /* _BR_PRIVATE_CFM_H_ */
--
2.28.0