Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bridge: cfm: Kernel space implementation of CFM. CCM frame TX added.

This is the second commit of the implementation of the CFM protocol
according to 802.1Q section 12.14.

Functionality is extended with CCM frame transmission.

Interface is extended with these functions:
br_cfm_cc_rdi_set()
br_cfm_cc_ccm_tx()
br_cfm_cc_config_set()

A MEP Continuity Check feature can be configured by
br_cfm_cc_config_set()
The Continuity Check parameters can be configured to be used when
transmitting CCM.

A MEP can be configured to start or stop transmission of CCM frames by
br_cfm_cc_ccm_tx()
The CCM will be transmitted for a selected period in seconds.
Must call this function before timeout to keep transmission alive.

A MEP transmitting CCM can be configured with inserted RDI in PDU by
br_cfm_cc_rdi_set()

Signed-off-by: Henrik Bjoernlund <henrik.bjoernlund@microchip.com>
Reviewed-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Acked-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Henrik Bjoernlund and committed by
Jakub Kicinski
a806ad8e 86a14b79

+377 -1
+38 -1
include/uapi/linux/cfm_bridge.h
··· 6 6 #include <linux/types.h> 7 7 #include <linux/if_ether.h> 8 8 9 - #define CFM_MAID_LENGTH 48 9 + #define ETHER_HEADER_LENGTH (6+6+4+2) 10 + #define CFM_MAID_LENGTH 48 11 + #define CFM_CCM_PDU_LENGTH 75 12 + #define CFM_PORT_STATUS_TLV_LENGTH 4 13 + #define CFM_IF_STATUS_TLV_LENGTH 4 14 + #define CFM_IF_STATUS_TLV_TYPE 4 15 + #define CFM_PORT_STATUS_TLV_TYPE 2 16 + #define CFM_ENDE_TLV_TYPE 0 17 + #define CFM_CCM_MAX_FRAME_LENGTH (ETHER_HEADER_LENGTH+\ 18 + CFM_CCM_PDU_LENGTH+\ 19 + CFM_PORT_STATUS_TLV_LENGTH+\ 20 + CFM_IF_STATUS_TLV_LENGTH) 21 + #define CFM_FRAME_PRIO 7 22 + #define CFM_CCM_TLV_OFFSET 70 23 + #define CFM_CCM_ITU_RESERVED_SIZE 16 24 + 25 + struct br_cfm_common_hdr { 26 + __u8 mdlevel_version; 27 + __u8 opcode; 28 + __u8 flags; 29 + __u8 tlv_offset; 30 + }; 31 + 32 + enum br_cfm_opcodes { 33 + BR_CFM_OPCODE_CCM = 0x1, 34 + }; 10 35 11 36 /* MEP domain */ 12 37 enum br_cfm_domain { ··· 43 18 enum br_cfm_mep_direction { 44 19 BR_CFM_MEP_DIRECTION_DOWN, 45 20 BR_CFM_MEP_DIRECTION_UP, 21 + }; 22 + 23 + /* CCM interval supported. */ 24 + enum br_cfm_ccm_interval { 25 + BR_CFM_CCM_INTERVAL_NONE, 26 + BR_CFM_CCM_INTERVAL_3_3_MS, 27 + BR_CFM_CCM_INTERVAL_10_MS, 28 + BR_CFM_CCM_INTERVAL_100_MS, 29 + BR_CFM_CCM_INTERVAL_1_SEC, 30 + BR_CFM_CCM_INTERVAL_10_SEC, 31 + BR_CFM_CCM_INTERVAL_1_MIN, 32 + BR_CFM_CCM_INTERVAL_10_MIN, 46 33 }; 47 34 48 35 #endif
+285
net/bridge/br_cfm.c
··· 53 53 return NULL; 54 54 } 55 55 56 + /* Calculate the CCM interval in us. */ 57 + static u32 interval_to_us(enum br_cfm_ccm_interval interval) 58 + { 59 + switch (interval) { 60 + case BR_CFM_CCM_INTERVAL_NONE: 61 + return 0; 62 + case BR_CFM_CCM_INTERVAL_3_3_MS: 63 + return 3300; 64 + case BR_CFM_CCM_INTERVAL_10_MS: 65 + return 10 * 1000; 66 + case BR_CFM_CCM_INTERVAL_100_MS: 67 + return 100 * 1000; 68 + case BR_CFM_CCM_INTERVAL_1_SEC: 69 + return 1000 * 1000; 70 + case BR_CFM_CCM_INTERVAL_10_SEC: 71 + return 10 * 1000 * 1000; 72 + case BR_CFM_CCM_INTERVAL_1_MIN: 73 + return 60 * 1000 * 1000; 74 + case BR_CFM_CCM_INTERVAL_10_MIN: 75 + return 10 * 60 * 1000 * 1000; 76 + } 77 + return 0; 78 + } 79 + 80 + /* Convert the interface interval to CCM PDU value. */ 81 + static u32 interval_to_pdu(enum br_cfm_ccm_interval interval) 82 + { 83 + switch (interval) { 84 + case BR_CFM_CCM_INTERVAL_NONE: 85 + return 0; 86 + case BR_CFM_CCM_INTERVAL_3_3_MS: 87 + return 1; 88 + case BR_CFM_CCM_INTERVAL_10_MS: 89 + return 2; 90 + case BR_CFM_CCM_INTERVAL_100_MS: 91 + return 3; 92 + case BR_CFM_CCM_INTERVAL_1_SEC: 93 + return 4; 94 + case BR_CFM_CCM_INTERVAL_10_SEC: 95 + return 5; 96 + case BR_CFM_CCM_INTERVAL_1_MIN: 97 + return 6; 98 + case BR_CFM_CCM_INTERVAL_10_MIN: 99 + return 7; 100 + } 101 + return 0; 102 + } 103 + 104 + static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep, 105 + const struct br_cfm_cc_ccm_tx_info *const tx_info) 106 + 107 + { 108 + struct br_cfm_common_hdr *common_hdr; 109 + struct net_bridge_port *b_port; 110 + struct br_cfm_maid *maid; 111 + u8 *itu_reserved, *e_tlv; 112 + struct ethhdr *eth_hdr; 113 + struct sk_buff *skb; 114 + __be32 *status_tlv; 115 + __be32 *snumber; 116 + __be16 *mepid; 117 + 118 + skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH); 119 + if (!skb) 120 + return NULL; 121 + 122 + rcu_read_lock(); 123 + b_port = rcu_dereference(mep->b_port); 124 + if (!b_port) { 125 + kfree_skb(skb); 126 + rcu_read_unlock(); 127 + return NULL; 128 + } 129 + skb->dev = b_port->dev; 130 + rcu_read_unlock(); 131 + /* The device cannot be deleted until the work_queue functions has 132 + * completed. This function is called from ccm_tx_work_expired() 133 + * that is a work_queue functions. 134 + */ 135 + 136 + skb->protocol = htons(ETH_P_CFM); 137 + skb->priority = CFM_FRAME_PRIO; 138 + 139 + /* Ethernet header */ 140 + eth_hdr = skb_put(skb, sizeof(*eth_hdr)); 141 + ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr); 142 + ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr); 143 + eth_hdr->h_proto = htons(ETH_P_CFM); 144 + 145 + /* Common CFM Header */ 146 + common_hdr = skb_put(skb, sizeof(*common_hdr)); 147 + common_hdr->mdlevel_version = mep->config.mdlevel << 5; 148 + common_hdr->opcode = BR_CFM_OPCODE_CCM; 149 + common_hdr->flags = (mep->rdi << 7) | 150 + interval_to_pdu(mep->cc_config.exp_interval); 151 + common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET; 152 + 153 + /* Sequence number */ 154 + snumber = skb_put(skb, sizeof(*snumber)); 155 + if (tx_info->seq_no_update) { 156 + *snumber = cpu_to_be32(mep->ccm_tx_snumber); 157 + mep->ccm_tx_snumber += 1; 158 + } else { 159 + *snumber = 0; 160 + } 161 + 162 + mepid = skb_put(skb, sizeof(*mepid)); 163 + *mepid = cpu_to_be16((u16)mep->config.mepid); 164 + 165 + maid = skb_put(skb, sizeof(*maid)); 166 + memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data)); 167 + 168 + /* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */ 169 + itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE); 170 + memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE); 171 + 172 + /* Generel CFM TLV format: 173 + * TLV type: one byte 174 + * TLV value length: two bytes 175 + * TLV value: 'TLV value length' bytes 176 + */ 177 + 178 + /* Port status TLV. The value length is 1. Total of 4 bytes. */ 179 + if (tx_info->port_tlv) { 180 + status_tlv = skb_put(skb, sizeof(*status_tlv)); 181 + *status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) | 182 + (1 << 8) | /* Value length */ 183 + (tx_info->port_tlv_value & 0xFF)); 184 + } 185 + 186 + /* Interface status TLV. The value length is 1. Total of 4 bytes. */ 187 + if (tx_info->if_tlv) { 188 + status_tlv = skb_put(skb, sizeof(*status_tlv)); 189 + *status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) | 190 + (1 << 8) | /* Value length */ 191 + (tx_info->if_tlv_value & 0xFF)); 192 + } 193 + 194 + /* End TLV */ 195 + e_tlv = skb_put(skb, sizeof(*e_tlv)); 196 + *e_tlv = CFM_ENDE_TLV_TYPE; 197 + 198 + return skb; 199 + } 200 + 201 + static void ccm_frame_tx(struct sk_buff *skb) 202 + { 203 + skb_reset_network_header(skb); 204 + dev_queue_xmit(skb); 205 + } 206 + 207 + /* This function is called with the configured CC 'expected_interval' 208 + * in order to drive CCM transmission when enabled. 209 + */ 210 + static void ccm_tx_work_expired(struct work_struct *work) 211 + { 212 + struct delayed_work *del_work; 213 + struct br_cfm_mep *mep; 214 + struct sk_buff *skb; 215 + u32 interval_us; 216 + 217 + del_work = to_delayed_work(work); 218 + mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork); 219 + 220 + if (time_before_eq(mep->ccm_tx_end, jiffies)) { 221 + /* Transmission period has ended */ 222 + mep->cc_ccm_tx_info.period = 0; 223 + return; 224 + } 225 + 226 + skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info); 227 + if (skb) 228 + ccm_frame_tx(skb); 229 + 230 + interval_us = interval_to_us(mep->cc_config.exp_interval); 231 + queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 232 + usecs_to_jiffies(interval_us)); 233 + } 234 + 56 235 int br_cfm_mep_create(struct net_bridge *br, 57 236 const u32 instance, 58 237 struct br_cfm_mep_create *const create, ··· 294 115 rcu_assign_pointer(mep->b_port, p); 295 116 296 117 INIT_HLIST_HEAD(&mep->peer_mep_list); 118 + INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); 297 119 298 120 hlist_add_tail_rcu(&mep->head, &br->mep_list); 299 121 ··· 314 134 hlist_del_rcu(&peer_mep->head); 315 135 kfree_rcu(peer_mep, rcu); 316 136 } 137 + 138 + cancel_delayed_work_sync(&mep->ccm_tx_dwork); 317 139 318 140 RCU_INIT_POINTER(mep->b_port, NULL); 319 141 hlist_del_rcu(&mep->head); ··· 359 177 } 360 178 361 179 mep->config = *config; 180 + 181 + return 0; 182 + } 183 + 184 + int br_cfm_cc_config_set(struct net_bridge *br, 185 + const u32 instance, 186 + const struct br_cfm_cc_config *const config, 187 + struct netlink_ext_ack *extack) 188 + { 189 + struct br_cfm_mep *mep; 190 + 191 + ASSERT_RTNL(); 192 + 193 + mep = br_mep_find(br, instance); 194 + if (!mep) { 195 + NL_SET_ERR_MSG_MOD(extack, 196 + "MEP instance does not exists"); 197 + return -ENOENT; 198 + } 199 + 200 + /* Check for no change in configuration */ 201 + if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0) 202 + return 0; 203 + 204 + mep->cc_config = *config; 205 + mep->ccm_tx_snumber = 1; 362 206 363 207 return 0; 364 208 } ··· 449 241 450 242 hlist_del_rcu(&peer_mep->head); 451 243 kfree_rcu(peer_mep, rcu); 244 + 245 + return 0; 246 + } 247 + 248 + int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance, 249 + const bool rdi, struct netlink_ext_ack *extack) 250 + { 251 + struct br_cfm_mep *mep; 252 + 253 + ASSERT_RTNL(); 254 + 255 + mep = br_mep_find(br, instance); 256 + if (!mep) { 257 + NL_SET_ERR_MSG_MOD(extack, 258 + "MEP instance does not exists"); 259 + return -ENOENT; 260 + } 261 + 262 + mep->rdi = rdi; 263 + 264 + return 0; 265 + } 266 + 267 + int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance, 268 + const struct br_cfm_cc_ccm_tx_info *const tx_info, 269 + struct netlink_ext_ack *extack) 270 + { 271 + struct br_cfm_mep *mep; 272 + 273 + ASSERT_RTNL(); 274 + 275 + mep = br_mep_find(br, instance); 276 + if (!mep) { 277 + NL_SET_ERR_MSG_MOD(extack, 278 + "MEP instance does not exists"); 279 + return -ENOENT; 280 + } 281 + 282 + if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) { 283 + /* No change in tx_info. */ 284 + if (mep->cc_ccm_tx_info.period == 0) 285 + /* Transmission is not enabled - just return */ 286 + return 0; 287 + 288 + /* Transmission is ongoing, the end time is recalculated */ 289 + mep->ccm_tx_end = jiffies + 290 + usecs_to_jiffies(tx_info->period * 1000000); 291 + return 0; 292 + } 293 + 294 + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0) 295 + /* Some change in info and transmission is not ongoing */ 296 + goto save; 297 + 298 + if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) { 299 + /* Some change in info and transmission is ongoing 300 + * The end time is recalculated 301 + */ 302 + mep->ccm_tx_end = jiffies + 303 + usecs_to_jiffies(tx_info->period * 1000000); 304 + 305 + goto save; 306 + } 307 + 308 + if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) { 309 + cancel_delayed_work_sync(&mep->ccm_tx_dwork); 310 + goto save; 311 + } 312 + 313 + /* Start delayed work to transmit CCM frames. It is done with zero delay 314 + * to send first frame immediately 315 + */ 316 + mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000); 317 + queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 0); 318 + 319 + save: 320 + mep->cc_ccm_tx_info = *tx_info; 452 321 453 322 return 0; 454 323 }
+54
net/bridge/br_private_cfm.h
··· 32 32 const struct br_cfm_mep_config *const config, 33 33 struct netlink_ext_ack *extack); 34 34 35 + struct br_cfm_maid { 36 + u8 data[CFM_MAID_LENGTH]; 37 + }; 38 + 39 + struct br_cfm_cc_config { 40 + /* Expected received CCM PDU MAID. */ 41 + struct br_cfm_maid exp_maid; 42 + 43 + /* Expected received CCM PDU interval. */ 44 + /* Transmitting CCM PDU interval when CCM tx is enabled. */ 45 + enum br_cfm_ccm_interval exp_interval; 46 + }; 47 + 48 + int br_cfm_cc_config_set(struct net_bridge *br, 49 + const u32 instance, 50 + const struct br_cfm_cc_config *const config, 51 + struct netlink_ext_ack *extack); 52 + 35 53 int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance, 36 54 u32 peer_mep_id, 37 55 struct netlink_ext_ack *extack); ··· 57 39 u32 peer_mep_id, 58 40 struct netlink_ext_ack *extack); 59 41 42 + /* Transmitted CCM Remote Defect Indication status set. 43 + * This RDI is inserted in transmitted CCM PDUs if CCM transmission is enabled. 44 + * See br_cfm_cc_ccm_tx() with interval != BR_CFM_CCM_INTERVAL_NONE 45 + */ 46 + int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance, 47 + const bool rdi, struct netlink_ext_ack *extack); 48 + 49 + /* OAM PDU Tx information */ 50 + struct br_cfm_cc_ccm_tx_info { 51 + struct mac_addr dmac; 52 + /* The CCM will be transmitted for this period in seconds. 53 + * Call br_cfm_cc_ccm_tx before timeout to keep transmission alive. 54 + * When period is zero any ongoing transmission will be stopped. 55 + */ 56 + u32 period; 57 + 58 + bool seq_no_update; /* Update Tx CCM sequence number */ 59 + bool if_tlv; /* Insert Interface Status TLV */ 60 + u8 if_tlv_value; /* Interface Status TLV value */ 61 + bool port_tlv; /* Insert Port Status TLV */ 62 + u8 port_tlv_value; /* Port Status TLV value */ 63 + /* Sender ID TLV ?? 64 + * Organization-Specific TLV ?? 65 + */ 66 + }; 67 + 68 + int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance, 69 + const struct br_cfm_cc_ccm_tx_info *const tx_info, 70 + struct netlink_ext_ack *extack); 71 + 60 72 struct br_cfm_mep { 61 73 /* list header of MEP instances */ 62 74 struct hlist_node head; 63 75 u32 instance; 64 76 struct br_cfm_mep_create create; 65 77 struct br_cfm_mep_config config; 78 + struct br_cfm_cc_config cc_config; 79 + struct br_cfm_cc_ccm_tx_info cc_ccm_tx_info; 66 80 /* List of multiple peer MEPs */ 67 81 struct hlist_head peer_mep_list; 68 82 struct net_bridge_port __rcu *b_port; 83 + unsigned long ccm_tx_end; 84 + struct delayed_work ccm_tx_dwork; 85 + u32 ccm_tx_snumber; 86 + bool rdi; 69 87 struct rcu_head rcu; 70 88 }; 71 89