mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Return statements are not needed in Void function. Signed-off-by: gushengxian <gushengxian@yulong.com> Signed-off-by: David S. Miller <davem@davemloft.net>
		
			
				
	
	
		
			867 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			867 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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;
 | 
						|
 | 
						|
	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();
 | 
						|
	/* 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.
 | 
						|
	 */
 | 
						|
 | 
						|
	skb->protocol = htons(ETH_P_CFM);
 | 
						|
	skb->priority = CFM_FRAME_PRIO;
 | 
						|
 | 
						|
	/* Ethernet header */
 | 
						|
	eth_hdr = skb_put(skb, sizeof(*eth_hdr));
 | 
						|
	ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr);
 | 
						|
	ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr);
 | 
						|
	eth_hdr->h_proto = htons(ETH_P_CFM);
 | 
						|
 | 
						|
	/* Common CFM Header */
 | 
						|
	common_hdr = skb_put(skb, sizeof(*common_hdr));
 | 
						|
	common_hdr->mdlevel_version = mep->config.mdlevel << 5;
 | 
						|
	common_hdr->opcode = BR_CFM_OPCODE_CCM;
 | 
						|
	common_hdr->flags = (mep->rdi << 7) |
 | 
						|
			    interval_to_pdu(mep->cc_config.exp_interval);
 | 
						|
	common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET;
 | 
						|
 | 
						|
	/* Sequence number */
 | 
						|
	snumber = skb_put(skb, sizeof(*snumber));
 | 
						|
	if (tx_info->seq_no_update) {
 | 
						|
		*snumber = cpu_to_be32(mep->ccm_tx_snumber);
 | 
						|
		mep->ccm_tx_snumber += 1;
 | 
						|
	} else {
 | 
						|
		*snumber = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	mepid = skb_put(skb, sizeof(*mepid));
 | 
						|
	*mepid = cpu_to_be16((u16)mep->config.mepid);
 | 
						|
 | 
						|
	maid = skb_put(skb, sizeof(*maid));
 | 
						|
	memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data));
 | 
						|
 | 
						|
	/* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */
 | 
						|
	itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE);
 | 
						|
	memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE);
 | 
						|
 | 
						|
	/* Generel CFM TLV format:
 | 
						|
	 * TLV type:		one byte
 | 
						|
	 * TLV value length:	two bytes
 | 
						|
	 * TLV value:		'TLV value length' bytes
 | 
						|
	 */
 | 
						|
 | 
						|
	/* Port status TLV. The value length is 1. Total of 4 bytes. */
 | 
						|
	if (tx_info->port_tlv) {
 | 
						|
		status_tlv = skb_put(skb, sizeof(*status_tlv));
 | 
						|
		*status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) |
 | 
						|
					  (1 << 8) |	/* Value length */
 | 
						|
					  (tx_info->port_tlv_value & 0xFF));
 | 
						|
	}
 | 
						|
 | 
						|
	/* Interface status TLV. The value length is 1. Total of 4 bytes. */
 | 
						|
	if (tx_info->if_tlv) {
 | 
						|
		status_tlv = skb_put(skb, sizeof(*status_tlv));
 | 
						|
		*status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) |
 | 
						|
					  (1 << 8) |	/* Value length */
 | 
						|
					  (tx_info->if_tlv_value & 0xFF));
 | 
						|
	}
 | 
						|
 | 
						|
	/* End TLV */
 | 
						|
	e_tlv = skb_put(skb, sizeof(*e_tlv));
 | 
						|
	*e_tlv = CFM_ENDE_TLV_TYPE;
 | 
						|
 | 
						|
	return skb;
 | 
						|
}
 | 
						|
 | 
						|
static void ccm_frame_tx(struct sk_buff *skb)
 | 
						|
{
 | 
						|
	skb_reset_network_header(skb);
 | 
						|
	dev_queue_xmit(skb);
 | 
						|
}
 | 
						|
 | 
						|
/* This function is called with the configured CC 'expected_interval'
 | 
						|
 * in order to drive CCM transmission when enabled.
 | 
						|
 */
 | 
						|
static void ccm_tx_work_expired(struct work_struct *work)
 | 
						|
{
 | 
						|
	struct delayed_work *del_work;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
	struct sk_buff *skb;
 | 
						|
	u32 interval_us;
 | 
						|
 | 
						|
	del_work = to_delayed_work(work);
 | 
						|
	mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork);
 | 
						|
 | 
						|
	if (time_before_eq(mep->ccm_tx_end, jiffies)) {
 | 
						|
		/* Transmission period has ended */
 | 
						|
		mep->cc_ccm_tx_info.period = 0;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info);
 | 
						|
	if (skb)
 | 
						|
		ccm_frame_tx(skb);
 | 
						|
 | 
						|
	interval_us = interval_to_us(mep->cc_config.exp_interval);
 | 
						|
	queue_delayed_work(system_wq, &mep->ccm_tx_dwork,
 | 
						|
			   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 net_bridge_port *b_port;
 | 
						|
	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;
 | 
						|
 | 
						|
		/* Change in CCM defect status - notify */
 | 
						|
		rcu_read_lock();
 | 
						|
		b_port = rcu_dereference(peer_mep->mep->b_port);
 | 
						|
		if (b_port)
 | 
						|
			br_cfm_notify(RTM_NEWLINK, b_port);
 | 
						|
		rcu_read_unlock();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
			/* Change in CCM defect status - notify */
 | 
						|
			br_cfm_notify(RTM_NEWLINK, port);
 | 
						|
 | 
						|
			/* 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,
 | 
						|
		      struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct net_bridge_port *p;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	if (create->domain == BR_CFM_VLAN) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "VLAN domain not supported");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (create->domain != BR_CFM_PORT) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "Invalid domain value");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (create->direction == BR_CFM_MEP_DIRECTION_UP) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "Up-MEP not supported");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	if (create->direction != BR_CFM_MEP_DIRECTION_DOWN) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "Invalid direction value");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	p = br_mep_get_port(br, create->ifindex);
 | 
						|
	if (!p) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "Port is not related to bridge");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance already exists");
 | 
						|
		return -EEXIST;
 | 
						|
	}
 | 
						|
 | 
						|
	/* In PORT domain only one instance can be created per port */
 | 
						|
	if (create->domain == BR_CFM_PORT) {
 | 
						|
		mep = br_mep_find_ifindex(br, create->ifindex);
 | 
						|
		if (mep) {
 | 
						|
			NL_SET_ERR_MSG_MOD(extack,
 | 
						|
					   "Only one Port MEP on a port allowed");
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	mep = kzalloc(sizeof(*mep), GFP_KERNEL);
 | 
						|
	if (!mep)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	mep->create = *create;
 | 
						|
	mep->instance = instance;
 | 
						|
	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))
 | 
						|
		br_add_frame(br, &cfm_frame_type);
 | 
						|
 | 
						|
	hlist_add_tail_rcu(&mep->head, &br->mep_list);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void mep_delete_implementation(struct net_bridge *br,
 | 
						|
				      struct br_cfm_mep *mep)
 | 
						|
{
 | 
						|
	struct br_cfm_peer_mep *peer_mep;
 | 
						|
	struct hlist_node *n_store;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	/* 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);
 | 
						|
	}
 | 
						|
 | 
						|
	cancel_delayed_work_sync(&mep->ccm_tx_dwork);
 | 
						|
 | 
						|
	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 u32 instance,
 | 
						|
		      struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (!mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance does not exists");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	mep_delete_implementation(br, mep);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_mep_config_set(struct net_bridge *br,
 | 
						|
			  const u32 instance,
 | 
						|
			  const struct br_cfm_mep_config *const config,
 | 
						|
			  struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (!mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance does not exists");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	mep->config = *config;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_cc_config_set(struct net_bridge *br,
 | 
						|
			 const u32 instance,
 | 
						|
			 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();
 | 
						|
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (!mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance does not exists");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Check for no change in configuration */
 | 
						|
	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;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance,
 | 
						|
			   u32 mepid,
 | 
						|
			   struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct br_cfm_peer_mep *peer_mep;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	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 already exists");
 | 
						|
		return -EEXIST;
 | 
						|
	}
 | 
						|
 | 
						|
	peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL);
 | 
						|
	if (!peer_mep)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	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 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
 | 
						|
			      u32 mepid,
 | 
						|
			      struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct br_cfm_peer_mep *peer_mep;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	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;
 | 
						|
	}
 | 
						|
 | 
						|
	cc_peer_disable(peer_mep);
 | 
						|
 | 
						|
	hlist_del_rcu(&peer_mep->head);
 | 
						|
	kfree_rcu(peer_mep, rcu);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (!mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance does not exists");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	mep->rdi = rdi;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance,
 | 
						|
		     const struct br_cfm_cc_ccm_tx_info *const tx_info,
 | 
						|
		     struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	mep = br_mep_find(br, instance);
 | 
						|
	if (!mep) {
 | 
						|
		NL_SET_ERR_MSG_MOD(extack,
 | 
						|
				   "MEP instance does not exists");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	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 */
 | 
						|
		mep->ccm_tx_end = jiffies +
 | 
						|
				  usecs_to_jiffies(tx_info->period * 1000000);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0)
 | 
						|
		/* Some change in info and transmission is not ongoing */
 | 
						|
		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
 | 
						|
		 */
 | 
						|
		mep->ccm_tx_end = jiffies +
 | 
						|
				  usecs_to_jiffies(tx_info->period * 1000000);
 | 
						|
 | 
						|
		goto save;
 | 
						|
	}
 | 
						|
 | 
						|
	if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) {
 | 
						|
		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
 | 
						|
	 */
 | 
						|
	mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000);
 | 
						|
	queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 0);
 | 
						|
 | 
						|
save:
 | 
						|
	mep->cc_ccm_tx_info = *tx_info;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_mep_count(struct net_bridge *br, u32 *count)
 | 
						|
{
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	*count = 0;
 | 
						|
 | 
						|
	rcu_read_lock();
 | 
						|
	hlist_for_each_entry_rcu(mep, &br->mep_list, head)
 | 
						|
		*count += 1;
 | 
						|
	rcu_read_unlock();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int br_cfm_peer_mep_count(struct net_bridge *br, u32 *count)
 | 
						|
{
 | 
						|
	struct br_cfm_peer_mep *peer_mep;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	*count = 0;
 | 
						|
 | 
						|
	rcu_read_lock();
 | 
						|
	hlist_for_each_entry_rcu(mep, &br->mep_list, head)
 | 
						|
		hlist_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head)
 | 
						|
			*count += 1;
 | 
						|
	rcu_read_unlock();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
bool br_cfm_created(struct net_bridge *br)
 | 
						|
{
 | 
						|
	return !hlist_empty(&br->mep_list);
 | 
						|
}
 | 
						|
 | 
						|
/* Deletes the CFM instances on a specific bridge port
 | 
						|
 */
 | 
						|
void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port)
 | 
						|
{
 | 
						|
	struct hlist_node *n_store;
 | 
						|
	struct br_cfm_mep *mep;
 | 
						|
 | 
						|
	ASSERT_RTNL();
 | 
						|
 | 
						|
	hlist_for_each_entry_safe(mep, n_store, &br->mep_list, head)
 | 
						|
		if (mep->create.ifindex == port->dev->ifindex)
 | 
						|
			mep_delete_implementation(br, mep);
 | 
						|
}
 |