Commit | Line | Data |
---|---|---|
86a14b79 HB |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | ||
3 | #include <linux/cfm_bridge.h> | |
4 | #include <uapi/linux/cfm_bridge.h> | |
5 | #include "br_private_cfm.h" | |
6 | ||
7 | static struct br_cfm_mep *br_mep_find(struct net_bridge *br, u32 instance) | |
8 | { | |
9 | struct br_cfm_mep *mep; | |
10 | ||
11 | hlist_for_each_entry(mep, &br->mep_list, head) | |
12 | if (mep->instance == instance) | |
13 | return mep; | |
14 | ||
15 | return NULL; | |
16 | } | |
17 | ||
18 | static struct br_cfm_mep *br_mep_find_ifindex(struct net_bridge *br, | |
19 | u32 ifindex) | |
20 | { | |
21 | struct br_cfm_mep *mep; | |
22 | ||
23 | hlist_for_each_entry_rcu(mep, &br->mep_list, head, | |
24 | lockdep_rtnl_is_held()) | |
25 | if (mep->create.ifindex == ifindex) | |
26 | return mep; | |
27 | ||
28 | return NULL; | |
29 | } | |
30 | ||
31 | static struct br_cfm_peer_mep *br_peer_mep_find(struct br_cfm_mep *mep, | |
32 | u32 mepid) | |
33 | { | |
34 | struct br_cfm_peer_mep *peer_mep; | |
35 | ||
36 | hlist_for_each_entry_rcu(peer_mep, &mep->peer_mep_list, head, | |
37 | lockdep_rtnl_is_held()) | |
38 | if (peer_mep->mepid == mepid) | |
39 | return peer_mep; | |
40 | ||
41 | return NULL; | |
42 | } | |
43 | ||
44 | static struct net_bridge_port *br_mep_get_port(struct net_bridge *br, | |
45 | u32 ifindex) | |
46 | { | |
47 | struct net_bridge_port *port; | |
48 | ||
49 | list_for_each_entry(port, &br->port_list, list) | |
50 | if (port->dev->ifindex == ifindex) | |
51 | return port; | |
52 | ||
53 | return NULL; | |
54 | } | |
55 | ||
a806ad8e HB |
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 | ||
dc32cbb3 HB |
104 | /* Convert the CCM PDU value to interval on interface. */ |
105 | static u32 pdu_to_interval(u32 value) | |
106 | { | |
107 | switch (value) { | |
108 | case 0: | |
109 | return BR_CFM_CCM_INTERVAL_NONE; | |
110 | case 1: | |
111 | return BR_CFM_CCM_INTERVAL_3_3_MS; | |
112 | case 2: | |
113 | return BR_CFM_CCM_INTERVAL_10_MS; | |
114 | case 3: | |
115 | return BR_CFM_CCM_INTERVAL_100_MS; | |
116 | case 4: | |
117 | return BR_CFM_CCM_INTERVAL_1_SEC; | |
118 | case 5: | |
119 | return BR_CFM_CCM_INTERVAL_10_SEC; | |
120 | case 6: | |
121 | return BR_CFM_CCM_INTERVAL_1_MIN; | |
122 | case 7: | |
123 | return BR_CFM_CCM_INTERVAL_10_MIN; | |
124 | } | |
125 | return BR_CFM_CCM_INTERVAL_NONE; | |
126 | } | |
127 | ||
128 | static void ccm_rx_timer_start(struct br_cfm_peer_mep *peer_mep) | |
129 | { | |
130 | u32 interval_us; | |
131 | ||
132 | interval_us = interval_to_us(peer_mep->mep->cc_config.exp_interval); | |
133 | /* Function ccm_rx_dwork must be called with 1/4 | |
134 | * of the configured CC 'expected_interval' | |
135 | * in order to detect CCM defect after 3.25 interval. | |
136 | */ | |
137 | queue_delayed_work(system_wq, &peer_mep->ccm_rx_dwork, | |
138 | usecs_to_jiffies(interval_us / 4)); | |
139 | } | |
140 | ||
141 | static void cc_peer_enable(struct br_cfm_peer_mep *peer_mep) | |
142 | { | |
143 | memset(&peer_mep->cc_status, 0, sizeof(peer_mep->cc_status)); | |
144 | peer_mep->ccm_rx_count_miss = 0; | |
145 | ||
146 | ccm_rx_timer_start(peer_mep); | |
147 | } | |
148 | ||
149 | static void cc_peer_disable(struct br_cfm_peer_mep *peer_mep) | |
150 | { | |
151 | cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork); | |
152 | } | |
153 | ||
a806ad8e HB |
154 | static struct sk_buff *ccm_frame_build(struct br_cfm_mep *mep, |
155 | const struct br_cfm_cc_ccm_tx_info *const tx_info) | |
156 | ||
157 | { | |
158 | struct br_cfm_common_hdr *common_hdr; | |
159 | struct net_bridge_port *b_port; | |
160 | struct br_cfm_maid *maid; | |
161 | u8 *itu_reserved, *e_tlv; | |
162 | struct ethhdr *eth_hdr; | |
163 | struct sk_buff *skb; | |
164 | __be32 *status_tlv; | |
165 | __be32 *snumber; | |
166 | __be16 *mepid; | |
167 | ||
168 | skb = dev_alloc_skb(CFM_CCM_MAX_FRAME_LENGTH); | |
169 | if (!skb) | |
170 | return NULL; | |
171 | ||
172 | rcu_read_lock(); | |
173 | b_port = rcu_dereference(mep->b_port); | |
174 | if (!b_port) { | |
175 | kfree_skb(skb); | |
176 | rcu_read_unlock(); | |
177 | return NULL; | |
178 | } | |
179 | skb->dev = b_port->dev; | |
180 | rcu_read_unlock(); | |
181 | /* The device cannot be deleted until the work_queue functions has | |
182 | * completed. This function is called from ccm_tx_work_expired() | |
183 | * that is a work_queue functions. | |
184 | */ | |
185 | ||
186 | skb->protocol = htons(ETH_P_CFM); | |
187 | skb->priority = CFM_FRAME_PRIO; | |
188 | ||
189 | /* Ethernet header */ | |
190 | eth_hdr = skb_put(skb, sizeof(*eth_hdr)); | |
191 | ether_addr_copy(eth_hdr->h_dest, tx_info->dmac.addr); | |
192 | ether_addr_copy(eth_hdr->h_source, mep->config.unicast_mac.addr); | |
193 | eth_hdr->h_proto = htons(ETH_P_CFM); | |
194 | ||
195 | /* Common CFM Header */ | |
196 | common_hdr = skb_put(skb, sizeof(*common_hdr)); | |
197 | common_hdr->mdlevel_version = mep->config.mdlevel << 5; | |
198 | common_hdr->opcode = BR_CFM_OPCODE_CCM; | |
199 | common_hdr->flags = (mep->rdi << 7) | | |
200 | interval_to_pdu(mep->cc_config.exp_interval); | |
201 | common_hdr->tlv_offset = CFM_CCM_TLV_OFFSET; | |
202 | ||
203 | /* Sequence number */ | |
204 | snumber = skb_put(skb, sizeof(*snumber)); | |
205 | if (tx_info->seq_no_update) { | |
206 | *snumber = cpu_to_be32(mep->ccm_tx_snumber); | |
207 | mep->ccm_tx_snumber += 1; | |
208 | } else { | |
209 | *snumber = 0; | |
210 | } | |
211 | ||
212 | mepid = skb_put(skb, sizeof(*mepid)); | |
213 | *mepid = cpu_to_be16((u16)mep->config.mepid); | |
214 | ||
215 | maid = skb_put(skb, sizeof(*maid)); | |
216 | memcpy(maid->data, mep->cc_config.exp_maid.data, sizeof(maid->data)); | |
217 | ||
218 | /* ITU reserved (CFM_CCM_ITU_RESERVED_SIZE octets) */ | |
219 | itu_reserved = skb_put(skb, CFM_CCM_ITU_RESERVED_SIZE); | |
220 | memset(itu_reserved, 0, CFM_CCM_ITU_RESERVED_SIZE); | |
221 | ||
222 | /* Generel CFM TLV format: | |
223 | * TLV type: one byte | |
224 | * TLV value length: two bytes | |
225 | * TLV value: 'TLV value length' bytes | |
226 | */ | |
227 | ||
228 | /* Port status TLV. The value length is 1. Total of 4 bytes. */ | |
229 | if (tx_info->port_tlv) { | |
230 | status_tlv = skb_put(skb, sizeof(*status_tlv)); | |
231 | *status_tlv = cpu_to_be32((CFM_PORT_STATUS_TLV_TYPE << 24) | | |
232 | (1 << 8) | /* Value length */ | |
233 | (tx_info->port_tlv_value & 0xFF)); | |
234 | } | |
235 | ||
236 | /* Interface status TLV. The value length is 1. Total of 4 bytes. */ | |
237 | if (tx_info->if_tlv) { | |
238 | status_tlv = skb_put(skb, sizeof(*status_tlv)); | |
239 | *status_tlv = cpu_to_be32((CFM_IF_STATUS_TLV_TYPE << 24) | | |
240 | (1 << 8) | /* Value length */ | |
241 | (tx_info->if_tlv_value & 0xFF)); | |
242 | } | |
243 | ||
244 | /* End TLV */ | |
245 | e_tlv = skb_put(skb, sizeof(*e_tlv)); | |
246 | *e_tlv = CFM_ENDE_TLV_TYPE; | |
247 | ||
248 | return skb; | |
249 | } | |
250 | ||
251 | static void ccm_frame_tx(struct sk_buff *skb) | |
252 | { | |
253 | skb_reset_network_header(skb); | |
254 | dev_queue_xmit(skb); | |
255 | } | |
256 | ||
257 | /* This function is called with the configured CC 'expected_interval' | |
258 | * in order to drive CCM transmission when enabled. | |
259 | */ | |
260 | static void ccm_tx_work_expired(struct work_struct *work) | |
261 | { | |
262 | struct delayed_work *del_work; | |
263 | struct br_cfm_mep *mep; | |
264 | struct sk_buff *skb; | |
265 | u32 interval_us; | |
266 | ||
267 | del_work = to_delayed_work(work); | |
268 | mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork); | |
269 | ||
270 | if (time_before_eq(mep->ccm_tx_end, jiffies)) { | |
271 | /* Transmission period has ended */ | |
272 | mep->cc_ccm_tx_info.period = 0; | |
273 | return; | |
274 | } | |
275 | ||
276 | skb = ccm_frame_build(mep, &mep->cc_ccm_tx_info); | |
277 | if (skb) | |
278 | ccm_frame_tx(skb); | |
279 | ||
280 | interval_us = interval_to_us(mep->cc_config.exp_interval); | |
281 | queue_delayed_work(system_wq, &mep->ccm_tx_dwork, | |
282 | usecs_to_jiffies(interval_us)); | |
283 | } | |
284 | ||
dc32cbb3 HB |
285 | /* This function is called with 1/4 of the configured CC 'expected_interval' |
286 | * in order to detect CCM defect after 3.25 interval. | |
287 | */ | |
288 | static void ccm_rx_work_expired(struct work_struct *work) | |
289 | { | |
290 | struct br_cfm_peer_mep *peer_mep; | |
291 | struct delayed_work *del_work; | |
292 | ||
293 | del_work = to_delayed_work(work); | |
294 | peer_mep = container_of(del_work, struct br_cfm_peer_mep, ccm_rx_dwork); | |
295 | ||
296 | /* After 13 counts (4 * 3,25) then 3.25 intervals are expired */ | |
297 | if (peer_mep->ccm_rx_count_miss < 13) { | |
298 | /* 3.25 intervals are NOT expired without CCM reception */ | |
299 | peer_mep->ccm_rx_count_miss++; | |
300 | ||
301 | /* Start timer again */ | |
302 | ccm_rx_timer_start(peer_mep); | |
303 | } else { | |
304 | /* 3.25 intervals are expired without CCM reception. | |
305 | * CCM defect detected | |
306 | */ | |
307 | peer_mep->cc_status.ccm_defect = true; | |
308 | } | |
309 | } | |
310 | ||
311 | static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index, | |
312 | struct br_cfm_peer_mep *peer_mep) | |
313 | { | |
314 | __be32 *s_tlv; | |
315 | __be32 _s_tlv; | |
316 | u32 h_s_tlv; | |
317 | u8 *e_tlv; | |
318 | u8 _e_tlv; | |
319 | ||
320 | e_tlv = skb_header_pointer(skb, index, sizeof(_e_tlv), &_e_tlv); | |
321 | if (!e_tlv) | |
322 | return 0; | |
323 | ||
324 | /* TLV is present - get the status TLV */ | |
325 | s_tlv = skb_header_pointer(skb, | |
326 | index, | |
327 | sizeof(_s_tlv), &_s_tlv); | |
328 | if (!s_tlv) | |
329 | return 0; | |
330 | ||
331 | h_s_tlv = ntohl(*s_tlv); | |
332 | if ((h_s_tlv >> 24) == CFM_IF_STATUS_TLV_TYPE) { | |
333 | /* Interface status TLV */ | |
334 | peer_mep->cc_status.tlv_seen = true; | |
335 | peer_mep->cc_status.if_tlv_value = (h_s_tlv & 0xFF); | |
336 | } | |
337 | ||
338 | if ((h_s_tlv >> 24) == CFM_PORT_STATUS_TLV_TYPE) { | |
339 | /* Port status TLV */ | |
340 | peer_mep->cc_status.tlv_seen = true; | |
341 | peer_mep->cc_status.port_tlv_value = (h_s_tlv & 0xFF); | |
342 | } | |
343 | ||
344 | /* The Sender ID TLV is not handled */ | |
345 | /* The Organization-Specific TLV is not handled */ | |
346 | ||
347 | /* Return the length of this tlv. | |
348 | * This is the length of the value field plus 3 bytes for size of type | |
349 | * field and length field | |
350 | */ | |
351 | return ((h_s_tlv >> 8) & 0xFFFF) + 3; | |
352 | } | |
353 | ||
354 | /* note: already called with rcu_read_lock */ | |
355 | static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) | |
356 | { | |
357 | u32 mdlevel, interval, size, index, max; | |
358 | const struct br_cfm_common_hdr *hdr; | |
359 | struct br_cfm_peer_mep *peer_mep; | |
360 | const struct br_cfm_maid *maid; | |
361 | struct br_cfm_common_hdr _hdr; | |
362 | struct br_cfm_maid _maid; | |
363 | struct br_cfm_mep *mep; | |
364 | struct net_bridge *br; | |
365 | __be32 *snumber; | |
366 | __be32 _snumber; | |
367 | __be16 *mepid; | |
368 | __be16 _mepid; | |
369 | ||
370 | if (port->state == BR_STATE_DISABLED) | |
371 | return 0; | |
372 | ||
373 | hdr = skb_header_pointer(skb, 0, sizeof(_hdr), &_hdr); | |
374 | if (!hdr) | |
375 | return 1; | |
376 | ||
377 | br = port->br; | |
378 | mep = br_mep_find_ifindex(br, port->dev->ifindex); | |
379 | if (unlikely(!mep)) | |
380 | /* No MEP on this port - must be forwarded */ | |
381 | return 0; | |
382 | ||
383 | mdlevel = hdr->mdlevel_version >> 5; | |
384 | if (mdlevel > mep->config.mdlevel) | |
385 | /* The level is above this MEP level - must be forwarded */ | |
386 | return 0; | |
387 | ||
388 | if ((hdr->mdlevel_version & 0x1F) != 0) { | |
389 | /* Invalid version */ | |
390 | mep->status.version_unexp_seen = true; | |
391 | return 1; | |
392 | } | |
393 | ||
394 | if (mdlevel < mep->config.mdlevel) { | |
395 | /* The level is below this MEP level */ | |
396 | mep->status.rx_level_low_seen = true; | |
397 | return 1; | |
398 | } | |
399 | ||
400 | if (hdr->opcode == BR_CFM_OPCODE_CCM) { | |
401 | /* CCM PDU received. */ | |
402 | /* MA ID is after common header + sequence number + MEP ID */ | |
403 | maid = skb_header_pointer(skb, | |
404 | CFM_CCM_PDU_MAID_OFFSET, | |
405 | sizeof(_maid), &_maid); | |
406 | if (!maid) | |
407 | return 1; | |
408 | if (memcmp(maid->data, mep->cc_config.exp_maid.data, | |
409 | sizeof(maid->data))) | |
410 | /* MA ID not as expected */ | |
411 | return 1; | |
412 | ||
413 | /* MEP ID is after common header + sequence number */ | |
414 | mepid = skb_header_pointer(skb, | |
415 | CFM_CCM_PDU_MEPID_OFFSET, | |
416 | sizeof(_mepid), &_mepid); | |
417 | if (!mepid) | |
418 | return 1; | |
419 | peer_mep = br_peer_mep_find(mep, (u32)ntohs(*mepid)); | |
420 | if (!peer_mep) | |
421 | return 1; | |
422 | ||
423 | /* Interval is in common header flags */ | |
424 | interval = hdr->flags & 0x07; | |
425 | if (mep->cc_config.exp_interval != pdu_to_interval(interval)) | |
426 | /* Interval not as expected */ | |
427 | return 1; | |
428 | ||
429 | /* A valid CCM frame is received */ | |
430 | if (peer_mep->cc_status.ccm_defect) { | |
431 | peer_mep->cc_status.ccm_defect = false; | |
432 | ||
433 | /* Start CCM RX timer */ | |
434 | ccm_rx_timer_start(peer_mep); | |
435 | } | |
436 | ||
437 | peer_mep->cc_status.seen = true; | |
438 | peer_mep->ccm_rx_count_miss = 0; | |
439 | ||
440 | /* RDI is in common header flags */ | |
441 | peer_mep->cc_status.rdi = (hdr->flags & 0x80) ? true : false; | |
442 | ||
443 | /* Sequence number is after common header */ | |
444 | snumber = skb_header_pointer(skb, | |
445 | CFM_CCM_PDU_SEQNR_OFFSET, | |
446 | sizeof(_snumber), &_snumber); | |
447 | if (!snumber) | |
448 | return 1; | |
449 | if (ntohl(*snumber) != (mep->ccm_rx_snumber + 1)) | |
450 | /* Unexpected sequence number */ | |
451 | peer_mep->cc_status.seq_unexp_seen = true; | |
452 | ||
453 | mep->ccm_rx_snumber = ntohl(*snumber); | |
454 | ||
455 | /* TLV end is after common header + sequence number + MEP ID + | |
456 | * MA ID + ITU reserved | |
457 | */ | |
458 | index = CFM_CCM_PDU_TLV_OFFSET; | |
459 | max = 0; | |
460 | do { /* Handle all TLVs */ | |
461 | size = ccm_tlv_extract(skb, index, peer_mep); | |
462 | index += size; | |
463 | max += 1; | |
464 | } while (size != 0 && max < 4); /* Max four TLVs possible */ | |
465 | ||
466 | return 1; | |
467 | } | |
468 | ||
469 | mep->status.opcode_unexp_seen = true; | |
470 | ||
471 | return 1; | |
472 | } | |
473 | ||
474 | static struct br_frame_type cfm_frame_type __read_mostly = { | |
475 | .type = cpu_to_be16(ETH_P_CFM), | |
476 | .frame_handler = br_cfm_frame_rx, | |
477 | }; | |
478 | ||
86a14b79 HB |
479 | int br_cfm_mep_create(struct net_bridge *br, |
480 | const u32 instance, | |
481 | struct br_cfm_mep_create *const create, | |
482 | struct netlink_ext_ack *extack) | |
483 | { | |
484 | struct net_bridge_port *p; | |
485 | struct br_cfm_mep *mep; | |
486 | ||
487 | ASSERT_RTNL(); | |
488 | ||
489 | if (create->domain == BR_CFM_VLAN) { | |
490 | NL_SET_ERR_MSG_MOD(extack, | |
491 | "VLAN domain not supported"); | |
492 | return -EINVAL; | |
493 | } | |
494 | if (create->domain != BR_CFM_PORT) { | |
495 | NL_SET_ERR_MSG_MOD(extack, | |
496 | "Invalid domain value"); | |
497 | return -EINVAL; | |
498 | } | |
499 | if (create->direction == BR_CFM_MEP_DIRECTION_UP) { | |
500 | NL_SET_ERR_MSG_MOD(extack, | |
501 | "Up-MEP not supported"); | |
502 | return -EINVAL; | |
503 | } | |
504 | if (create->direction != BR_CFM_MEP_DIRECTION_DOWN) { | |
505 | NL_SET_ERR_MSG_MOD(extack, | |
506 | "Invalid direction value"); | |
507 | return -EINVAL; | |
508 | } | |
509 | p = br_mep_get_port(br, create->ifindex); | |
510 | if (!p) { | |
511 | NL_SET_ERR_MSG_MOD(extack, | |
512 | "Port is not related to bridge"); | |
513 | return -EINVAL; | |
514 | } | |
515 | mep = br_mep_find(br, instance); | |
516 | if (mep) { | |
517 | NL_SET_ERR_MSG_MOD(extack, | |
518 | "MEP instance already exists"); | |
519 | return -EEXIST; | |
520 | } | |
521 | ||
522 | /* In PORT domain only one instance can be created per port */ | |
523 | if (create->domain == BR_CFM_PORT) { | |
524 | mep = br_mep_find_ifindex(br, create->ifindex); | |
525 | if (mep) { | |
526 | NL_SET_ERR_MSG_MOD(extack, | |
527 | "Only one Port MEP on a port allowed"); | |
528 | return -EINVAL; | |
529 | } | |
530 | } | |
531 | ||
532 | mep = kzalloc(sizeof(*mep), GFP_KERNEL); | |
533 | if (!mep) | |
534 | return -ENOMEM; | |
535 | ||
536 | mep->create = *create; | |
537 | mep->instance = instance; | |
538 | rcu_assign_pointer(mep->b_port, p); | |
539 | ||
540 | INIT_HLIST_HEAD(&mep->peer_mep_list); | |
a806ad8e | 541 | INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); |
86a14b79 | 542 | |
dc32cbb3 HB |
543 | if (hlist_empty(&br->mep_list)) |
544 | br_add_frame(br, &cfm_frame_type); | |
545 | ||
86a14b79 HB |
546 | hlist_add_tail_rcu(&mep->head, &br->mep_list); |
547 | ||
548 | return 0; | |
549 | } | |
550 | ||
551 | static void mep_delete_implementation(struct net_bridge *br, | |
552 | struct br_cfm_mep *mep) | |
553 | { | |
554 | struct br_cfm_peer_mep *peer_mep; | |
555 | struct hlist_node *n_store; | |
556 | ||
557 | ASSERT_RTNL(); | |
558 | ||
559 | /* Empty and free peer MEP list */ | |
560 | hlist_for_each_entry_safe(peer_mep, n_store, &mep->peer_mep_list, head) { | |
dc32cbb3 | 561 | cancel_delayed_work_sync(&peer_mep->ccm_rx_dwork); |
86a14b79 HB |
562 | hlist_del_rcu(&peer_mep->head); |
563 | kfree_rcu(peer_mep, rcu); | |
564 | } | |
565 | ||
a806ad8e HB |
566 | cancel_delayed_work_sync(&mep->ccm_tx_dwork); |
567 | ||
86a14b79 HB |
568 | RCU_INIT_POINTER(mep->b_port, NULL); |
569 | hlist_del_rcu(&mep->head); | |
570 | kfree_rcu(mep, rcu); | |
dc32cbb3 HB |
571 | |
572 | if (hlist_empty(&br->mep_list)) | |
573 | br_del_frame(br, &cfm_frame_type); | |
86a14b79 HB |
574 | } |
575 | ||
576 | int br_cfm_mep_delete(struct net_bridge *br, | |
577 | const u32 instance, | |
578 | struct netlink_ext_ack *extack) | |
579 | { | |
580 | struct br_cfm_mep *mep; | |
581 | ||
582 | ASSERT_RTNL(); | |
583 | ||
584 | mep = br_mep_find(br, instance); | |
585 | if (!mep) { | |
586 | NL_SET_ERR_MSG_MOD(extack, | |
587 | "MEP instance does not exists"); | |
588 | return -ENOENT; | |
589 | } | |
590 | ||
591 | mep_delete_implementation(br, mep); | |
592 | ||
593 | return 0; | |
594 | } | |
595 | ||
596 | int br_cfm_mep_config_set(struct net_bridge *br, | |
597 | const u32 instance, | |
598 | const struct br_cfm_mep_config *const config, | |
599 | struct netlink_ext_ack *extack) | |
600 | { | |
601 | struct br_cfm_mep *mep; | |
602 | ||
603 | ASSERT_RTNL(); | |
604 | ||
605 | mep = br_mep_find(br, instance); | |
606 | if (!mep) { | |
607 | NL_SET_ERR_MSG_MOD(extack, | |
608 | "MEP instance does not exists"); | |
609 | return -ENOENT; | |
610 | } | |
611 | ||
612 | mep->config = *config; | |
613 | ||
614 | return 0; | |
615 | } | |
616 | ||
a806ad8e HB |
617 | int br_cfm_cc_config_set(struct net_bridge *br, |
618 | const u32 instance, | |
619 | const struct br_cfm_cc_config *const config, | |
620 | struct netlink_ext_ack *extack) | |
621 | { | |
dc32cbb3 | 622 | struct br_cfm_peer_mep *peer_mep; |
a806ad8e HB |
623 | struct br_cfm_mep *mep; |
624 | ||
625 | ASSERT_RTNL(); | |
626 | ||
627 | mep = br_mep_find(br, instance); | |
628 | if (!mep) { | |
629 | NL_SET_ERR_MSG_MOD(extack, | |
630 | "MEP instance does not exists"); | |
631 | return -ENOENT; | |
632 | } | |
633 | ||
634 | /* Check for no change in configuration */ | |
635 | if (memcmp(config, &mep->cc_config, sizeof(*config)) == 0) | |
636 | return 0; | |
637 | ||
dc32cbb3 HB |
638 | if (config->enable && !mep->cc_config.enable) |
639 | /* CC is enabled */ | |
640 | hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) | |
641 | cc_peer_enable(peer_mep); | |
642 | ||
643 | if (!config->enable && mep->cc_config.enable) | |
644 | /* CC is disabled */ | |
645 | hlist_for_each_entry(peer_mep, &mep->peer_mep_list, head) | |
646 | cc_peer_disable(peer_mep); | |
647 | ||
a806ad8e | 648 | mep->cc_config = *config; |
dc32cbb3 | 649 | mep->ccm_rx_snumber = 0; |
a806ad8e HB |
650 | mep->ccm_tx_snumber = 1; |
651 | ||
652 | return 0; | |
653 | } | |
654 | ||
86a14b79 HB |
655 | int br_cfm_cc_peer_mep_add(struct net_bridge *br, const u32 instance, |
656 | u32 mepid, | |
657 | struct netlink_ext_ack *extack) | |
658 | { | |
659 | struct br_cfm_peer_mep *peer_mep; | |
660 | struct br_cfm_mep *mep; | |
661 | ||
662 | ASSERT_RTNL(); | |
663 | ||
664 | mep = br_mep_find(br, instance); | |
665 | if (!mep) { | |
666 | NL_SET_ERR_MSG_MOD(extack, | |
667 | "MEP instance does not exists"); | |
668 | return -ENOENT; | |
669 | } | |
670 | ||
671 | peer_mep = br_peer_mep_find(mep, mepid); | |
672 | if (peer_mep) { | |
673 | NL_SET_ERR_MSG_MOD(extack, | |
674 | "Peer MEP-ID already exists"); | |
675 | return -EEXIST; | |
676 | } | |
677 | ||
678 | peer_mep = kzalloc(sizeof(*peer_mep), GFP_KERNEL); | |
679 | if (!peer_mep) | |
680 | return -ENOMEM; | |
681 | ||
682 | peer_mep->mepid = mepid; | |
683 | peer_mep->mep = mep; | |
dc32cbb3 HB |
684 | INIT_DELAYED_WORK(&peer_mep->ccm_rx_dwork, ccm_rx_work_expired); |
685 | ||
686 | if (mep->cc_config.enable) | |
687 | cc_peer_enable(peer_mep); | |
86a14b79 HB |
688 | |
689 | hlist_add_tail_rcu(&peer_mep->head, &mep->peer_mep_list); | |
690 | ||
691 | return 0; | |
692 | } | |
693 | ||
694 | int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance, | |
695 | u32 mepid, | |
696 | struct netlink_ext_ack *extack) | |
697 | { | |
698 | struct br_cfm_peer_mep *peer_mep; | |
699 | struct br_cfm_mep *mep; | |
700 | ||
701 | ASSERT_RTNL(); | |
702 | ||
703 | mep = br_mep_find(br, instance); | |
704 | if (!mep) { | |
705 | NL_SET_ERR_MSG_MOD(extack, | |
706 | "MEP instance does not exists"); | |
707 | return -ENOENT; | |
708 | } | |
709 | ||
710 | peer_mep = br_peer_mep_find(mep, mepid); | |
711 | if (!peer_mep) { | |
712 | NL_SET_ERR_MSG_MOD(extack, | |
713 | "Peer MEP-ID does not exists"); | |
714 | return -ENOENT; | |
715 | } | |
716 | ||
dc32cbb3 HB |
717 | cc_peer_disable(peer_mep); |
718 | ||
86a14b79 HB |
719 | hlist_del_rcu(&peer_mep->head); |
720 | kfree_rcu(peer_mep, rcu); | |
721 | ||
722 | return 0; | |
723 | } | |
724 | ||
a806ad8e HB |
725 | int br_cfm_cc_rdi_set(struct net_bridge *br, const u32 instance, |
726 | const bool rdi, struct netlink_ext_ack *extack) | |
727 | { | |
728 | struct br_cfm_mep *mep; | |
729 | ||
730 | ASSERT_RTNL(); | |
731 | ||
732 | mep = br_mep_find(br, instance); | |
733 | if (!mep) { | |
734 | NL_SET_ERR_MSG_MOD(extack, | |
735 | "MEP instance does not exists"); | |
736 | return -ENOENT; | |
737 | } | |
738 | ||
739 | mep->rdi = rdi; | |
740 | ||
741 | return 0; | |
742 | } | |
743 | ||
744 | int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance, | |
745 | const struct br_cfm_cc_ccm_tx_info *const tx_info, | |
746 | struct netlink_ext_ack *extack) | |
747 | { | |
748 | struct br_cfm_mep *mep; | |
749 | ||
750 | ASSERT_RTNL(); | |
751 | ||
752 | mep = br_mep_find(br, instance); | |
753 | if (!mep) { | |
754 | NL_SET_ERR_MSG_MOD(extack, | |
755 | "MEP instance does not exists"); | |
756 | return -ENOENT; | |
757 | } | |
758 | ||
759 | if (memcmp(tx_info, &mep->cc_ccm_tx_info, sizeof(*tx_info)) == 0) { | |
760 | /* No change in tx_info. */ | |
761 | if (mep->cc_ccm_tx_info.period == 0) | |
762 | /* Transmission is not enabled - just return */ | |
763 | return 0; | |
764 | ||
765 | /* Transmission is ongoing, the end time is recalculated */ | |
766 | mep->ccm_tx_end = jiffies + | |
767 | usecs_to_jiffies(tx_info->period * 1000000); | |
768 | return 0; | |
769 | } | |
770 | ||
771 | if (tx_info->period == 0 && mep->cc_ccm_tx_info.period == 0) | |
772 | /* Some change in info and transmission is not ongoing */ | |
773 | goto save; | |
774 | ||
775 | if (tx_info->period != 0 && mep->cc_ccm_tx_info.period != 0) { | |
776 | /* Some change in info and transmission is ongoing | |
777 | * The end time is recalculated | |
778 | */ | |
779 | mep->ccm_tx_end = jiffies + | |
780 | usecs_to_jiffies(tx_info->period * 1000000); | |
781 | ||
782 | goto save; | |
783 | } | |
784 | ||
785 | if (tx_info->period == 0 && mep->cc_ccm_tx_info.period != 0) { | |
786 | cancel_delayed_work_sync(&mep->ccm_tx_dwork); | |
787 | goto save; | |
788 | } | |
789 | ||
790 | /* Start delayed work to transmit CCM frames. It is done with zero delay | |
791 | * to send first frame immediately | |
792 | */ | |
793 | mep->ccm_tx_end = jiffies + usecs_to_jiffies(tx_info->period * 1000000); | |
794 | queue_delayed_work(system_wq, &mep->ccm_tx_dwork, 0); | |
795 | ||
796 | save: | |
797 | mep->cc_ccm_tx_info = *tx_info; | |
798 | ||
799 | return 0; | |
800 | } | |
801 | ||
86a14b79 HB |
802 | /* Deletes the CFM instances on a specific bridge port |
803 | */ | |
804 | void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *port) | |
805 | { | |
806 | struct hlist_node *n_store; | |
807 | struct br_cfm_mep *mep; | |
808 | ||
809 | ASSERT_RTNL(); | |
810 | ||
811 | hlist_for_each_entry_safe(mep, n_store, &br->mep_list, head) | |
812 | if (mep->create.ifindex == port->dev->ifindex) | |
813 | mep_delete_implementation(br, mep); | |
814 | } |