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;
}
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));
}
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.
*/
/* 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));
}
/* This function is called with the configured CC 'expected_interval' * in order to drive CCM transmission when enabled.
*/ staticvoid 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;
/* This function is called with 1/4 of the configured CC 'expected_interval' * in order to detect CCM defect after 3.25 interval.
*/ staticvoid 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;
/* 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();
}
}
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;
}
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);
/* 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 */
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 = 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 = 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);
/* 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;
/* 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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.