return 0;
 }
 
+/* Convert the CCM PDU value to interval on interface. */
+static u32 pdu_to_interval(u32 value)
+{
+       switch (value) {
+       case 0:
+               return BR_CFM_CCM_INTERVAL_NONE;
+       case 1:
+               return BR_CFM_CCM_INTERVAL_3_3_MS;
+       case 2:
+               return BR_CFM_CCM_INTERVAL_10_MS;
+       case 3:
+               return BR_CFM_CCM_INTERVAL_100_MS;
+       case 4:
+               return BR_CFM_CCM_INTERVAL_1_SEC;
+       case 5:
+               return BR_CFM_CCM_INTERVAL_10_SEC;
+       case 6:
+               return BR_CFM_CCM_INTERVAL_1_MIN;
+       case 7:
+               return BR_CFM_CCM_INTERVAL_10_MIN;
+       }
+       return BR_CFM_CCM_INTERVAL_NONE;
+}
+
+static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep)
+{
+       u32 interval_us;
+
+       interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval);
+       /* Function ccm_rx_dwork must be called with 1/4
+        * of the configured CC 'expected_interval'
+        * in order to detect CCM defect after 3.25 interval.
+        */
+       queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork,
+                          usecs_to_jiffies(interval_us / 4));
+}
+
+static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep)
+{
+       memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status));
+       peer_mep->ccm_rx_count_miss = 0;
+
+       ccm_rx_timer_start(peer_mep);
+}
+
+static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep)
+{
+       cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
+}
+
 static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep,
                                       const struct br_cfm_cc_ccm_tx_info *const tx_info)
 
                           usecs_to_jiffies(interval_us));
 }
 
+/* This function is called with 1/4 of the configured CC 'expected_interval'
+ * in order to detect CCM defect after 3.25 interval.
+ */
+static void ccm_rx_work_expired(struct work_struct *work)
+{
+       struct br_cfm_peer_mep *peer_mep;
+       struct delayed_work *del_work;
+
+       del_work = to_delayed_work(work);
+       peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork);
+
+       /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */
+       if (peer_mep->ccm_rx_count_miss < 13) {
+               /* 3.25 intervals are NOT expired without CCM reception */
+               peer_mep->ccm_rx_count_miss++;
+
+               /* Start timer again */
+               ccm_rx_timer_start(peer_mep);
+       } else {
+               /* 3.25 intervals are expired without CCM reception.
+                * CCM defect detected
+                */
+               peer_mep->cc_status.ccm_defect = true;
+       }
+}
+
+static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index,
+                          struct br_cfm_peer_mep *peer_mep)
+{
+       __be32 *s_tlv;
+       __be32 _s_tlv;
+       u32 h_s_tlv;
+       u8 *e_tlv;
+       u8 _e_tlv;
+
+       e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv);
+       if (!e_tlv)
+               return 0;
+
+       /* TLV is present - get the status TLV */
+       s_tlv = skb_header_pointer(skb,
+                                  index,
+                                  sizeof(_s_tlv), &_s_tlv);
+       if (!s_tlv)
+               return 0;
+
+       h_s_tlv = ntohl(*s_tlv);
+       if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) {
+               /* Interface status TLV */
+               peer_mep->cc_status.tlv_seen = true;
+               peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF);
+       }
+
+       if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) {
+               /* Port status TLV */
+               peer_mep->cc_status.tlv_seen = true;
+               peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF);
+       }
+
+       /* The Sender ID TLV is not handled */
+       /* The Organization-Specific TLV is not handled */
+
+       /* Return the length of this tlv.
+        * This is the length of the value field plus 3 bytes for size of type
+        * field and length field
+        */
+       return ((h_s_tlv >> 8) & 0xFFFF) + 3;
+}
+
+/* note: already called with rcu_read_lock */
+static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb)
+{
+       u32 mdlevel, interval, size, index, max;
+       const struct br_cfm_common_hdr *hdr;
+       struct br_cfm_peer_mep *peer_mep;
+       const struct br_cfm_maid *maid;
+       struct br_cfm_common_hdr _hdr;
+       struct br_cfm_maid _maid;
+       struct br_cfm_mep *mep;
+       struct net_bridge *br;
+       __be32 *snumber;
+       __be32 _snumber;
+       __be16 *mepid;
+       __be16 _mepid;
+
+       if (port->state == BR_STATE_DISABLED)
+               return 0;
+
+       hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr);
+       if (!hdr)
+               return 1;
+
+       br = port->br;
+       mep = br_mep_find_ifindex(br, port->dev->ifindex);
+       if (unlikely(!mep))
+               /* No MEP on this port - must be forwarded */
+               return 0;
+
+       mdlevel = hdr->mdlevel_version >> 5;
+       if (mdlevel > mep->config.mdlevel)
+               /* The level is above this MEP level - must be forwarded */
+               return 0;
+
+       if ((hdr->mdlevel_version & 0x1F) != 0) {
+               /* Invalid version */
+               mep->status.version_unexp_seen = true;
+               return 1;
+       }
+
+       if (mdlevel < mep->config.mdlevel) {
+               /* The level is below this MEP level */
+               mep->status.rx_level_low_seen = true;
+               return 1;
+       }
+
+       if (hdr->opcode == BR_CFM_OPCODE_CCM) {
+               /* CCM PDU received. */
+               /* MA ID is after common header + sequence number + MEP ID */
+               maid = skb_header_pointer(skb,
+                                         CFM_CCM_PDU_MAID_OFFSET,
+                                         sizeof(_maid), &_maid);
+               if (!maid)
+                       return 1;
+               if (memcmp(maid->data, mep->cc_config.exp_maid.data,
+                          sizeof(maid->data)))
+                       /* MA ID not as expected */
+                       return 1;
+
+               /* MEP ID is after common header + sequence number */
+               mepid = skb_header_pointer(skb,
+                                          CFM_CCM_PDU_MEPID_OFFSET,
+                                          sizeof(_mepid), &_mepid);
+               if (!mepid)
+                       return 1;
+               peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid));
+               if (!peer_mep)
+                       return 1;
+
+               /* Interval is in common header flags */
+               interval = hdr->flags & 0x07;
+               if (mep->cc_config.exp_interval != pdu_to_interval(interval))
+                       /* Interval not as expected */
+                       return 1;
+
+               /* A valid CCM frame is received */
+               if (peer_mep->cc_status.ccm_defect) {
+                       peer_mep->cc_status.ccm_defect = false;
+
+                       /* Start CCM RX timer */
+                       ccm_rx_timer_start(peer_mep);
+               }
+
+               peer_mep->cc_status.seen = true;
+               peer_mep->ccm_rx_count_miss = 0;
+
+               /* RDI is in common header flags */
+               peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false;
+
+               /* Sequence number is after common header */
+               snumber = skb_header_pointer(skb,
+                                            CFM_CCM_PDU_SEQNR_OFFSET,
+                                            sizeof(_snumber), &_snumber);
+               if (!snumber)
+                       return 1;
+               if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1))
+                       /* Unexpected sequence number */
+                       peer_mep->cc_status.seq_unexp_seen = true;
+
+               mep->ccm_rx_snumber = ntohl(*snumber);
+
+               /* TLV end is after common header + sequence number + MEP ID +
+                * MA ID + ITU reserved
+                */
+               index = CFM_CCM_PDU_TLV_OFFSET;
+               max = 0;
+               do { /* Handle all TLVs */
+                       size = ccm_tlv_extract(skb, index, peer_mep);
+                       index += size;
+                       max += 1;
+               } while (size != 0 && max < 4); /* Max four TLVs possible */
+
+               return 1;
+       }
+
+       mep->status.opcode_unexp_seen = true;
+
+       return 1;
+}
+
+static struct br_frame_type cfm_frame_type __read_mostly = {
+       .type = cpu_to_be16(ETH_P_CFM),
+       .frame_handler = br_cfm_frame_rx,
+};
+
 int br_cfm_mep_create(struct net_bridge *br,
                      const u32 instance,
                      struct br_cfm_mep_create *const create,
        INIT_HLIST_HEAD(&mep->peer_mep_list);
        INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired);
 
+       if (hlist_empty(&br->mep_list))
+               br_add_frame(br, &cfm_frame_type);
+
        hlist_add_tail_rcu(&mep->head, &br->mep_list);
 
        return 0;
 
        /* Empty and free peer MEP list */
        hlist_for_each_entry_safe(peer_mep, n_store, &mep->peer_mep_list, head) {
+               cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork);
                hlist_del_rcu(&peer_mep->head);
                kfree_rcu(peer_mep, rcu);
        }
        RCU_INIT_POINTER(mep->b_port, NULL);
        hlist_del_rcu(&mep->head);
        kfree_rcu(mep, rcu);
+
+       if (hlist_empty(&br->mep_list))
+               br_del_frame(br, &cfm_frame_type);
 }
 
 int br_cfm_mep_delete(struct net_bridge *br,
                         const struct br_cfm_cc_config *const config,
                         struct netlink_ext_ack *extack)
 {
+       struct br_cfm_peer_mep *peer_mep;
        struct br_cfm_mep *mep;
 
        ASSERT_RTNL();
        if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0)
                return 0;
 
+       if (config->enable && !mep->cc_config.enable)
+               /* CC is enabled */
+               hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head)
+                       cc_peer_enable(peer_mep);
+
+       if (!config->enable && mep->cc_config.enable)
+               /* CC is disabled */
+               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;
 
        peer_mep->mepid = mepid;
        peer_mep->mep = mep;
+       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 -ENOENT;
        }
 
+       cc_peer_disable(peer_mep);
+
        hlist_del_rcu(&peer_mep->head);
        kfree_rcu(peer_mep, rcu);