| // 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); | 
 | } |