blob: cd92c92b7bb03da48db423cc2ff8528e14cc3443 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
/* This file contains the pieces of the OAM negotiation state machine that are common to all OAM. */
#include "oam_common.h"
#include "bcmolt_utils.h"
#include "bcmolt_math.h"
#include "bcmolt_eon_private.h"
#include "bcmolt_user_appl_epon_oam.h"
#include "dpoe/dpoe.h"
#include "brcm/brcm.h"
#include "ctc/ctc.h"
static const oam_info_add_tlv add_org_spec_tlv[EON_OAM_SET_ID_COUNT] =
{
[EON_OAM_SET_ID_DPOE] = dpoe_tx_add_tlv,
[EON_OAM_SET_ID_BRCM] = brcm_tx_add_tlv,
[EON_OAM_SET_ID_CTC] = ctc_tx_add_tlv
};
static const oam_rx_org_spec_tlv rx_org_spec_tlv[EON_OAM_SET_ID_COUNT] =
{
[EON_OAM_SET_ID_DPOE] = dpoe_rx_tlv,
[EON_OAM_SET_ID_BRCM] = brcm_rx_tlv,
[EON_OAM_SET_ID_CTC] = ctc_rx_tlv
};
static uint8_t get_oam_version(void)
{
return 0x01; /* only version currently supported */
}
static void fill_local_tlv(const eon_link_state *link_state, bcmolt_epon_oam_local_remote_info *tlv)
{
tlv->oam_version = get_oam_version();
tlv->revision = link_state->revision;
tlv->state =
BCMOLT_EPON_OAM_LOCAL_REMOTE_INFO_STATE_PARSER_ACTION1 |
BCMOLT_EPON_OAM_LOCAL_REMOTE_INFO_STATE_MULTIPLEXER_ACTION;
tlv->oam_config =
BCMOLT_EPON_OAM_LOCAL_REMOTE_INFO_CONFIG_OAM_MODE |
BCMOLT_EPON_OAM_LOCAL_REMOTE_INFO_CONFIG_UNIDIRECTIONAL_SUPPORT |
BCMOLT_EPON_OAM_LOCAL_REMOTE_INFO_CONFIG_LINK_EVENTS;
tlv->max_pdu_size = link_state->max_pdu_size;
tlv->oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_TEK;
tlv->vendor_specific_information[0] = 0x06;
tlv->vendor_specific_information[1] = 0x86;
tlv->vendor_specific_information[2] = 0x20;
tlv->vendor_specific_information[3] = 0xA0;
}
static void dump_std_info_tlv(const eon_link_key *link_key, const char *type, bcmolt_epon_oam_local_remote_info *info)
{
EON_LINK_LOG(
DEBUG,
link_key,
"%s: ver %02x rev %04x state %02x cfg %02x pdu %5u oui %06x vendor %02x%02x%02x%02x\n",
type,
info->oam_version,
info->revision,
info->state,
info->oam_config,
info->max_pdu_size,
info->oui,
info->vendor_specific_information[0],
info->vendor_specific_information[1],
info->vendor_specific_information[2],
info->vendor_specific_information[3]);
}
static void dump_org_spec_tlv(const eon_link_key *link_key, bcmolt_epon_oam_organization_specific_info *org_spec)
{
switch (org_spec->oui)
{
case BCMOLT_EPON_OAM_WELL_KNOWN_OUI_TEK:
if (BCMOLT_EPON_OAM_TEK_INFO_TLV_TYPE_EXTENSION_SUPPORT == org_spec->u.tek.tlvs.type)
{
EON_LINK_LOG(
DEBUG, link_key, "BRCM Ext Support: ver %02x rpt %02x pref %02x\n",
org_spec->u.tek.tlvs.u.extension_support.version,
org_spec->u.tek.tlvs.u.extension_support.report_mode,
org_spec->u.tek.tlvs.u.extension_support.preferred_report_mode);
}
break;
case BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE:
if (BCMOLT_EPON_OAM_DPOE_INFO_TLV_TYPE_DPOE_OAM_SUPPORT == org_spec->u.dpoe.dpoe_info_tlv.type)
{
EON_LINK_LOG(
DEBUG, link_key, "DPoE Support: ver %02x\n",
org_spec->u.dpoe.dpoe_info_tlv.u.dpoe_oam_support.dpoe_oam_version);
}
break;
case BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC:
EON_LINK_LOG(
DEBUG, link_key, "CTC Extension Support %u, Preferred Version 0x%02x, Supports (%u):\n",
org_spec->u.ctc.extension_support,
org_spec->u.ctc.version,
org_spec->u.ctc.supp_version_count);
for (uint32_t i = 0; i < org_spec->u.ctc.supp_version_count; i++)
{
EON_LINK_LOG(
DEBUG, link_key, "\tOUI %06x, Version 0x%02x\n",
org_spec->u.ctc.supp_version[i].oui, org_spec->u.ctc.supp_version[i].version);
}
break;
default:
EON_LINK_LOG(WARNING, link_key, "No support for %06x\n", org_spec->oui);
break;
}
}
static void dump_info_frame(const bcmolt_epon_oam_ethernet_frame *oam, const eon_link_state* link_state)
{
static const char* entname[] = {"ONU","OLT"};
char da_buf[MAC_STR_LEN];
char sa_buf[MAC_STR_LEN];
bcmolt_epon_oam_oam_flags flags = oam->protocols[0].u.slow_protocol.value.u.oam.flags;
uint8_t is_olt = (0 == memcmp(link_state->epon_ni_mac_addr.u8, oam->sa.u8, BCMOS_ETH_ALEN));
uint8_t olt_stab = 0 !=
(flags & (is_olt ? BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE : BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE));
uint8_t onu_stab = 0 !=
(flags & (is_olt ? BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE : BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE));
uint8_t olt_eval = 0 !=
(flags & (is_olt ? BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING : BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING));
uint8_t onu_eval = 0 !=
(flags & (is_olt ? BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING : BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING));
EON_LINK_LOG(
DEBUG, &link_state->link_key, "from %s: OLT %s ONU %s\n", entname[is_olt],
olt_stab ? "stab" : (olt_eval ? "eval" : "NONE"),
onu_stab ? "stab" : (onu_eval ? "eval" : "NONE"));
EON_LINK_LOG(DEBUG, &link_state->link_key, " da sa eth su flgs cd\n");
EON_LINK_LOG(
DEBUG, &link_state->link_key, "%s %s %04x %02x %04x %02x\n",
bcmos_mac_2_str(&oam->da, da_buf), bcmos_mac_2_str(&oam->sa, sa_buf),
oam->protocols[0].ethertype, oam->protocols[0].u.slow_protocol.value.subtype,
oam->protocols[0].u.slow_protocol.value.u.oam.flags,
oam->protocols[0].u.slow_protocol.value.u.oam.content.code);
EON_LINK_LOG(
DEBUG, &link_state->link_key, "TLVS: (%u)\n",
oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs_count);
for (uint8_t i = 0; i < oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs_count; i++)
{
switch (oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs[i].type)
{
case BCMOLT_EPON_OAM_INFO_TLV_TYPE_LOCAL:
dump_std_info_tlv(
&link_state->link_key,
"LOCAL",
&oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs[i].u.local.info);
break;
case BCMOLT_EPON_OAM_INFO_TLV_TYPE_REMOTE:
dump_std_info_tlv(
&link_state->link_key,
"REMOTE",
&oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs[i].u.remote.info);
break;
case BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC:
dump_org_spec_tlv(
&link_state->link_key,
&oam->protocols[0].u.slow_protocol.value.u.oam.content.u.info.tlvs[i].u.organization_specific.value);
break;
default:
break;
}
}
}
bcmos_errno build_tx_info_frame(eon_link_state *link_state, eon_frame_data *frame_data)
{
bcmos_bool have_remote_info = link_state->remote_info.oam_version == get_oam_version();
bcmolt_epon_oam_ethernet_frame oam_frame = {};
bcmolt_epon_oam_ethernet_protocol protocol = {};
bcmolt_epon_oam_info_tlv_base tlvs[4] = {};
EON_LINK_LOG(DEBUG, &link_state->link_key, "BUILDING INFO FRAME, current state %04x\n", link_state->oam_state);
oam_frame.da = slow_protocol_multicast_mac;
oam_frame.sa = link_state->epon_ni_mac_addr;
oam_frame.protocols_count = 1;
oam_frame.protocols = &protocol;
protocol.ethertype = BCMOLT_EPON_OAM_PROTOCOL_TYPE_SLOW_PROTOCOL;
protocol.u.slow_protocol.value.subtype = BCMOLT_EPON_OAM_SLOW_PROTOCOL_SUBTYPE_OAM;
protocol.u.slow_protocol.value.u.oam.flags = link_state->oam_state;
protocol.u.slow_protocol.value.u.oam.content.code = BCMOLT_EPON_OAM_OAM_OPCODE_INFO;
protocol.u.slow_protocol.value.u.oam.content.u.info.tlvs_count = 1;
protocol.u.slow_protocol.value.u.oam.content.u.info.tlvs = tlvs;
tlvs[0].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_LOCAL;
fill_local_tlv(link_state, &tlvs[0].u.local.info);
if (have_remote_info)
{
tlvs[1].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_REMOTE;
tlvs[1].u.remote.info = link_state->remote_info;
protocol.u.slow_protocol.value.u.oam.content.u.info.tlvs_count++;
}
add_org_spec_tlv[link_state->oam_set](link_state, &protocol.u.slow_protocol.value.u.oam.content);
tlvs[protocol.u.slow_protocol.value.u.oam.content.u.info.tlvs_count].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_END;
protocol.u.slow_protocol.value.u.oam.content.u.info.tlvs_count++;
dump_info_frame(&oam_frame, link_state);
return epon_oam_pack_frame(&oam_frame, &frame_data->payload, &frame_data->length);
}
/* OK = done
in_progress = send next frame
parse = ignore
other = stop
*/
bcmos_errno handle_rx_info_frame(eon_link_state *link_state, uint16_t rx_length, uint8_t *rx_payload)
{
bcmos_errno rc = BCM_ERR_IN_PROGRESS;
bcmolt_epon_oam_oam_flags start_state;
bcmolt_epon_oam_ethernet_frame *frame;
bcmolt_epon_oam_slow_protocol *sp;
bcmolt_epon_oam_organization_specific_info *org_spec = NULL;
start_state = link_state->oam_state;
EON_LINK_LOG(DEBUG, &link_state->link_key, "RECEIVED INFO FRAME, current state %04x\n", start_state);
frame = epon_oam_unpack(link_state->link_key.device_id, rx_length, rx_payload);
if ((frame->protocols_count == 0) ||
(frame->protocols[0].ethertype != BCMOLT_EPON_OAM_PROTOCOL_TYPE_SLOW_PROTOCOL) ||
(frame->protocols[0].u.slow_protocol.value.subtype != BCMOLT_EPON_OAM_SLOW_PROTOCOL_SUBTYPE_OAM) ||
(frame->protocols[0].u.slow_protocol.value.u.oam.content.code != BCMOLT_EPON_OAM_OAM_OPCODE_INFO))
{
/* not an info frame, ignore */
bcmos_free(frame);
return BCM_ERR_PARSE;
}
dump_info_frame(frame, link_state);
sp = &frame->protocols[0].u.slow_protocol.value;
/* capture link's TLV for subsequent retransmission */
if ((sp->u.oam.content.u.info.tlvs_count > 0) &&
(sp->u.oam.content.u.info.tlvs[0].type == BCMOLT_EPON_OAM_INFO_TLV_TYPE_LOCAL))
{
link_state->remote_info = sp->u.oam.content.u.info.tlvs[0].u.local.info;
}
else
{
bcmos_free(frame);
return BCM_ERR_ONU_ERR_RESP; /* no local info */
}
if (0 != memcmp(frame->da.u8, slow_protocol_multicast_mac.u8, sizeof(slow_protocol_multicast_mac)))
{
rc = BCM_ERR_ONU_ERR_RESP;
EON_LINK_LOG(WARNING, &link_state->link_key,
"DA mismatch "BCMOS_MACADDR_FMT_STR" expected vs. "BCMOS_MACADDR_FMT_STR" actual\n",
BCMOS_MACADDR_PARAMS(&slow_protocol_multicast_mac), BCMOS_MACADDR_PARAMS(&frame->da));
}
if (0 != memcmp(frame->sa.u8, &link_state->link_key.link.mac_address, sizeof(frame->sa)))
{
rc = BCM_ERR_ONU_ERR_RESP;
EON_LINK_LOG(WARNING, &link_state->link_key,
"SA mismatch "BCMOS_MACADDR_FMT_STR" expected vs. "BCMOS_MACADDR_FMT_STR" actual\n",
BCMOS_MACADDR_PARAMS(&link_state->link_key.link.mac_address), BCMOS_MACADDR_PARAMS(&frame->sa));
}
if (sp->u.oam.content.u.info.tlvs[0].u.local.info.oam_version != get_oam_version())
{
rc = BCM_ERR_ONU_ERR_RESP;
EON_LINK_LOG(WARNING, &link_state->link_key, "oam version mismatch 0x%02x expected vs. 0x%02x actual\n",
get_oam_version(), sp->u.oam.content.u.info.tlvs[0].u.local.info.oam_version);
}
/* Is the far end echoing the TLV we sent? */
if ((sp->u.oam.content.u.info.tlvs_count > 1) &&
(sp->u.oam.content.u.info.tlvs[1].type == BCMOLT_EPON_OAM_INFO_TLV_TYPE_REMOTE))
{
if (sp->u.oam.content.u.info.tlvs[0].u.local.info.max_pdu_size !=
sp->u.oam.content.u.info.tlvs[1].u.remote.info.max_pdu_size)
{
link_state->max_pdu_size = MIN(
sp->u.oam.content.u.info.tlvs[0].u.local.info.max_pdu_size,
sp->u.oam.content.u.info.tlvs[1].u.remote.info.max_pdu_size);
link_state->revision++;
}
else
{
bcmolt_epon_oam_local_remote_info loc_tlv = {};
fill_local_tlv(link_state, &loc_tlv);
if (0 == memcmp(&loc_tlv, &sp->u.oam.content.u.info.tlvs[1].u.remote.info, sizeof(loc_tlv)))
{
link_state->oam_state = (link_state->oam_state | BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE) &
(~BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING);
EON_LINK_LOG(DEBUG, &link_state->link_key, "setting LOCAL STABLE flag\n");
}
else
{
EON_LINK_LOG(WARNING, &link_state->link_key, "Remote TLV from ONU does NOT match what we sent\n");
}
}
}
if (sp->u.oam.flags & BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING)
{
link_state->oam_state = (link_state->oam_state | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING) &
(~BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE);
EON_LINK_LOG(DEBUG, &link_state->link_key, "setting REMOTE EVAL flag\n");
}
if (sp->u.oam.flags & BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE)
{
link_state->oam_state = (link_state->oam_state | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE) &
(~BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING);
EON_LINK_LOG(DEBUG, &link_state->link_key, "setting REMOTE STABLE flag\n");
}
if (LOCAL_STABLE_REMOTE_STABLE == link_state->oam_state)
{
rc = BCM_ERR_OK; /* basic negotiation complete */
}
if ((sp->u.oam.content.u.info.tlvs_count > 2) &&
(sp->u.oam.content.u.info.tlvs[2].type == BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC))
{
org_spec = &sp->u.oam.content.u.info.tlvs[2].u.organization_specific.value;
}
rx_org_spec_tlv[link_state->oam_set](link_state, org_spec, &rc);
EON_LINK_LOG(DEBUG, &link_state->link_key, "state %04x -> %04x (%s)\n",
start_state, link_state->oam_state, bcmos_strerror(rc));
bcmos_free(frame);
return rc;
}