// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/cfm_bridge.h>
#include <uapi/linux/cfm_bridge.h>
#include "br_private_cfm.h"
static struct br_cfm_mep *br_mep_find(struct net_bridge *br, u32 instance)
{
struct br_cfm_mep *mep;
hlist_for_each_entry(mep, &br->mep_list, head)
if (mep->instance == instance)
return mep;
return NULL;
}
static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br,
u32 ifindex)
{
struct br_cfm_mep *mep;
hlist_for_each_entry_rcu(mep, &br->mep_list, head,
lockdep_rtnl_is_held())
if (mep->create.ifindex == ifindex)
return mep;
return NULL;
}
static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep,
u32 mepid)
{
struct br_cfm_peer_mep *peer_mep;
hlist_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head,
lockdep_rtnl_is_held())
if (peer_mep->mepid == mepid)
return peer_mep;
return NULL;
}
static struct net_bridge_port *br_mep_get_port(struct net_bridge *br,
u32 ifindex)
{
struct net_bridge_port *port;
list_for_each_entry(port, &br->port_list, list)
if (port->dev->ifindex == ifindex)
return port;
return NULL;
}
/* Calculate the CCM interval in us. */
static u32 interval_to_us(enum br_cfm_ccm_interval interval)
{
switch (interval) {
case BR_CFM_CCM_INTERVAL_NONE:
return 0;
case BR_CFM_CCM_INTERVAL_3_3_MS:
return 3300;
case BR_CFM_CCM_INTERVAL_10_MS:
return 10 * 1000;
case BR_CFM_CCM_INTERVAL_100_MS:
return 100 * 1000;
case BR_CFM_CCM_INTERVAL_1_SEC:
return 1000 * 1000;
case BR_CFM_CCM_INTERVAL_10_SEC:
return 10 * 1000 * 1000;
case BR_CFM_CCM_INTERVAL_1_MIN:
return 60 * 1000 * 1000;
case BR_CFM_CCM_INTERVAL_10_MIN:
return 10 * 60 * 1000 * 1000;
}
return 0;
}
/* Convert the interface interval to CCM PDU value. */
static u32 interval_to_pdu(enum br_cfm_ccm_interval interval)
{
switch (interval) {
case BR_CFM_CCM_INTERVAL_NONE:
return 0;
case BR_CFM_CCM_INTERVAL_3_3_MS:
return 1;
case BR_CFM_CCM_INTERVAL_10_MS:
return 2;
case BR_CFM_CCM_INTERVAL_100_MS:
return 3;
case BR_CFM_CCM_INTERVAL_1_SEC:
return 4;
case BR_CFM_CCM_INTERVAL_10_SEC:
return 5;
case BR_CFM_CCM_INTERVAL_1_MIN:
return 6;
case BR_CFM_CCM_INTERVAL_10_MIN:
return 7;
}
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 br_cfm_notify(int event, const struct net_bridge_port *port)
{
u32 filter = RTEXT_FILTER_CFM_STATUS;
return br_info_notify(event, port->br, NULL, filter);
}
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)
{
struct br_cfm_common_hdr *common_hdr;
struct net_bridge_port *b_port;
struct br_cfm_maid *maid;
u8 *itu_reserved, *e_tlv;
struct ethhdr *eth_hdr;
struct sk_buff *skb;
__be32 *status_tlv;
__be32 *snumber;
__be16 *mepid;
skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH);
if (!skb)
return NULL;
rcu_read_lock();
b_port = rcu_dereference(mep->b_port);
if (!b_port) {
kfree_skb(skb);
rcu_read_unlock();
return NULL;
}
skb->dev = b_port->dev;
rcu_read_unlock();