BAL and Maple Release 2.2

Signed-off-by: Shad Ansari <developer@Carbon.local>
diff --git a/bcm68620_release/release/host_reference/user_appl/Makefile b/bcm68620_release/release/host_reference/user_appl/Makefile
new file mode 100644
index 0000000..37df795
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/Makefile
@@ -0,0 +1,28 @@
+ifeq ("$(ENABLE_CLI)", "y")
+	MOD_NAME = bcm_user_appl
+	MOD_TYPE = app
+	MOD_DEPS = host_api api_dev_log bcm_board bcm_user_appl_ps bcm_user_appl_eon bcm_user_appl_omon \
+                bcm_user_appl_epon_oam bcm_user_appl_epon_oam_cli pcie pcie_mod \
+                bcm_user_appl_epon_hde bcm_api_proxy common_epon_oam common_gpon bcm_image_transfer bcm_sw_upgrade \
+                device_selector bcm_remote_cli bcm_user_appl_playback bcm_user_appl_dpoe_sec
+
+ifneq ("$(RELEASE_BUILD)", "y")
+    MOD_DEPS +=	bcm_user_appl_gpon_stress bcm_user_appl_gpon_ds_omci \
+                bcm_user_appl_gpon_sn_acquisition bcm_user_appl_gpon_statistics \
+                bcm_user_appl_gpon_mac_learning bcm_user_appl_gpon_rssi \
+		bcm_user_appl_omci_swdl bcm_user_appl_remote_logger bcm_user_appl_onu_tuning
+endif
+
+	MOD_INC_DIRS = $(TOP)/host/board/wrx $(TOP)/common/drivers/maple/pcie
+	srcs = bcmolt_user_appl.c bcmolt_user_appl_cli.c bcmolt_user_appl_ex_cli.c bcmolt_user_appl_gpon_utils.c
+
+	ifeq ("$(OS_KERNEL)", "linux")
+		MOD_DEPS += dev_log_linux
+	endif
+	ifeq ($(ENABLE_KT2), y)
+		MOD_DEFS += -DENABLE_KT2
+		#EXTRA_INCLUDES += -I$(TOP_DIR)/common/cli
+	endif
+
+	MOD_DEPS += transport
+endif
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl.c b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl.c
new file mode 100644
index 0000000..279c314
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl.c
@@ -0,0 +1,520 @@
+/*
+<: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.
+
+:>
+ */
+
+#include <bcmolt_host_api.h>
+#include <bcmolt_board.h>
+#include <bcmolt_board_cli.h>
+#include <bcmolt_user_appl_cli.h>
+#include <bcmolt_user_appl_ex_cli.h>
+#include <bcmolt_user_appl_epon_oam.h>
+#if defined(LINUX_KERNEL_SPACE)
+#include <bcmolt_dev_log_linux.h>
+#endif
+#include <bcmolt_dev_selector.h>
+#include <bcm_api_cli.h>
+#include <bcmolt_api_proxy.h>
+#include <bcmolt_remote_cli.h>
+#include <bcmolt_image_transfer.h>
+
+#ifdef SIMULATION_BUILD
+    static void using_inband_set(bcmos_bool using_inband){}
+    static bcmos_bool using_inband_get(void)
+    {
+        return BCMOS_FALSE;
+    }
+    extern uint32_t bcmtr_host_ip;
+    extern uint16_t bcmtr_host_udp_port;
+    extern uint32_t bcmtr_olt_ip[BCMTR_MAX_OLTS];
+    extern uint16_t bcmtr_olt_udp_port[BCMTR_MAX_OLTS];
+#else
+    #include <bcmtr_plugin.h>
+#endif
+
+
+#define CLI_HOST_PROMPT_FORMAT "BCM.%u> "
+
+static bcmcli_session *current_session;
+
+//#if defined(SIMULATION_BUILD) && defined(LINUX_USER_SPACE)
+//extern uint32_t bcmtr_host_ip;
+//extern uint16_t bcmtr_host_udp_port;
+//extern uint32_t bcmtr_olt_ip[BCMTR_MAX_OLTS];
+//extern uint16_t bcmtr_olt_udp_port[BCMTR_MAX_OLTS];
+//#endif
+
+#ifdef ENABLE_LOG
+static int nologger=0;
+#endif
+
+static int _cmd_using_inband(bcmcli_session *sess, const bcmcli_cmd_parm parm[], uint16_t nParms)
+{
+    bcmcli_session_print(sess, "Maple management using %s\n", (using_inband_get())? "In-Band" : "PCIe");
+    return 0;
+}
+
+static int _cli_help(const char *cmd)
+{
+    const char *p;
+
+    while ((p = strchr(cmd, '/')))
+    {
+        cmd = p + 1;
+    }
+
+    fprintf(stderr,
+            "%s [-l <level>]"
+#if defined(SIMULATION_BUILD) && defined(LINUX_USER_SPACE)
+            " [-p udp_port_device1] [-p udp_port_device2] ...\n"
+            "OR\n"
+            " [-olt ip_device1:port_device1] [-olt ip_device2:port_device2] ...\n"
+            " [-listen ip:port]\n"
+#endif
+#ifdef ENABLE_LOG
+            " [-nl]\n"
+#endif
+            " [-ne]\n"
+            " [-dev]\n"
+            " [-if_mask mask]\n"
+            " [-proxy proxy_port_device1] [-proxy proxy_port_device2] ...\n"
+            " [-ud]\n"
+            " [-on_ready cmd]\n", cmd);
+    fprintf(stderr,
+            "\t\t level == guest | admin | debug\n"
+#ifdef ENABLE_LOG
+            "\t\t -nl - disable kernel logger integration\n"
+#endif
+            "\t\t -ne - disable line editing\n"
+            "\t\t -dev - device ID to auto-register for indications and run proxy on (default 0)\n"
+            "\t\t -if_mask - PON NI mask to apply to auto-registration of indications\n"
+            "\t\t -proxy - run the API proxy listening on this UDP port\n"
+            "\t\t -ud - use a unix domain connection to a user space device control"
+            "\t\t -on_ready - command for API proxy to execute after receiving device ready indication\n");
+    return -EINVAL;
+}
+
+/* quit command handler */
+static int _cmd_quit(bcmcli_session *sess, const bcmcli_cmd_parm parm[], uint16_t nParms)
+{
+    bcmcli_stop(sess);
+    bcmcli_session_print(sess, "Maple CLI terminated by 'Quit' command\n");
+    return 0;
+}
+
+#if defined(SIMULATION_BUILD) && defined(LINUX_USER_SPACE)
+/* parse ip:port */
+static bcmos_errno _parse_ip_port(const char *s, uint32_t *ip, uint16_t *port)
+{
+    int n;
+    uint32_t ip1, ip2, ip3, ip4, pp;
+
+    n = sscanf(s, "%u.%u.%u.%u:%u", &ip1, &ip2, &ip3, &ip4, &pp);
+    if (n != 5 || ip1 > 0xff || ip2 > 0xff || ip3 > 0xff || ip4 > 0xff || pp > 0xffff)
+    {
+        fprintf(stderr, "Can't parse %s. Must be ip_address:port\n", s);
+        return BCM_ERR_PARM;
+    }
+    *ip = (ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4;
+    *port = pp;
+    return BCM_ERR_OK;
+}
+#endif
+
+/* Try to learn system mode */
+static bcmolt_system_mode _get_system_mode(bcmolt_devid dev)
+{
+    bcmolt_device_key key = {};
+    bcmolt_device_cfg dev_cfg;
+    bcmos_errno rc;
+
+    BCMOLT_CFG_INIT(&dev_cfg, device, key);
+    BCMOLT_CFG_PROP_GET(&dev_cfg, device, system_mode);
+    rc = bcmolt_cfg_get(dev, &dev_cfg.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        bcmos_printf("Device %u: failed to read system mode (%s). Waiting for configuration\n",
+            (unsigned)dev, bcmos_strerror(rc));
+        return BCMOLT_SYSTEM_MODE__NUM_OF;
+    }
+    bcmos_printf("Device %u: System mode: %s\n",
+        (unsigned)dev, bcmolt_system_mode_name(dev_cfg.data.system_mode));
+    return dev_cfg.data.system_mode;
+}
+
+#ifdef ENABLE_LOG
+static int dev_log_time_to_str_cb(uint32_t time_stamp, char *time_str, int time_str_size)
+{
+    return snprintf(time_str, time_str_size, "%u", time_stamp / 1000); /* convert from usec to msec. */
+}
+
+static bcmos_errno bcm_init_logger(void)
+{
+#define DEV_LOG_SIZE1 1<<11
+#define DEV_LOG_SIZE2 1<<13
+#define DEV_LOG_QUEUE_SIZE 1000
+
+    static uint8_t logger_buf1[DEV_LOG_SIZE1];
+    static uint8_t logger_buf2[DEV_LOG_SIZE2];
+    bcmos_errno err;
+    void *addresses[DEV_LOG_MAX_FILES] = {logger_buf1, logger_buf2};
+    uint32_t sizes[DEV_LOG_MAX_FILES] = {sizeof(logger_buf1), sizeof(logger_buf2)};
+    uint32_t flags[DEV_LOG_MAX_FILES] = {BCM_DEV_LOG_FILE_FLAG_STOP_WHEN_FULL, BCM_DEV_LOG_FILE_FLAG_WRAP_AROUND};
+
+    err = bcm_dev_log_init_default_logger(addresses, sizes, flags, BCM_SIZEOFARRAY(addresses), 0x4000, TASK_PRIORITY_DEV_LOG, DEV_LOG_QUEUE_SIZE);
+    BCMOS_TRACE_CHECK_RETURN(err, err, "bcm_dev_log_initialize_dev_logger_default()\n");
+
+    bcm_dev_log_level_set_style(DEV_LOG_LEVEL_FATAL, BCM_DEV_LOG_STYLE_BOLD);
+    bcm_dev_log_level_set_style(DEV_LOG_LEVEL_ERROR, BCM_DEV_LOG_STYLE_BOLD);
+
+    bcm_dev_log_set_time_to_str_cb(dev_log_time_to_str_cb);
+
+#if defined(LINUX_KERNEL_SPACE)
+    if (!nologger)
+    {
+        err = bcm_dev_log_linux_init();
+        BCMOS_TRACE_CHECK_RETURN(err, err, "bcm_dev_log_linux_init()\n");
+    }
+#endif
+
+    BCM_LOG(INFO, def_log_id, "Hello Logger\n");
+
+    return BCM_ERR_OK;
+}
+#endif
+
+static void handle_system_mode_change(bcmolt_devid dev)
+{
+    bcmcli_entry *cur_dir = bcmcli_dir_get(current_session);
+    char old_dir_name[32] = "";
+    bcmolt_system_mode system_mode;
+
+    bcmolt_system_mode_get(dev, &system_mode);
+    bcm_common_gpon_init(system_mode);
+
+    if (dev == current_device)
+    {
+        if (cur_dir)
+            strncpy(old_dir_name, bcmcli_token_name(cur_dir), sizeof(old_dir_name) - 1);
+
+        api_cli_set_commands(current_session);
+
+        /* Restore current CLI directory */
+        cur_dir = bcmcli_dir_find(NULL, old_dir_name);
+        if (cur_dir)
+            bcmcli_dir_set(current_session, cur_dir);
+    }
+
+    bcmolt_user_appl_cli_handle_system_mode_change(dev);
+}
+
+static void cli_get_prompt(bcmcli_session *session, char *buf, uint32_t max_len)
+{
+    snprintf(buf, max_len, CLI_HOST_PROMPT_FORMAT, current_device);
+}
+
+int main(int argc, char *argv[])
+{
+    bcmcli_session_parm mon_session_parm;
+#ifndef SIMULATION_BUILD
+    uint32_t fpga_version;
+#endif
+    int noedit = 0;
+    int rc;
+    int i;
+    bcmolt_host_init_params params =
+    {
+#ifdef ENABLE_LOG
+        .logger_init_cb = bcm_init_logger,
+#endif
+        .run_dev_ctrl = BCMOS_FALSE
+    };
+    bcmolt_devid device = 0;
+    bcmos_bool specific_device = BCMOS_FALSE;
+    bcmos_bool run_proxy = BCMOS_FALSE;
+    bcmos_bool run_remote_cli = BCMOS_FALSE;
+    uint32_t proxy_port[BCMTR_MAX_OLTS] = {};
+    uint32_t remote_cli_port = 0;
+    int cur_proxy_dev = 0;
+    char *on_ready_cmd = NULL;
+    struct bcmolt_rx_cfg rx_cfg =
+    {
+        .obj_type = BCMOLT_OBJECT_ANY,
+        .flags = BCMOLT_AUTO_FLAGS_NONE,
+    };
+#if defined(SIMULATION_BUILD) && defined(LINUX_USER_SPACE)
+    int cur_olt = 0;
+#endif
+
+    (void)noedit; /* prevent warning */
+
+    memset(&mon_session_parm, 0, sizeof(mon_session_parm));
+    mon_session_parm.get_prompt = cli_get_prompt;
+    mon_session_parm.access_right = BCMCLI_ACCESS_ADMIN;
+
+    for (i = 1; i < argc; i++)
+    {
+        if (!strcmp(argv[i], "-l"))
+        {
+            ++i;
+            if (!strcmp(argv[i], "admin"))
+                mon_session_parm.access_right = BCMCLI_ACCESS_ADMIN;
+            else if (!strcmp(argv[i], "guest"))
+                mon_session_parm.access_right = BCMCLI_ACCESS_GUEST;
+            else if (!strcmp(argv[i], "debug"))
+                mon_session_parm.access_right = BCMCLI_ACCESS_DEBUG;
+            else
+                return _cli_help(argv[0]);
+        }
+        else if (!strcmp(argv[i], "-ne"))
+        {
+            noedit = 1;
+        }
+#ifdef ENABLE_LOG
+        else if (!strcmp(argv[i], "-nl"))
+        {
+            nologger = 1;
+        }
+#endif
+#if defined(SIMULATION_BUILD) && defined(LINUX_USER_SPACE)
+        else if (!strcmp(argv[i], "-p"))
+        {
+            if (cur_olt >= BCMTR_MAX_OLTS)
+            {
+                printf("Too many -p and/or -olt options\n");
+                return -1;
+            }
+            ++i;
+            bcmtr_olt_udp_port[cur_olt] = strtol(argv[i], NULL, 0);
+            ++cur_olt;
+        }
+        else if (!strcmp(argv[i], "-olt"))
+        {
+            if (cur_olt >= BCMTR_MAX_OLTS)
+            {
+                printf("Too many -p and/or -olt options\n");
+                return -1;
+            }
+            ++i;
+            if (_parse_ip_port(argv[i], &bcmtr_olt_ip[cur_olt], &bcmtr_olt_udp_port[cur_olt]) != BCM_ERR_OK)
+                return -1;
+            ++cur_olt;
+        }
+        else if (!strcmp(argv[i], "-listen"))
+        {
+            ++i;
+            if (_parse_ip_port(argv[i], &bcmtr_host_ip, &bcmtr_host_udp_port) != BCM_ERR_OK)
+                return -1;
+        }
+#endif
+        else if (!strcmp(argv[i], "-dev"))
+        {
+            ++i;
+            device = (bcmolt_devid)strtoul(argv[i], NULL, 0);
+            if (device >= BCMTR_MAX_OLTS)
+            {
+                printf("Invalid device ID\n");
+                return -1;
+            }
+            current_device = device;
+            specific_device = BCMOS_TRUE;
+        }
+        else if (!strcmp(argv[i], "-if_mask"))
+        {
+            ++i;
+            rx_cfg.pon_ni_mask = (uint32_t)strtoul(argv[i], NULL, 0);
+        }
+        else if (!strcmp(argv[i], "-proxy"))
+        {
+            if (cur_proxy_dev >= BCMTR_MAX_OLTS)
+            {
+                printf("Too many -proxy options\n");
+                return -1;
+            }
+            ++i;
+            run_proxy = BCMOS_TRUE;
+            proxy_port[cur_proxy_dev] = (uint32_t)strtoul(argv[i], NULL, 0);
+            if (proxy_port[cur_proxy_dev] > 0xffff)
+            {
+                printf("Invalid proxy port %u\n", (unsigned)proxy_port[cur_proxy_dev]);
+                return -1;
+            }
+            ++cur_proxy_dev;
+        }
+        else if (!strcmp(argv[i], "-remote"))
+        {
+            ++i;
+            run_remote_cli = BCMOS_TRUE;
+            remote_cli_port = (uint32_t)strtoul(argv[i], NULL, 0);
+        }
+        else if (!strcmp(argv[i], "-ud"))
+        {
+            using_inband_set(BCMOS_TRUE);
+        }
+        else if (!strcmp(argv[i], "-on_ready"))
+        {
+            ++i;
+            on_ready_cmd = argv[i];
+        }
+        else
+        {
+            return _cli_help(argv[0]);
+        }
+    }
+
+    rc = bcmolt_host_init(&params);
+    BUG_ON(rc);
+
+    bcmos_trace_level_set(BCMOS_TRACE_LEVEL_DEBUG);
+
+    if (noedit)
+    {
+        mon_session_parm.line_edit_mode = BCMCLI_LINE_EDIT_DISABLE;
+    }
+    if (bcmcli_session_open(&mon_session_parm, &current_session))
+    {
+        printf("Can't open CLI session\n");
+        return -EINVAL;
+    }
+
+    /* Try to learn system mode for all devices */
+    if (specific_device)
+    {
+        bcmolt_system_mode_set(current_device, _get_system_mode(current_device));
+    }
+    else
+    {
+        for (i = 0; i < BCMTR_MAX_OLTS; i++)
+            bcmolt_system_mode_set(i, _get_system_mode(i));
+    }
+
+    /* Init device selector */
+    rc = bcmolt_dev_sel_init(NULL);
+
+    /* Add Maple API commands */
+    rc = rc ? rc : api_cli_init(NULL, current_session);
+
+    /* (x)GPON init. Does nothing if system mode is not (x)GPON */
+    if (rc == BCM_ERR_OK)
+    {
+        bcmolt_system_mode system_mode = BCMOLT_SYSTEM_MODE__NUM_OF;
+        bcmolt_system_mode_get(current_device, &system_mode);
+        bcm_common_gpon_init(system_mode);
+    }
+
+    /* Add transport commands */
+    rc = rc ? rc : bcmtr_cli_init();
+
+    /* Add capture, log, debug commands */
+    rc = rc ? rc : bcmtr_cld_init(current_session);
+
+    /* Initialize embedded CLI module */
+    rc = rc ? rc : bcm_embedded_cli_init();
+
+    rc = rc ? rc : bcmos_cli_init(NULL);
+
+    rc = rc ? rc : bcmolt_user_appl_cli_init(NULL);
+
+    rc = rc ? rc : bcmolt_user_appl_ex_cli_init();
+
+
+#if defined(LINUX_KERNEL_SPACE)
+    rc = rc ? rc : bcm_board_init();
+    rc = rc ? rc : bcm_board_cli_init(NULL);
+#endif
+
+#ifdef ENABLE_LOG
+    /* logger CLI directory */
+    bcm_dev_log_cli_init(NULL);
+#endif
+
+    if (run_proxy)
+    {
+        /* If executing for specific device, run only 1 proxy instance.
+         * Otherwise, create as many instances as requested
+         */
+        if (specific_device)
+            rc = rc ? rc : bcmolt_api_proxy_init(NULL, device, proxy_port[0], on_ready_cmd);
+        else
+        {
+            for (i = 0; i < cur_proxy_dev; i++)
+            {
+                rc = rc ? rc : bcmolt_api_proxy_init(NULL, i, proxy_port[i], on_ready_cmd);
+            }
+        }
+    }
+
+    if (run_remote_cli)
+    {
+        rc = rc ? rc : bcmolt_remote_cli_init(NULL, device, remote_cli_port);
+    }
+
+    /* Add other commands, e.g., access to embedded CLI */
+    if (rc)
+        return rc;
+
+    rx_cfg.rx_cb = bcmolt_user_appl_indication_cb;
+    rc = bcmolt_auto_rx_cb_set(device, &rx_cfg);
+    BUG_ON(BCM_ERR_OK != rc);
+    rx_cfg.rx_cb = bcmolt_user_appl_proxy_rx_cb;
+    rc = bcmolt_proxy_rx_cb_set(device, &rx_cfg);
+    BUG_ON(BCM_ERR_OK != rc);
+
+    bcmolt_user_mftp_init();
+
+    sm_change_cb = handle_system_mode_change;
+
+    BCMCLI_MAKE_CMD_NOPARM(NULL, "quit", "Quit", _cmd_quit);
+    BCMCLI_MAKE_CMD_NOPARM(NULL, "using_inband", "Using In Band", _cmd_using_inband);
+
+#ifndef SIMULATION_BUILD
+    bcm_board_fpga_version_get(&fpga_version);
+    bcmcli_session_print(current_session, "FPGA version: %d\n", fpga_version);
+#endif
+
+    /* Process user input until EOF or quit command */
+    bcmcli_driver(current_session);
+
+#if defined(LINUX_KERNEL_SPACE)
+    bcm_board_uninit();
+#endif
+    bcmtr_exit();
+    bcmcli_session_close(current_session);
+    bcmcli_token_destroy(NULL);
+
+    if (run_proxy)
+    {
+        bcmolt_api_proxy_stop();
+    }
+
+    if (run_remote_cli)
+    {
+        bcmolt_remote_cli_stop();
+    }
+
+    return 0;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.c b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.c
new file mode 100644
index 0000000..8c621da
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.c
@@ -0,0 +1,568 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include <bcmolt_msg.h>
+#include <bcmolt_api.h>
+#include <bcmcli.h>
+#include <bcm_api_cli.h>
+#include <bcm_api_dev_log.h>
+#include <bcm_dev_log.h>
+#include <bcmolt_msg_pack.h>
+#include "bcmolt_user_appl_cli.h"
+#include "bcmolt_user_appl_gpon_utils.h"
+#include "bcmolt_user_appl_ps.h"
+#include "bcmolt_user_appl_ps_cli.h"
+#ifndef RELEASE_BUILD
+#include "bcmolt_user_appl_omci_swdl_cli.h"
+#include "bcmolt_user_appl_omci_swdl.h"
+#include "bcmolt_user_appl_gpon_mac_learning_cli.h"
+#include "bcmolt_user_appl_gpon_mac_learning.h"
+#include "bcmolt_user_appl_gpon_stress_cli.h"
+#include "bcmolt_user_appl_gpon_stress.h"
+#include "bcmolt_user_appl_gpon_sn_acquisition_cli.h"
+#include "bcmolt_user_appl_gpon_sn_acquisition.h"
+#include "bcmolt_user_appl_gpon_ds_omci_cli.h"
+#include "bcmolt_user_appl_gpon_ds_omci.h"
+#include "bcmolt_user_appl_gpon_statistics_cli.h"
+#include "bcmolt_user_appl_gpon_statistics.h"
+#include "bcmolt_user_appl_gpon_rssi_cli.h"
+#include "bcmolt_user_appl_gpon_rssi.h"
+#include "bcmolt_user_appl_remote_logger_cli.h"
+#include "bcmolt_user_appl_remote_logger.h"
+#include "bcmolt_user_appl_onu_tuning_cli.h"
+#include "bcmolt_user_appl_onu_tuning.h"
+#endif /* Not RELEASE_BUILD */
+#include "bcmolt_eon.h"
+#include "bcmolt_epon_hde.h"
+#ifndef SIMULATION_BUILD
+#include "omon.h"
+#include <bcmolt_dev_ctrl_ioctl.h>
+#include <bcmtr_pcie.h>
+#endif /* #endif SIMULATION_BUILD */
+#include "bcmolt_board.h"
+#include "bcmolt_api_proxy.h"
+#include "bcmolt_remote_cli.h"
+#include "bcmolt_user_appl_epon_oam_cli.h"
+#include "bcmolt_user_appl_epon_oam.h"
+#include "bcmolt_image_transfer_cli.h"
+#include "bcmolt_sw_upgrade_cli.h"
+#include "bcmolt_user_appl_playback.h"
+#include "bcmolt_user_appl_dpoe_sec.h"
+
+#ifdef ENABLE_LOG
+static uint32_t *bcmlcli_pci_dumpptr;
+static dev_log_id user_appl_log_id;
+static dev_log_id ind_log_id[BCMTR_MAX_INSTANCES];
+static bcmcli_entry *user_dir;
+static bcmos_bool sys_mode_set[BCMTR_MAX_OLTS] = {};
+
+static void user_appl_api_msg_dump(const char *title, bcmolt_msg *msg)
+{
+    uint8_t inst = bcmolt_msg_instance(msg);
+    inst = (inst > BCMTR_MAX_INSTANCES) ? 0 : inst;
+    bcmolt_msg_log(ind_log_id[inst], msg);
+}
+
+#ifndef RELEASE_BUILD
+static bcmos_errno bcmolt_user_appl_process_exception_ind(bcmolt_devid device_id, bcmolt_auto *ind)
+{
+    switch (ind->hdr.obj_type)
+    {
+    case BCMOLT_OBJ_ID_DEVICE:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_DEVICE_AUTO_CFG_ID_SW_EXCEPTION:
+        case BCMOLT_DEVICE_AUTO_CFG_ID_SW_ERROR:
+            /* Stop all user application tasks. */
+            if (bcmolt_gpon_mac_learning_appl_is_running(device_id))
+                bcmolt_gpon_mac_learning_appl_stop(device_id);
+            if (bcmolt_gpon_stress_appl_is_running(device_id))
+                bcmolt_gpon_stress_appl_stop(device_id);
+            if (bcmolt_gpon_sn_acquisition_appl_is_running(device_id))
+                bcmolt_gpon_sn_acquisition_appl_stop(device_id);
+            if (bcmolt_user_appl_gpon_statistics_is_running(device_id))
+                bcmolt_user_appl_gpon_statistics_stop(device_id);
+            if (bcmolt_gpon_ds_omci_appl_is_running(device_id))
+                bcmolt_gpon_ds_omci_appl_stop(device_id);
+            if (bcmolt_gpon_rssi_appl_is_running(device_id))
+                bcmolt_gpon_rssi_appl_stop(device_id);
+        default:
+            break;
+        }
+        break;
+    default:
+        break;
+    }
+
+    return BCM_ERR_OK;
+}
+#endif
+
+void bcmolt_user_appl_indication_cb(bcmolt_devid device_id, bcmolt_msg *msg)
+{
+    uint8_t instance  = bcmolt_msg_instance(msg);
+    bcmolt_auto *ind = (bcmolt_auto *)msg;
+    bcmolt_ps_pon ps_pon = { device_id, instance };
+
+    user_appl_api_msg_dump("Indication", msg);
+    bcmolt_ps_process_ind(&ps_pon, ind);
+
+#ifndef RELEASE_BUILD
+    bcmolt_user_appl_process_exception_ind(device_id, ind);
+    /* Forward the indication to each user application.  These applications will ignore indications that aren't
+     * relevant and errors will be printed to dedicated log IDs per application. */
+    bcmolt_gpon_mac_learning_process_ind(device_id, ind);
+    bcmolt_gpon_stress_process_ind(device_id, ind);
+    bcmolt_gpon_sn_acquisition_process_ind(device_id, ind);
+    bcmolt_user_appl_gpon_statistics_process_ind(device_id, ind);
+    bcmolt_gpon_ds_omci_process_ind(device_id, ind);
+    bcmolt_gpon_rssi_process_ind(device_id, ind);
+    bcmolt_onu_tuning_process_ind(device_id, ind);
+#endif /* Not RELEASE_BUILD */
+
+#ifndef SIMULATION_BUILD
+    bcmolt_user_appl_omon_handle_ind(device_id, instance, ind);
+#endif
+
+    bcmolt_api_proxy_auto_rx_cb(device_id, msg);
+    bcmolt_remote_cli_auto_rx_cb(device_id, msg);
+
+    /* Free the indication since we're done processing it */
+    bcmolt_msg_free(msg);
+}
+
+void bcmolt_user_appl_proxy_rx_cb(bcmolt_devid device_id, bcmolt_msg *msg)
+{
+    bcmolt_proxy_rx *proxy_rx = (bcmolt_proxy_rx *)msg;
+
+#ifndef RELEASE_BUILD
+    bcmolt_omci_swdl_process_proxy_rx(device_id, proxy_rx);
+
+    /* Dump RX packet if OMCI SWDL is Not running*/
+    if (!bcmolt_omci_swdl_appl_is_running(device_id))
+    {
+        user_appl_api_msg_dump("Proxy Rx", msg);
+    }
+#else
+    user_appl_api_msg_dump("Proxy Rx", msg);
+#endif /* Not RELEASE_BUILD */
+
+    bcmolt_api_proxy_auto_rx_cb(device_id, msg);
+    bcmolt_remote_cli_auto_rx_cb(device_id, msg);
+    bcmolt_user_appl_eon_process_rx(device_id, proxy_rx);
+    bcmolt_epon_hde_process_rx(device_id, bcmolt_msg_instance(msg), proxy_rx);
+    bcmolt_user_appl_epon_oam_handle_rx(device_id, proxy_rx, NULL);
+    bcmolt_user_appl_dpoe_sec_process_proxy_rx(device_id, proxy_rx);
+    bcmolt_msg_free(msg);
+}
+
+/* example on how to read the embedded logger from the host */
+static bcmos_errno _apicli_read_embedded_logger(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_logger_cfg  cfg;
+    bcmolt_logger_key key = {.file_id = parm[0].value.unumber, .reserved = 0};
+    int rc;
+
+    bcm_dev_log_log(
+        user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "--------------------------------------------------------------------------------------------------\n");
+    bcm_dev_log_log(
+        user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+              "| device %u logger                                                                                 |\n", current_device);
+    bcm_dev_log_log(user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "|-------------------------------------------------------------------------------------------------|\n");
+
+    while (1)
+    {
+        BCMOLT_CFG_INIT(&cfg, logger, key);
+        BCMOLT_CFG_PROP_GET(&cfg, logger, buffer);
+        rc = bcmolt_cfg_get(current_device, &cfg.hdr);
+        if (rc > 0)
+        {
+            bcmos_printf("get obj error %d\n", rc);
+        }
+        bcmos_printf("%s", cfg.data.buffer.buff);
+        if (cfg.data.buffer.msg_to_read == 0)
+        {
+            break;
+        }
+    }
+    return BCM_ERR_OK;
+}
+
+/* example on how to read all the embedded log entries from the host */
+static bcmos_errno _apicli_get_all_log_entries(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_log_entry_cfg  cfg;
+    bcmolt_log_entry_key key = {.log_id = 0, .reserved=0};
+    int rc;
+
+    char *_log_level[] =
+    {
+        "NO_LOG",
+        "FATAL",
+        "ERROR",
+        "WARNING",
+        "INFO" ,
+        "DEBUG"
+    };
+
+    char *_log_type[] =
+    {
+        "NONE",
+        "PRINT",
+        "SAVE",
+        "BOTH"
+    };
+
+    char *_log_style[] =
+    {
+        "NORMAL",
+        "BOLD",
+        "UNDERLINE",
+        "BLIK",
+        "REVERSE_VIDEO"
+    };
+
+    bcm_dev_log_log(
+        user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "--------------------------------------------------------------------------------------------------\n");
+    bcm_dev_log_log(
+        user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "| LOG ID |        LOG NAME       | LOG level print | LOG level save  | log type |    log style    |\n");
+    bcm_dev_log_log(user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "|-------------------------------------------------------------------------------------------------|\n");
+
+    while (1)
+    {
+        BCMOLT_CFG_INIT(&cfg, log_entry, key);
+        BCMOLT_CFG_PROP_GET(&cfg, log_entry, all_properties);
+        rc = bcmolt_cfg_get(current_device, &cfg.hdr);
+        if (rc)
+        {
+            break;
+        }
+        bcm_dev_log_log(user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER | BCM_LOG_FLAG_CALLER_FMT,
+            "|%-8d|%-23s|%-17s|%-17s|%-10s|%-17s|\n",
+            key.log_id,
+            cfg.data.log_name.str,
+            _log_level[cfg.data.log_level_print],
+            _log_level[cfg.data.log_level_save],
+            _log_type[cfg.data.log_type],
+            _log_style[cfg.data.log_style]);
+        bcm_dev_log_log(user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+            "%s", "|-------------------------------------------------------------------------------------------------|\n");
+        key.log_id++;
+
+    }
+    bcm_dev_log_log(user_appl_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "%s", "|-------------------------------------------------------------------------------------------------|\n");
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno _apicli_indication_handler(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    struct bcmolt_rx_cfg rx_cfg =
+    {
+        .obj_type = BCMOLT_OBJECT_ANY,
+        .rx_cb = bcmolt_user_appl_indication_cb,
+        .flags = BCMOLT_AUTO_FLAGS_NONE,
+        .pon_ni_mask = parm[0].value.unumber
+    };
+    return bcmolt_auto_rx_cb_set(current_device, &rx_cfg);
+}
+
+static bcmos_errno _apicli_proxy_rx_handler(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    struct bcmolt_rx_cfg rx_cfg =
+    {
+        .obj_type = BCMOLT_OBJECT_ANY,
+        .rx_cb = bcmolt_user_appl_proxy_rx_cb,
+        .flags = BCMOLT_AUTO_FLAGS_NONE,
+        .pon_ni_mask = parm[0].value.unumber
+    };
+    return bcmolt_proxy_rx_cb_set(current_device, &rx_cfg);
+}
+#endif
+
+#ifndef SIMULATION_BUILD
+static bcmos_errno _apicli_pcistat(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno rc;
+    uint8_t start;
+    bcm_pcied_stat *stat;
+
+    start = 0;
+    if (n_parms > 0)
+    {
+        if (!strncmp(parm[0].value.string, "yes", strlen("yes")))
+            start = 1;
+    }
+
+    rc = bcm_board_pci_debug(0, MAPLE_DEV_CTRL_IOCTL_OP_PCI_STAT, start, 0, bcmlcli_pci_dumpptr);
+    stat = (bcm_pcied_stat *)bcmlcli_pci_dumpptr;
+    BCMOS_TRACE_CHECK_RETURN(rc, rc, "bcm_board_pci_debug() failed\n");
+    bcmcli_session_print(session, "Received     : %d\n", stat->rx_counter);
+    bcmcli_session_print(session, "Send         : %d\n", stat->tx_counter);
+    bcmcli_session_print(session, "Rx Done Isr  : %d\n", stat->rx_done_isr_counter);
+    bcmcli_session_print(session, "Rx Err Isr   : %d\n", stat->rx_err_isr_counter);
+    bcmcli_session_print(session, "Tx Done Isr  : %d\n", stat->tx_done_isr_counter);
+    bcmcli_session_print(session, "Tx Err Isr   : %d\n", stat->tx_err_isr_counter);
+    bcmcli_session_print(session, "Rx empty     : %d\n", stat->rx_pcie_empty_counter);
+    bcmcli_session_print(session, "Tx full      : %d\n\n", stat->tx_pcie_full_counter);
+
+    return BCM_ERR_OK;
+}
+static bcmos_errno _apicli_pcidump(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno rc;
+    uint32_t command;
+    int32_t start, howmany;
+
+    start = -1;
+    howmany = 1;
+
+    if (n_parms > 1)
+    {
+        start = parm[1].value.number;
+        howmany = 1;
+        if (n_parms > 2)
+            howmany = parm[2].value.number;
+    }
+    if (!strncmp(parm[0].value.string, "tx", strlen("tx")))
+    {
+        command = MAPLE_DEV_CTRL_IOCTL_OP_PCI_DUMP_TX;
+    }
+    else if (!strncmp(parm[0].value.string, "rx", strlen("rx")))
+    {
+        command = MAPLE_DEV_CTRL_IOCTL_OP_PCI_DUMP_RX;
+    }
+    else
+    {
+        bcmcli_session_print(session, "Error - wrong command: %s\n", parm[0].value.string);
+        return BCM_ERR_PARM;
+    }
+
+    rc = bcm_board_pci_debug(0, command, start, howmany, bcmlcli_pci_dumpptr);
+    BCMOS_TRACE_CHECK_RETURN(rc, rc, "bcm_board_pci_debug() failed\n");
+    bcmcli_session_print(session, "%s", (char *)bcmlcli_pci_dumpptr);
+
+    return BCM_ERR_OK;
+}
+#endif
+
+static void bcmolt_user_appl_cli_start_epon(void)
+{
+    bcmolt_user_appl_epon_oam_init();
+    bcmolt_user_appl_epon_oam_cli_init(user_dir);
+
+    bcmolt_user_appl_eon_init();
+    bcmolt_user_appl_eon_cli_init(user_dir);
+
+#ifndef SIMULATION_BUILD
+    bcmolt_epon_omon_appl_init();
+    bcmolt_user_appl_epon_omon_cli_init(user_dir);
+
+    bcmolt_epon_hde_appl_init();
+    bcmolt_epon_hde_appl_cli_init(user_dir);
+#endif
+
+    bcmolt_user_appl_dpoe_sec_init();
+    bcmolt_user_appl_dpoe_sec_cli_init(user_dir);
+}
+
+static void bcmolt_user_appl_cli_start_gpon(bcmolt_devid dev)
+{
+    bcmolt_user_appl_gpon_utils_init(user_dir);
+
+#ifndef RELEASE_BUILD
+    bcmolt_gpon_mac_learning_appl_init(dev);
+    bcmolt_user_appl_gpon_mac_learning_cli_init(user_dir);
+
+    bcmolt_gpon_stress_appl_init(dev);
+    bcmolt_user_appl_gpon_stress_cli_init(user_dir);
+
+    bcmolt_gpon_sn_acquisition_appl_init(dev);
+    bcmolt_user_appl_gpon_sn_acquisition_cli_init(user_dir);
+
+    bcmolt_user_appl_gpon_statistics_init(dev);
+    bcmolt_user_appl_gpon_statistics_cli_init(user_dir);
+
+    bcmolt_gpon_ds_omci_appl_init(dev);
+    bcmolt_user_appl_gpon_ds_omci_cli_init(user_dir);
+
+    bcmolt_omci_swdl_appl_init(dev);
+    bcmolt_user_appl_omci_swdl_cli_init(user_dir);
+
+    bcmolt_gpon_rssi_appl_init(dev);
+    bcmolt_user_appl_gpon_rssi_cli_init(user_dir);
+#endif /* Not RELEASE_BUILD */
+}
+
+static void bcmolt_user_appl_cli_start_xgpon(bcmolt_devid dev)
+{
+    bcmolt_user_appl_gpon_utils_init(user_dir);
+
+#ifndef RELEASE_BUILD
+
+    bcmolt_gpon_stress_appl_init(dev);
+    bcmolt_user_appl_gpon_stress_cli_init(user_dir);
+
+    bcmolt_gpon_sn_acquisition_appl_init(dev);
+    bcmolt_user_appl_gpon_sn_acquisition_cli_init(user_dir);
+
+    bcmolt_user_appl_gpon_statistics_init(dev);
+    bcmolt_user_appl_gpon_statistics_cli_init(user_dir);
+
+    bcmolt_gpon_ds_omci_appl_init(dev);
+    bcmolt_user_appl_gpon_ds_omci_cli_init(user_dir);
+
+    bcmolt_gpon_rssi_appl_init(dev);
+    bcmolt_user_appl_gpon_rssi_cli_init(user_dir);
+
+    bcmolt_onu_tuning_appl_init(dev);
+    bcmolt_user_appl_onu_tuning_cli_init(user_dir);
+#endif /* Not RELEASE_BUILD */
+}
+
+void bcmolt_user_appl_cli_handle_system_mode_change(bcmolt_devid dev)
+{
+    bcmolt_system_mode sys_mode;
+
+    if (sys_mode_set[dev] == BCMOS_TRUE)
+    {
+        return; /* only run once */
+    }
+
+    sys_mode_set[dev] = BCMOS_TRUE;
+    (void)bcmolt_system_mode_get(dev, &sys_mode);
+    switch (sys_mode)
+    {
+    case BCMOLT_SYSTEM_MODE_EPON__16_X:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X_10_G:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X_10_G:
+    case BCMOLT_SYSTEM_MODE_EPON__2_X_10_G:
+        bcmolt_user_appl_cli_start_epon();
+        break;
+    case BCMOLT_SYSTEM_MODE_GPON__16_X:
+    case BCMOLT_SYSTEM_MODE_GPON__8_X:
+    case BCMOLT_SYSTEM_MODE_GPON__4_X:
+        bcmolt_user_appl_cli_start_gpon(dev);
+        break;
+    case BCMOLT_SYSTEM_MODE_XGPON_1__8_X:
+    case BCMOLT_SYSTEM_MODE_XGPON_1__4_X:
+    case BCMOLT_SYSTEM_MODE_XGS__2_X_10_G:
+    case BCMOLT_SYSTEM_MODE_NGPON2__2_X_10_G:
+        bcmolt_user_appl_cli_start_xgpon(dev);
+        break;
+    case BCMOLT_SYSTEM_MODE__NUM_OF: /* unconfigured */
+        sys_mode_set[dev] = BCMOS_FALSE;
+        break;
+    default:
+        /* do nothing */
+        break;
+    }
+}
+
+bcmos_errno bcmolt_user_appl_cli_init(bcmcli_entry *top_dir)
+{
+    bcmos_errno err;
+    bcmlcli_pci_dumpptr = bcmos_alloc(1024*5);
+    uint8_t inst;
+
+    if (!bcmlcli_pci_dumpptr)
+    {
+        BCMOS_CHECK_RETURN_ERROR(!bcmlcli_pci_dumpptr, BCM_ERR_NOMEM);
+        return BCM_ERR_NULL;
+    }
+#ifdef ENABLE_LOG
+    static bcmcli_enum_val enum_table_file_id[] =
+    {
+        { .name = "SRAM", .val = BCMOLT_LOG_FILE_ID_SRAM },
+        { .name = "DDR", .val = BCMOLT_LOG_FILE_ID_DDR },
+        BCMCLI_ENUM_LAST
+    };
+
+    bcmolt_msg_log_init(); /* initialize API/logger integration */
+    user_appl_log_id = bcm_dev_log_id_register("user_appl", DEV_LOG_LEVEL_DEBUG, DEV_LOG_ID_TYPE_BOTH);
+    for (inst = 0; inst < BCMTR_MAX_INSTANCES; inst++)
+    {
+        char log_name[MAX_DEV_LOG_ID_NAME];
+        sprintf(log_name, "ind_msg%u", inst);
+        ind_log_id[inst] = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_DEBUG, DEV_LOG_ID_TYPE_BOTH);
+    }
+#endif
+    user_dir = bcmcli_dir_add(top_dir, "user", "User application", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!user_dir, BCM_ERR_NOMEM);
+#ifdef ENABLE_LOG
+    BCMCLI_MAKE_CMD(user_dir, "register_indication_handler", "Register indication handler", _apicli_indication_handler,
+        BCMCLI_MAKE_PARM_DEFVAL("pon_ni_mask", "PON_NI interface bitmask. 0=all", BCMCLI_PARM_UNUMBER, 0, 0));
+    BCMCLI_MAKE_CMD(user_dir, "register_proxy_rx_handler", "Register Proxy Rx handler", _apicli_proxy_rx_handler,
+        BCMCLI_MAKE_PARM_DEFVAL("pon_ni_mask", "PON_NI interface bitmask. 0=all", BCMCLI_PARM_UNUMBER, 0, 0));
+    BCMCLI_MAKE_CMD(user_dir, "read_embedded_logger", "Read embedded logger", _apicli_read_embedded_logger,
+        BCMCLI_MAKE_PARM_ENUM("file_id", "file_id", enum_table_file_id, 0));
+
+    BCMCLI_MAKE_CMD_NOPARM(user_dir, "get_all_log_entries", "Get all embedded log entries", _apicli_get_all_log_entries);
+#endif
+
+#ifndef SIMULATION_BUILD
+    BCMCLI_MAKE_CMD(user_dir, "pcistat", "Print PCIe statistics", _apicli_pcistat,
+        BCMCLI_MAKE_PARM("clear", "clear", BCMCLI_PARM_STRING, BCMCLI_PARM_FLAG_OPTIONAL));
+    BCMCLI_MAKE_CMD(user_dir, "pcidump", "Print PCIe ring", _apicli_pcidump,
+        BCMCLI_MAKE_PARM("dir", "direction: tx or rx", BCMCLI_PARM_STRING, 0),
+        BCMCLI_MAKE_PARM("from", "start index", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM("howmany", "howmany", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_OPTIONAL));
+#endif
+
+#ifndef RELEASE_BUILD
+    bcmolt_remote_logger_appl_init();
+    bcmolt_remote_logger_appl_cli_init(user_dir);
+#endif
+
+    err = bcmolt_user_appl_cli_image_transfer_init(user_dir);
+    BCMOS_CHECK_RETURN_ERROR(err != BCM_ERR_OK, err);
+
+    err = bcmolt_user_appl_cli_sw_upgrade_init(user_dir);
+    BCMOS_CHECK_RETURN_ERROR(err != BCM_ERR_OK, err);
+
+    bcmolt_ps_appl_init();
+    err = bcmolt_user_appl_ps_cli_init(user_dir);
+    BCMOS_CHECK_RETURN_ERROR(err != BCM_ERR_OK, err);
+
+    bcmolt_user_appl_playback_init();
+    bcmolt_user_appl_playback_cli_init(user_dir);
+
+    bcmolt_user_appl_cli_handle_system_mode_change(current_device);
+
+    return err;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.h b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.h
new file mode 100644
index 0000000..43a105e
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_cli.h
@@ -0,0 +1,45 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_CLI_H_
+#define _BCMOLT_USER_APPL_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_msg.h>
+
+void bcmolt_user_appl_indication_cb(bcmolt_devid dev, bcmolt_msg *msg);
+
+void bcmolt_user_appl_proxy_rx_cb(bcmolt_devid dev, bcmolt_msg *msg);
+
+void bcmolt_user_appl_cli_handle_system_mode_change(bcmolt_devid dev);
+
+bcmos_errno bcmolt_user_appl_cli_init(bcmcli_entry *top_dir);
+
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.c b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.c
new file mode 100644
index 0000000..b6ac016
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.c
@@ -0,0 +1,771 @@
+/*
+<: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.
+
+:>
+ */
+
+#include <bcmolt_msg.h>
+#include <bcmolt_api.h>
+#include <bcmcli.h>
+#include <bcm_api_cli.h>
+#include <bcm_api_cli_helpers.h>
+#include <bcm_dev_log.h>
+#include "bcmolt_user_appl_ex_cli.h"
+
+#ifdef ENABLE_LOG
+static dev_log_id user_appl_ex_log_id;
+
+static void user_appl_general_indication_cb(bcmolt_devid olt, bcmolt_msg *msg)
+{
+    bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "[-- Dev %u: Indication Received --]\n", olt);
+    switch (msg->obj_type)
+    {
+        case BCMOLT_OBJ_ID_DEBUG:
+            break;
+        case BCMOLT_OBJ_ID_GPON_NI:
+            switch (msg->subgroup)
+            {
+                case BCMOLT_GPON_NI_AUTO_ID_LOS:
+                    break;
+                case BCMOLT_GPON_NI_AUTO_ID_STATE_CHANGE_COMPLETED:
+                {
+                    bcmolt_gpon_ni_state_change_completed *ind = (bcmolt_gpon_ni_state_change_completed*)msg;
+                    bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+                        "Dev %u: pon ni state changed new_state %d, prev state %d, result %d \n",
+                        olt, ind->data.new_state, ind->data.previous_state, ind->data.result);
+                    break;
+               }
+
+                default:
+                    break;
+            /* ... */
+           }
+            break;
+        case BCMOLT_OBJ_ID_GPON_ONU:
+            break;
+        default:
+            break;
+        /* ... */
+    }
+    bcmolt_msg_free(msg);
+}
+
+static void user_appl_pon_ni_indication_cb(bcmolt_devid olt, bcmolt_msg *msg)
+{
+    bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "[-- Dev %u: Pon NI Indication Received --]\n", olt);
+    switch (msg->obj_type)
+    {
+        case BCMOLT_OBJ_ID_GPON_NI:
+            switch (msg->subgroup)
+            {
+                case BCMOLT_GPON_NI_AUTO_ID_LOS:
+                    break;
+                case BCMOLT_GPON_NI_AUTO_ID_STATE_CHANGE_COMPLETED:
+                {
+                    bcmolt_gpon_ni_state_change_completed *ind = (bcmolt_gpon_ni_state_change_completed*)msg;
+                    bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+                        "Dev %u: pon ni state changed new_state %d, prev state %d, result %d \n",
+                        olt, ind->data.new_state, ind->data.previous_state, ind->data.result);
+                    break;
+               }
+                default:
+                    break;
+           }
+            break;
+        default:
+            bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+                "[-- Dev %u: some other indication  --]\n", olt);
+            break;
+        /* ... */
+    }
+    bcmolt_msg_free(msg);
+}
+
+static void user_appl_device_logger_indication_cb(bcmolt_devid olt, bcmolt_msg *msg)
+{
+    if (msg->subgroup == BCMOLT_DEBUG_AUTO_ID_CLI_OUTPUT)
+    {
+        bcmolt_debug_cli_output *ind = (bcmolt_debug_cli_output *)msg;
+        bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+            "Dev %u: [device logger] %s\n", olt, ind->data.data.val);
+   }
+}
+
+static bcmos_errno user_appl_indication_handler(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_rx_cfg rx_cfg;
+    bcmos_errno rc;
+
+    rx_cfg.obj_type = BCMOLT_OBJ_ID_DEBUG;
+    rx_cfg.rx_cb =user_appl_device_logger_indication_cb;
+    rx_cfg.flags = BCMOLT_AUTO_FLAGS_NONE;
+    rc = bcmolt_auto_rx_cb_set(current_device, &rx_cfg);
+    if (rc != BCM_ERR_OK)
+    {
+        return rc;
+    }
+
+    rx_cfg.obj_type = BCMOLT_OBJ_ID_GPON_NI;
+    rx_cfg.rx_cb = user_appl_pon_ni_indication_cb;
+    rx_cfg.flags = BCMOLT_AUTO_FLAGS_NONE;
+    rc = bcmolt_auto_rx_cb_set(current_device, &rx_cfg);
+    if (rc != BCM_ERR_OK)
+    {
+        return rc;
+    }
+
+    rx_cfg.obj_type = BCMOLT_OBJECT_ANY;
+    rx_cfg.rx_cb = user_appl_general_indication_cb;
+    rx_cfg.flags = BCMOLT_AUTO_FLAGS_NONE;
+    return bcmolt_auto_rx_cb_set(current_device, &rx_cfg);
+}
+
+static bcmos_errno user_appl_log_entry_set(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmos_errno err;
+    bcmolt_log_entry_cfg cfg; /**< declare main API struct */
+    bcmolt_log_entry_key key = {.log_id= 0}; /**< declare key */
+
+    /* init the API struct */
+    BCMOLT_CFG_INIT(&cfg, log_entry, key);
+
+    BCMOLT_CFG_PROP_SET(&cfg, log_entry, log_level_print, BCMOLT_LOG_LEVEL_INFO);
+    BCMOLT_CFG_PROP_SET(&cfg, log_entry, log_level_save, BCMOLT_LOG_LEVEL_DEBUG);
+    /* call API */
+    err = bcmolt_cfg_set(0, &cfg.hdr);
+    return err;
+}
+
+static bcmos_errno user_appl_gpon_ni_disable_indication (bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmos_errno err;
+    bcmolt_gpon_ni_auto_cfg cfg; /**< declare main API struct */
+    bcmolt_gpon_ni_key key = {.pon_ni= 0}; /**< declare key */
+
+    /* init the API struct */
+    BCMOLT_AUTO_CFG_INIT(&cfg, gpon_ni, key);
+
+    BCMOLT_AUTO_CFG_PROP_SET(&cfg, gpon_ni, serial_number_acquisition_cycle_start, BCMOS_FALSE);
+    BCMOLT_AUTO_CFG_PROP_SET(&cfg, gpon_ni, stat_alarm_cleared, BCMOS_FALSE);
+    /* call API */
+    err = bcmolt_auto_cfg_set(0, &cfg.hdr);
+    return err;
+}
+
+static bcmos_errno user_appl_redirect_device_logger (bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_debug_cfg cfg;
+    bcmolt_debug_key key = {};
+
+    BCMOLT_CFG_INIT(&cfg, debug, key);
+    BCMOLT_CFG_PROP_SET(&cfg, debug, console_redirection, BCMOLT_CONSOLE_REDIRECTION_CLONE);
+    bcmolt_cfg_set(0, &cfg.hdr);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno user_appl_gpon_device_init(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_device_cfg cfg;
+    bcmos_errno err;
+    bcmolt_device_key key = {.reserved = 0};
+    bcmolt_device_nni_speed nni_speed = {}; /**< Device network interface speed configuration */
+    bcmolt_device_connect oper;
+
+    nni_speed.first_half = BCMOLT_NNI_SPEED_GBPS_1;
+    nni_speed.second_half = BCMOLT_NNI_SPEED_GBPS_1;
+
+    BCMOLT_CFG_INIT(&cfg, device, key);
+    BCMOLT_CFG_PROP_SET(&cfg, device, system_mode, BCMOLT_SYSTEM_MODE_GPON__16_X);
+    BCMOLT_CFG_PROP_SET(&cfg, device, keepalive_interval, 5);
+    BCMOLT_CFG_PROP_SET(&cfg, device, keepalive_tolerance, 5);
+    BCMOLT_CFG_PROP_SET(&cfg, device, nni_speed, nni_speed);
+    err = bcmolt_cfg_set(0, &cfg.hdr);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    BCMOLT_OPER_INIT(&oper, device, connect, key);
+    err = bcmolt_oper_submit(0, &oper.hdr);
+    return err;
+}
+
+static bcmos_errno user_appl_gpon_set_trx(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_trx_cfg cfg;
+    bcmolt_gpon_trx_key key = {.pon_ni = 0};
+
+    BCMOLT_CFG_INIT(&cfg, gpon_trx, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_trx, transceiver_type, BCMOLT_TRX_TYPE_SOURCE_PHOTONICS);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_set_interworking_mode(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_iwf_cfg cfg;
+    bcmolt_gpon_iwf_key key = {.pon_ni = 0};
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf, iwf_mode, BCMOLT_IWF_MODE_PER_FLOW_MODE);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+
+}
+
+static bcmos_errno user_appl_gpon_set_mac_table_config(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_iwf_cfg cfg_iwf;
+    bcmolt_gpon_iwf_key key_iwf = {.pon_ni = 0};
+    bcmolt_mac_table_configuration mac_table_configuration;
+    mac_table_configuration.aging_time = 10000;
+    mac_table_configuration.automatic_mac_aging = BCMOLT_CONTROL_STATE_DISABLE;
+    mac_table_configuration.automatic_mac_learning = BCMOLT_CONTROL_STATE_DISABLE;
+    mac_table_configuration.automatic_mac_move = BCMOLT_CONTROL_STATE_DISABLE;
+    mac_table_configuration.automatic_static_mode = BCMOLT_CONTROL_STATE_DISABLE;
+    mac_table_configuration.default_flow_id = 600;
+    mac_table_configuration.learning_mode = BCMOLT_MAC_TABLE_LEARNING_MODE_NORMAL;
+    mac_table_configuration.miss_fallback = BCMOLT_MAC_TABLE_MISS_FALLBACK_DEFAULT_FLOW;
+
+    BCMOLT_CFG_INIT(&cfg_iwf, gpon_iwf, key_iwf);
+    BCMOLT_CFG_PROP_SET(&cfg_iwf, gpon_iwf, mac_table_configuration, mac_table_configuration);
+    /* must set the IWF to per fow for mac table mapping to work */
+    BCMOLT_CFG_PROP_SET(&cfg_iwf, gpon_iwf, iwf_mode, BCMOLT_IWF_MODE_PER_FLOW_MODE);
+    return bcmolt_cfg_set(0, &cfg_iwf.hdr);
+}
+
+static bcmos_errno user_appl_gpon_activate_pon(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_ni_set_pon_state oper_ni;
+    bcmolt_gpon_ni_key key_ni = {.pon_ni = 0};
+
+    BCMOLT_OPER_INIT(&oper_ni, gpon_ni, set_pon_state, key_ni);
+    BCMOLT_OPER_PROP_SET(&oper_ni, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_WORKING);
+    return bcmolt_oper_submit(0, &oper_ni.hdr);
+}
+
+static bcmos_errno user_appl_gpon_start_sn_acquisition(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_ni_cfg cfg_ni;
+    bcmolt_gpon_ni_key key_ni = {.pon_ni = 0};
+    bcmolt_gpon_sn_acquisition sn_acquisition;
+
+    sn_acquisition.control = BCMOLT_CONTROL_STATE_ENABLE;
+    sn_acquisition.interval = 77000;
+    sn_acquisition.onu_post_discovery_mode = BCMOLT_ONU_POST_DISCOVERY_MODE_DISABLE;
+
+    BCMOLT_CFG_INIT(&cfg_ni, gpon_ni, key_ni);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, sn_acquisition, sn_acquisition);
+    return bcmolt_cfg_set(0, &cfg_ni.hdr);
+}
+
+static bcmos_errno user_appl_gpon_ni_get_stats(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_ni_stat stat; /**< declare main API struct */
+    bcmolt_gpon_ni_key key = {}; /**< declare key */
+
+    /* init the API struct */
+    BCMOLT_STAT_INIT(&stat, gpon_ni, key);
+    BCMOLT_STAT_PROP_GET(&stat, gpon_ni, all_properties);
+    return bcmolt_stat_get(0, &stat.hdr, BCMOS_TRUE);
+}
+
+static bcmos_errno user_appl_gpon_onu_config(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_key key = {.pon_ni = 0, .onu_id = 2};
+    bcmolt_serial_number serial_number = {.vendor_id = {0x00,0x00,0x00, 0x00}, .vendor_specific = {0x00,0x00,0x00, 0x02}};
+    bcmolt_arr_u8_10 password = {.arr = {0x00,0x00,0x00, 0x00, 0x00,0x00,0x00, 0x00, 0x00, 0x00}};
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, serial_number, serial_number);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, password, password);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, auto_password_learning, BCMOS_TRUE);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, us_fec, BCMOS_FALSE);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, omci_port_id, 2);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, ds_ber_reporting_interval, 5000);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_activate_onu(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_onu_set_onu_state oper;
+    bcmolt_gpon_onu_key key = {.pon_ni = 0, .onu_id = 2};
+
+    BCMOLT_OPER_INIT(&oper, gpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ACTIVE);
+    return bcmolt_oper_submit(0, &oper.hdr);
+}
+
+static bcmos_errno user_appl_gpon_alloc_id_config(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_alloc_cfg cfg;
+    bcmolt_gpon_alloc_key key = {.pon_ni = 0, .alloc_id = 320};
+
+    bcmolt_pon_alloc_sla sla; /**< Alloc ID SLA. */
+
+    sla.additional_bw_eligibility = BCMOLT_ADDITIONAL_BW_ELIGIBILITY_NON_ASSURED;
+    sla.cbr_rt_bw = 0;
+    sla.cbr_nrt_bw = 140000000;
+    sla.guaranteed_bw = 140000000;
+    sla.maximum_bw = 140000000;
+    sla.alloc_type = BCMOLT_ALLOC_TYPE_NSR;
+    sla.cbr_rt_compensation = BCMOS_FALSE;
+    sla.cbr_nrt_ap_index = 0;
+    sla.cbr_rt_ap_index = 0;
+    sla.weight = 0;
+    sla.priority = 0;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_alloc, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_alloc, onu_id, 2);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_alloc, sla, sla);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+/* Configure a GEM - for DS or bidirectional traffic */
+static bcmos_errno user_appl_gpon_gem_port_config(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_gem_port_cfg cfg;
+    bcmolt_gpon_gem_port_key key = {.pon_ni = 0, .gem_port_id = 320};
+
+    bcmolt_gem_port_configuration configuration = {.direction = BCMOLT_GEM_PORT_DIRECTION_BIDIRECTIONAL, .type = BCMOLT_GEM_PORT_TYPE_UNICAST};
+    BCMOLT_CFG_INIT(&cfg, gpon_gem_port, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_gem_port, onu_id, 2);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_gem_port, downstream_encryption_mode, BCMOLT_CONTROL_STATE_DISABLE);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_gem_port, configuration, configuration);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_gem_port, upstream_destination_queue, BCMOLT_US_GEM_PORT_DESTINATION_DATA);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_gem_port, control, BCMOLT_CONTROL_STATE_ENABLE);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_ds_gem_port_modify(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    /* this can only be called in PER flow mode */
+    /* set DS port mapping initial GEM 300, final GEM 320 */
+    bcmolt_gpon_iwf_ds_egress_flow_key key = {.pon_ni = 0, .flow_id = 300};
+    bcmolt_gpon_iwf_ds_egress_flow_cfg cfg;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_ds_egress_flow, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_ds_egress_flow, gem_port, 320);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_set_ds_per_vlan_mapping_method(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    /* When working with per flow mode the user must add an entry for each VID (flow) this way */
+    bcmolt_gpon_iwf_ds_ingress_flow_key key = {.pon_ni = 0, .vlan_id = 300};
+    bcmolt_gpon_iwf_ds_ingress_flow_cfg cfg;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_ds_ingress_flow, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_ds_ingress_flow, mapping_method, BCMOLT_VLAN_TO_FLOW_MAPPING_METHOD_VID);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_ds_ingress_flow, mapping_tag, BCMOLT_MAPPING_TAG_METHOD_OUTER_VID);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_ds_ingress_flow, vlan_action, BCMOLT_DS_VLAN_ACTION_TRANSPARENT);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_send_omci_packet(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_onu_cpu_packets proxy; /**< declare main API struct */
+    bcmolt_gpon_onu_key key = {.pon_ni = 0, .onu_id = 2}; /**< declare key */
+    bcmolt_u8_list_u32_max_2048 buffer;
+    uint8_t cpu_buf[100];
+    buffer.len = 48;
+    buffer.val = cpu_buf;
+
+    /* init the API struct */
+    BCMOLT_PROXY_INIT(&proxy, gpon_onu, cpu_packets, key);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, packet_type, BCMOLT_PACKET_TYPE_OMCI);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, calc_crc, BCMOS_TRUE);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, number_of_packets, 1);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, packet_size, 48);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, buffer, buffer);
+
+    /* call API */
+    return bcmolt_proxy_send(0, &proxy.hdr);
+}
+
+static bcmos_errno user_appl_gpon_send_cpu_packets_over_gem_ports(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_ni_cpu_packets proxy; /**< declare main API struct */
+    bcmolt_gpon_ni_key key = {.pon_ni = 0}; /**< declare key */
+    bcmolt_gpon_gem_id gem_port_id_array[10];
+    bcmolt_gpon_gem_id_list_u8_max_16 gem_port_list;
+    bcmolt_u8_list_u32_max_2048 buffer;
+    uint8_t cpu_buf[100];
+    buffer.len = 96;
+    buffer.val = cpu_buf;
+
+    gem_port_list.len = 10;
+    gem_port_list.val = gem_port_id_array;
+
+    /* init the API struct */
+    BCMOLT_PROXY_INIT(&proxy, gpon_ni, cpu_packets, key);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, packet_type, BCMOLT_PACKET_TYPE_CPU);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, calc_crc, BCMOS_TRUE);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, gem_port_list, gem_port_list);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, buffer, buffer);
+
+    /* call API */
+    return bcmolt_proxy_send(0, &proxy.hdr);
+}
+
+static bcmos_errno user_appl_gpon_add_mac_entry(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    /* this can only be called in per flow mode. */
+    /* In order to pass traffic you must also add an entry to IWF ingress port as described in _gpon_set_per_vlan_mapping_method */
+    bcmolt_gpon_iwf_mac_table_cfg cfg;
+    bcmolt_gpon_iwf_mac_table_key key = {.pon_ni = 0, .mac_address.u8 = {0x00,0x00,0x00, 0x00, 0x00,0x00}, .vlan = 320};
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_mac_table, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_mac_table, flow_id, 320);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_dump_mac_table(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    const uint16_t max_msgs_per_call = 10;
+    const bcmolt_devid device = 0;
+    const uint16_t pon = 0;
+
+    bcmos_errno err;
+    bcmolt_msg_set *msg_set;
+    bcmolt_gpon_iwf_mac_table_cfg filter;
+    uint16_t i;
+    uint16_t count = 0;
+
+    /* a key of all Fs means "start from the beginning" */
+    bcmolt_gpon_iwf_mac_table_key wildcard_key =
+        { .pon_ni = pon, .mac_address.u8 = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, .vlan = 0xFFFF };
+
+    /* allocate space for the return values */
+    err = bcmolt_msg_set_alloc(BCMOLT_OBJ_ID_GPON_IWF_MAC_TABLE, BCMOLT_MGT_GROUP_CFG, max_msgs_per_call, &msg_set);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    /* initialize the filter structure and ask to read the flow ID / static flag */
+    BCMOLT_CFG_INIT(&filter, gpon_iwf_mac_table, wildcard_key);
+    BCMOLT_MSGSET_CFG_PROP_GET(msg_set, gpon_iwf_mac_table, flow_id);
+    BCMOLT_MSGSET_CFG_PROP_GET(msg_set, gpon_iwf_mac_table, stat);
+
+    /* filter to only include non-static entries (you could also filter on flow ID, or omit this to include all) */
+    BCMOLT_CFG_PROP_SET(&filter, gpon_iwf_mac_table, stat, BCMOS_FALSE);
+
+    do
+    {
+        /* call get multiple objects API function */
+        err = bcmolt_cfg_get_multi(device, &filter.hdr, BCMOLT_FILTER_FLAGS_NONE, msg_set);
+        if (err != BCM_ERR_OK)
+        {
+            bcmcli_session_print(session, "bcmolt_cfg_get_multi returned error: %s (%d)\n", bcmos_strerror(err), err);
+            break;
+        }
+
+        /* print each key/config structure that was received */
+        for (i = 0; i < msg_set->num_instances; i++)
+        {
+            bcmolt_gpon_iwf_mac_table_cfg *cfg = (bcmolt_gpon_iwf_mac_table_cfg *)msg_set->msg[i];
+            bcmcli_session_print(
+                session,
+                "entry[%d] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+                count,
+                cfg->key.mac_address.u8[0],
+                cfg->key.mac_address.u8[1],
+                cfg->key.mac_address.u8[2],
+                cfg->key.mac_address.u8[3],
+                cfg->key.mac_address.u8[4],
+                cfg->key.mac_address.u8[5]);
+            bcmcli_session_print(session, "entry[%d] VID: %d\n", count, cfg->key.vlan);
+            bcmcli_session_print(session, "entry[%d] flow ID: %d\n", count, cfg->data.flow_id);
+            bcmcli_session_print(session, "entry[%d] static: %s\n", count, cfg->data.stat ? "yes" : "no");
+            count++;
+        }
+
+        /* update the key for next call */
+        filter.key = *((bcmolt_gpon_iwf_mac_table_key *)msg_set->next_key);
+
+    /* keep calling the function until we have retrieved all entries */
+    } while (msg_set->more);
+
+    bcmolt_msg_set_free(msg_set);
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno user_appl_gpon_set_us_flow_configuration(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_gpon_iwf_us_flow_cfg cfg;
+    bcmolt_gpon_iwf_us_flow_key key = {.pon_ni = 0, .gem_port_id = 320};
+    bcmolt_vlan_tag vlan_tag;
+
+    /* vlad tag parameters for the add vlan */
+    vlan_tag.pbit = 9;
+    vlan_tag.vlan_id = 300;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_us_flow, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_us_flow, flow_id, 320);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_us_flow, mac_learning, BCMOS_FALSE);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_us_flow, vlan_action, BCMOLT_US_VLAN_ACTION_ADD);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_us_flow, vlan_tag, vlan_tag);
+    /* what is the tpid index */
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_us_flow, tpid_index, 0);
+    return bcmolt_cfg_set(0, &cfg.hdr);
+}
+
+static bcmos_errno user_appl_gpon_configure_and_activate_pon(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    /* TRX and PON Link configuration structures + PON control operation */
+    bcmos_errno rc;
+    bcmolt_gpon_ni_key key_ni = {.pon_ni = 0};
+    bcmolt_gpon_ni_cfg cfg_ni;
+    bcmolt_gpon_ni_set_pon_state oper_ni;
+
+    bcmolt_gpon_sn_acquisition sn_acquisition = {.control = BCMOLT_CONTROL_STATE_ENABLE, .interval = 10000, .onu_post_discovery_mode = BCMOLT_ONU_POST_DISCOVERY_MODE_NONE};
+    bcmolt_pon_drift_control drift_control = {.drift_interval = 1000, .drift_limit = 4, .transmission_control_limit = 8};
+    bcmolt_ber_monitor_params ber_monitor = {.us_ber_interval = 1000, .sd_threshold = 5, .sf_threshold = 3};
+    bcmolt_gpon_trx_key key_trx = {.pon_ni = 0};
+    bcmolt_gpon_trx_cfg cfg_trx;
+    bcmolt_trx_delimiter delimiter = {};
+
+    /* overide only the relevant parameters */
+    delimiter.pattern[0] = 0x0B;
+    delimiter.pattern[1] = 0x59;
+    delimiter.pattern[2] = 0x83;
+    delimiter.size = 3;
+    delimiter.window_size = 124;
+
+    BCMOLT_CFG_INIT(&cfg_trx, gpon_trx, key_trx);
+    BCMOLT_CFG_PROP_SET(&cfg_trx, gpon_trx, transceiver_type, BCMOLT_TRX_TYPE_SOURCE_PHOTONICS);
+    BCMOLT_CFG_PROP_SET(&cfg_trx, gpon_trx, delimiter, delimiter);
+    rc = bcmolt_cfg_set(0, &cfg_trx.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        bcm_dev_log_log(user_appl_ex_log_id, DEV_LOG_LEVEL_DEBUG, BCM_LOG_FLAG_NO_HEADER,
+        "Error in set trx %d", rc);
+    }
+
+    /* Initialize and configure PON */
+    BCMOLT_CFG_INIT(&cfg_ni, gpon_ni, key_ni);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, sn_acquisition, sn_acquisition);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, drift_control, drift_control);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, ber_monitor, ber_monitor);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, ds_ber_reporting_interval, 1000);
+    BCMOLT_CFG_PROP_SET(&cfg_ni, gpon_ni, preassigned_equalization_delay, 0);
+    rc = bcmolt_cfg_set(0, &cfg_ni.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        return rc;
+    }
+
+    BCMOLT_OPER_INIT(&oper_ni, gpon_ni, set_pon_state, key_ni);
+    BCMOLT_OPER_PROP_SET(&oper_ni, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_WORKING);
+    return bcmolt_oper_submit(0, &oper_ni.hdr);
+}
+
+static bcmos_errno gpon_add_activate_onu (bcmolt_devid dev, bcmolt_gpon_ni pon_ni, bcmolt_gpon_onu_id onu_id, bcmolt_serial_number serial_number)
+{
+    bcmolt_gpon_onu_cfg onu_cfg;
+    bcmolt_gpon_onu_key onu_key = {.pon_ni = pon_ni, .onu_id = onu_id};
+    bcmolt_gpon_onu_set_onu_state onu_oper;
+    bcmolt_arr_u8_10 password = {.arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
+    bcmolt_gpon_gem_port_cfg gem_cfg;
+    bcmolt_gpon_gem_port_key gem_key = {.pon_ni = pon_ni, .gem_port_id = onu_id};
+    bcmos_errno rc;
+
+    BCMOLT_CFG_INIT(&onu_cfg, gpon_onu, onu_key);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, serial_number, serial_number);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, password, password);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, auto_password_learning, BCMOS_TRUE);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, us_fec, BCMOS_TRUE);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, omci_port_id, 2);
+    BCMOLT_CFG_PROP_SET(&onu_cfg, gpon_onu, ds_ber_reporting_interval, 5000);
+    rc = bcmolt_cfg_set(dev, &onu_cfg.hdr);
+    if (rc)
+    {
+        return rc;
+    }
+
+    BCMOLT_CFG_INIT(&gem_cfg, gpon_gem_port, gem_key);
+    BCMOLT_CFG_PROP_SET(&gem_cfg, gpon_gem_port, downstream_encryption_mode, BCMOLT_CONTROL_STATE_ENABLE);
+    rc = bcmolt_cfg_set(dev, &gem_cfg.hdr);
+    if (rc)
+    {
+        return rc;
+    }
+
+    BCMOLT_OPER_INIT(&onu_oper, gpon_onu, set_onu_state, onu_key);
+    BCMOLT_OPER_PROP_SET(&onu_oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ACTIVE);
+    rc = bcmolt_oper_submit(dev, &onu_oper.hdr);
+    return rc;
+}
+
+static bcmos_errno user_appl_gpon_config_and_activate_onu (bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t nparms)
+{
+    bcmolt_serial_number serial_number = {.vendor_id = {0x00,0x00,0x00, 0x00}, .vendor_specific = {0x00,0x00,0x00, 0x02}};
+    return gpon_add_activate_onu(0, 0, 2, serial_number);
+}
+
+static bcmos_errno user_appl_epon_get_all_link_alarms(bcmcli_session *session,
+                                                      const bcmcli_cmd_parm parm[],
+                                                      uint16_t nparms)
+{
+    const uint16_t max_msgs_per_call = 10;
+    const bcmolt_devid device_id = 0;
+    const uint16_t pon = 0;
+    /* configure filter to select only links which do NOT have all alarms off */
+    const bcmolt_filter_flags flags = BCMOLT_FILTER_FLAGS_INVERT_SELECTION;
+    const bcmolt_epon_link_alarm_state alarm_state_filter =
+        {
+            .invalid_mpcp_report_received = BCMOLT_STATUS_OFF,
+            .key_exchange_failure = BCMOLT_STATUS_OFF,
+            .mpcp_report_timeout = BCMOLT_STATUS_OFF,
+            .oam_keepalive_timeout = BCMOLT_STATUS_OFF,
+        };
+
+    bcmos_errno err = BCM_ERR_OK;
+    bcmolt_msg_set *msg_set;
+    bcmolt_epon_link_cfg filter;
+    bcmolt_epon_link_key wildcard_key;
+    uint16_t i;
+
+    /* retrieve the EPON NI MAC address */
+    bcmolt_epon_ni_cfg pon_cfg;
+    bcmolt_epon_ni_key pon_key = { .epon_ni = pon };
+
+    BCMOLT_CFG_INIT(&pon_cfg, epon_ni, pon_key);
+    BCMOLT_CFG_PROP_GET(&pon_cfg, epon_ni, mac_address);
+    err = bcmolt_cfg_get(device_id, &pon_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        bcmcli_session_print(session, "Failed to retrieve EPON NI MAC address!\n");
+        return err;
+    }
+
+    /* a key with the EPON NI MAC address means "start from the beginning" */
+    wildcard_key.epon_ni = pon;
+    wildcard_key.mac_address = pon_cfg.data.mac_address;
+
+    /* allocate space for the return values */
+    err = bcmolt_msg_set_alloc(BCMOLT_OBJ_ID_EPON_LINK, BCMOLT_MGT_GROUP_CFG, max_msgs_per_call, &msg_set);
+    if (BCM_ERR_OK != err)
+    {
+        bcmcli_session_print(session, "Failed to allocate space for return!\n");
+        return err;
+    }
+
+    /* initialize the filter structure and ask to read the alarm state */
+    BCMOLT_CFG_INIT(&filter, epon_link, wildcard_key);
+    BCMOLT_MSGSET_CFG_PROP_GET(msg_set, epon_link, alarm_state);
+
+    /* apply filter */
+    BCMOLT_CFG_PROP_SET(&filter, epon_link, alarm_state, alarm_state_filter);
+
+    do
+    {
+        /* call get multiple objects API function */
+        err = bcmolt_cfg_get_multi(device_id, &filter.hdr, flags, msg_set);
+        if (BCM_ERR_OK != err)
+        {
+            bcmcli_session_print(session, "bcmolt_cfg_get_multi returned error: %s (%d)\n", bcmos_strerror(err), err);
+            break;
+        }
+
+        /* print each key/config that was received */
+        for (i = 0; i < msg_set->num_instances; i++)
+        {
+            bcmolt_epon_link_cfg *cfg = (bcmolt_epon_link_cfg*)msg_set->msg[i];
+            bcmcli_session_print(session, "Link: NI %u, MAC %02x%02x%02x%02x%02x%02x\n",
+                                 cfg->key.epon_ni,
+                                 cfg->key.mac_address.u8[0],
+                                 cfg->key.mac_address.u8[1],
+                                 cfg->key.mac_address.u8[2],
+                                 cfg->key.mac_address.u8[3],
+                                 cfg->key.mac_address.u8[4],
+                                 cfg->key.mac_address.u8[5]);
+            bcmcli_session_print(session, "\tInvalid MPCP Report Received: %u\n",
+                                 cfg->data.alarm_state.invalid_mpcp_report_received);
+            bcmcli_session_print(session, "\tKey Exchange failure: %u\n",
+                                 cfg->data.alarm_state.key_exchange_failure);
+            bcmcli_session_print(session, "\tMPCP Report Timeout: %u\n",
+                                 cfg->data.alarm_state.mpcp_report_timeout);
+            bcmcli_session_print(session, "\tOAM Keepalive Timeout: %u\n",
+                                 cfg->data.alarm_state.oam_keepalive_timeout);
+        }
+
+        /* update the key for next call */
+        filter.key = *((bcmolt_epon_link_key*)msg_set->next_key);
+
+        /* keep calling the function until we have retrieved all entries */
+    } while (msg_set->more);
+
+    bcmolt_msg_set_free(msg_set);
+    return err;
+}
+#endif
+
+bcmos_errno bcmolt_user_appl_ex_cli_init(void)
+{
+    bcmcli_entry *parent = bcmcli_dir_find(NULL, "user");
+    bcmcli_entry *dir;
+
+#ifdef ENABLE_LOG
+    user_appl_ex_log_id = bcm_dev_log_id_register("user_appl_ex", DEV_LOG_LEVEL_DEBUG, DEV_LOG_ID_TYPE_BOTH);
+#endif
+
+    dir = bcmcli_dir_add(parent, "example", "User application", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+#ifdef ENABLE_LOG
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_device_init", "gpon device init", user_appl_gpon_device_init);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "indication_handler", "indication handler", user_appl_indication_handler);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "log_entry_set", "log enrty set", user_appl_log_entry_set);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "redirect_device_logger", "redirect device logger", user_appl_redirect_device_logger);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_ni_disable_indication", "gpon_ni_disable_indication", user_appl_gpon_ni_disable_indication);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_ni_get_stats", "gpon_ni_get_stats", user_appl_gpon_ni_get_stats);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_start_sn_acquisition", "gpon start sn acquisition", user_appl_gpon_start_sn_acquisition);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_set_mac_table_config", "gpon set mac table config", user_appl_gpon_set_mac_table_config);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_set_interworking_mode", "gpon set interworking mode", user_appl_gpon_set_interworking_mode);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_set_trx", "gpon set trx", user_appl_gpon_set_trx);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_activate_pon", "gpon activate pon", user_appl_gpon_activate_pon);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_onu_config", "gpon onu config", user_appl_gpon_onu_config);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_activate_onu", "gpon activate onu", user_appl_gpon_activate_onu);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_alloc_id_config", "gpon alloc config", user_appl_gpon_alloc_id_config);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_gem_port_config", "gpon gem port config", user_appl_gpon_gem_port_config);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_add_mac_entry", "gpon add mac entry", user_appl_gpon_add_mac_entry);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_dump_mac_table", "print entire GPON MAC table", user_appl_gpon_dump_mac_table);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_ds_gem_port_modify", " gpon ds gem port modify", user_appl_gpon_ds_gem_port_modify);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_set_per_vlan_mapping_method", "gpon set per vlan mapping method", user_appl_gpon_set_ds_per_vlan_mapping_method);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_set_us_flow_configuration", "gpon set us flow configuration", user_appl_gpon_set_us_flow_configuration);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_send_omci_packet", "appl gpon send omci packet", user_appl_gpon_send_omci_packet);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_send_cpu_packets_over_gem_ports", "gpon send cpu packets over gem ports", user_appl_gpon_send_cpu_packets_over_gem_ports);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_configure_and_activate_pon", "configure and activate pon", user_appl_gpon_configure_and_activate_pon);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "gpon_config_and_activate_onu", "config and activate onu", user_appl_gpon_config_and_activate_onu);
+    BCMCLI_MAKE_CMD_NOPARM(dir, "epon_get_all_link_alarms", "retrieve all link alarms", user_appl_epon_get_all_link_alarms);
+#endif
+    return BCM_ERR_OK;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.h b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.h
new file mode 100644
index 0000000..1f5e544
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_ex_cli.h
@@ -0,0 +1,38 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_EX_CLI_H_
+#define _BCMOLT_USER_APPL_EX_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_msg.h>
+
+bcmos_errno bcmolt_user_appl_ex_cli_init(void);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.c b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.c
new file mode 100644
index 0000000..ad8d4a7
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.c
@@ -0,0 +1,768 @@
+/*
+<: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.
+
+:>
+ */
+
+#include <bcmolt_host_api.h>
+#include "bcmolt_user_appl_gpon_utils.h"
+#include <bcmolt_dev_selector.h>
+
+static bcmcli_entry *gpon_utils_cli_dir;
+static bcmcli_entry *gpon_utils_cli_top_dir;
+static bcmos_bool gpon_utils_is_registered;
+
+static bcmos_errno bcmolt_user_appl_gpon_utils_create(bcmcli_entry *top_dir);
+
+#define MAX_GEM_PORTS_FOR_SEND     64
+#define MAX_ONUS_FOR_SEND          64
+#define MAX_PACKET_SIZE            2048
+
+typedef enum
+{
+    GENERATED_PACKET_PATTERN_BYTE_CYCLE,
+    GENERATED_PACKET_PATTERN_WORD_CYCLE,
+    GENERATED_PACKET_PATTERN_CONST
+} generated_packet_pattern;
+
+typedef enum
+{
+    PON_MODE_GPON,
+    PON_MODE_XGPON,
+    PON_MODE_INVALID
+} pon_mode;
+
+static uint8_t saved_packet[MAX_PACKET_SIZE];
+static bcmolt_packet_type saved_packet_type;
+static bcmos_bool saved_packet_calc_crc;
+static uint16_t saved_packet_len = 0;
+
+static void gpon_utils_cli_dir_cli_destroy(void)
+{
+    if (gpon_utils_cli_dir)
+    {
+        bcmcli_token_destroy(gpon_utils_cli_dir);
+        gpon_utils_cli_dir = NULL;
+    }
+}
+
+/* Current device change indication */
+static void bcmolt_user_appl_gpon_utils_device_change_ind(bcmcli_session *session, bcmolt_devid dev)
+{
+    bcmolt_system_mode system_mode;
+    bcmos_errno err = bcmolt_system_mode_get(dev, &system_mode);
+
+    if (err != BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "Error device Id\n");
+        return;
+    }
+
+    if (system_mode == BCMOLT_SYSTEM_MODE_GPON__16_X ||
+        system_mode == BCMOLT_SYSTEM_MODE_GPON__8_X ||
+        system_mode == BCMOLT_SYSTEM_MODE_GPON__4_X ||
+        system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__8_X ||
+        system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__4_X ||
+        system_mode == BCMOLT_SYSTEM_MODE_XGS__2_X_10_G ||
+        system_mode == BCMOLT_SYSTEM_MODE_NGPON2__2_X_10_G)
+    {
+        bcmcli_session_print(session, "Building gpon_utils CLI for device %d\n", dev);
+
+        err = bcmolt_user_appl_gpon_utils_create(gpon_utils_cli_top_dir);
+        if (err)
+        {
+            bcmcli_session_print(session, "Error building gpon_utils CLI\n");
+        }
+    }
+    else
+    {
+        gpon_utils_cli_dir_cli_destroy();
+    }
+}
+
+static inline pon_mode pon_mode_get(bcmolt_pon_ni pon_id)
+{
+    bcmolt_system_mode system_mode;
+    bcmos_errno err = bcmolt_system_mode_get(current_device, &system_mode);
+    if (err != BCM_ERR_OK)
+    {
+        return PON_MODE_INVALID;
+    }
+
+    if (system_mode == BCMOLT_SYSTEM_MODE_GPON_8_XGPON_4_X_COEXISTENCE)
+    {
+        /* special handling for the 8x GPON / 4x XGPON co-existence mode */
+        return pon_id < 8 ? PON_MODE_XGPON : PON_MODE_GPON;
+    }
+    else if (bcmolt_obj_tag_valid_for_system_mode(system_mode, BCMOLT_OBJ_TAG_GPON))
+    {
+        return PON_MODE_GPON;
+    }
+    else if (bcmolt_obj_tag_valid_for_system_mode(system_mode, BCMOLT_OBJ_TAG_XGPON))
+    {
+        return PON_MODE_XGPON;
+    }
+    else
+    {
+        return PON_MODE_INVALID;
+    }
+}
+
+static bcmos_errno build_packet(bcmcli_session *session, bcmolt_packet_type type)
+{
+    uint16_t i;
+    bcmos_bool calc_crc = (bcmos_bool)bcmcli_find_named_parm(session, "calc_crc")->value.unumber;
+    uint16_t packet_size = (uint16_t)bcmcli_find_named_parm(session, "packet_size")->value.unumber;
+    generated_packet_pattern pattern = 
+        (generated_packet_pattern)bcmcli_find_named_parm(session, "pattern")->value.unumber;
+    uint8_t const_value = (uint8_t)bcmcli_find_named_parm(session, "const_value")->value.unumber;
+    uint16_t data_offset;
+    
+    /* save packet type / size / CRC flag */
+    saved_packet_type = type;
+    saved_packet_len = packet_size;
+    saved_packet_calc_crc = calc_crc;
+
+    if (type == BCMOLT_PACKET_TYPE_CPU)
+    {
+        bcmos_mac_address eth_da = bcmcli_find_named_parm(session, "eth_da")->value.mac;
+        bcmos_mac_address eth_sa = bcmcli_find_named_parm(session, "eth_sa")->value.mac;
+
+        /* copy destination MAC */
+        memcpy(saved_packet, &eth_da, 6);
+
+        /* copy source MAC */
+        memcpy(&saved_packet[6], &eth_sa, 6);
+
+        /* write EtherType of 0000 */
+        memset(&saved_packet[12], 0, 2);
+
+        /* data starts after EtherType */
+        data_offset = 14;
+    }
+    else
+    {
+        /* fill the entire packet */
+        data_offset = 0;
+    }
+
+    /* fill the rest of the buffer using the specified data pattern */
+    switch (pattern)
+    {
+    case GENERATED_PACKET_PATTERN_BYTE_CYCLE:
+        const_value = 0;
+        for (i = data_offset; i < packet_size; i++)
+        {
+            saved_packet[i] = const_value++;
+        }
+        break;
+    case GENERATED_PACKET_PATTERN_WORD_CYCLE:
+        /* first 4 bytes = 00000000, next 4 = 11111111, next 4 = 22222222, etc */
+        const_value = 0;
+        for (i = data_offset; i < packet_size; i += 4)
+        {
+            uint16_t j;
+            for (j = i; j < i + 4 && j < packet_size; j++)
+            {
+                saved_packet[j] = const_value;
+            }
+            const_value = (const_value == 0xFF) ? 0 : const_value + 0x11;
+        }
+        break;
+    case GENERATED_PACKET_PATTERN_CONST:
+        for (i = data_offset; i < packet_size; i++)
+        {
+            saved_packet[i] = const_value;
+        }
+        break;
+    default:
+        bcmcli_session_print(session, "[Error] Invalid pattern: %u\n", pattern);
+        return BCM_ERR_INTERNAL;
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno build_cpu_packet_cb(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno err = build_packet(session, BCMOLT_PACKET_TYPE_CPU);
+    if (err == BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "CPU packet buffer saved successfully\n");
+    }
+    else
+    {
+        bcmcli_session_print(session, "[Error] saving CPU packet buffer: %s\n", bcmos_strerror(err));
+    }
+    return err;
+}
+
+static bcmos_errno build_omci_packet_cb(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno err = build_packet(session, BCMOLT_PACKET_TYPE_OMCI);
+    if (err == BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "OMCI packet buffer saved successfully\n");
+    }
+    else
+    {
+        bcmcli_session_print(session, "[Error] saving OMCI packet buffer: %s\n", bcmos_strerror(err));
+    }
+    return err;
+}
+
+static bcmos_errno send_packet_to_gpon_gem_list(bcmcli_session *session, uint16_t gem_count, bcmolt_gpon_gem_id *gems)
+{
+    uint16_t count = 1;
+    uint16_t i;
+    uint16_t gem_idx;
+    bcmos_errno err;
+    bcmcli_cmd_parm *parm;
+    bcmolt_devid device = (bcmolt_devid)bcmcli_find_named_parm(session, "device")->value.unumber;
+    bcmolt_pon_ni pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+    bcmolt_gpon_ni_key pon_ni_key = { .pon_ni = pon_ni };
+    bcmolt_gpon_gem_id_list_u8_max_16 gem_port_list;
+    bcmolt_gpon_ni_cpu_packets proxy;
+    bcmolt_u8_list_u32_max_2048 buffer = { .len = saved_packet_len, .val = saved_packet };
+
+    if (saved_packet_len == 0)
+    {
+        bcmcli_session_print(session, "[Error] you must build/save a packet before you can send it\n");
+        return BCM_ERR_PARM;
+    }
+
+    parm = bcmcli_find_named_parm(session, "count");
+    if (parm != NULL)
+    {
+        count = (uint16_t)parm->value.unumber;
+    }
+
+    bcmcli_session_print(
+        session,
+        "Sending %u proxy packet%s to %u GEM port%s...\n",
+        count,
+        count == 1 ? "" : "s",
+        gem_count,
+        gem_count == 1 ? "" : "s");
+
+    /* build the proxy API message, except the GEM port list */
+    BCMOLT_PROXY_INIT(&proxy, gpon_ni, cpu_packets, pon_ni_key);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, packet_type, saved_packet_type);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, calc_crc, saved_packet_calc_crc);
+    BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, buffer, buffer);
+
+    /* call the proxy API <count> times */
+    for (i = 0; i < count; i++)
+    {
+        /* we may need to split this up into multiple API calls (only 16 GEM ports are allowed per proxy API) */
+        for (gem_idx = 0; gem_idx < gem_count; gem_idx += 16)
+        {
+            gem_port_list.len = MIN(16, gem_count - gem_idx);
+            gem_port_list.val = &gems[gem_idx];
+            BCMOLT_PROXY_PROP_SET(&proxy, gpon_ni, cpu_packets, gem_port_list, gem_port_list);
+
+            err = bcmolt_proxy_send(device, &proxy.hdr);
+            if (err != BCM_ERR_OK)
+            {
+                bcmcli_session_print(
+                    session,
+                    "[Error] proxy send: packet index %u GEM index %u: %s\n",
+                    i,
+                    gem_idx,
+                    bcmos_strerror(err));
+                return err;
+            }
+        }
+    }
+
+    bcmcli_session_print(session, "%u proxy packet%s sent successfully!\n", count, count == 1 ? "" : "s");
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno send_packet_to_xgpon_gem_list(bcmcli_session *session, uint16_t gem_count, bcmolt_xgpon_gem_id *gems)
+{
+    uint16_t count = 1;
+    uint16_t i;
+    uint16_t gem_idx;
+    bcmos_errno err;
+    bcmcli_cmd_parm *parm;
+    bcmolt_devid device = (bcmolt_devid)bcmcli_find_named_parm(session, "device")->value.unumber;
+    bcmolt_pon_ni pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+    bcmolt_xgpon_ni_key pon_ni_key = { .pon_ni = pon_ni };
+    bcmolt_xgpon_gem_id_list_u8_max_16 gem_port_list;
+    bcmolt_xgpon_ni_cpu_packets proxy;
+    bcmolt_u8_list_u32_max_2048 buffer = { .len = saved_packet_len, .val = saved_packet };
+
+    if (saved_packet_len == 0)
+    {
+        bcmcli_session_print(session, "[Error] you must build/save a packet before you can send it\n");
+        return BCM_ERR_PARM;
+    }
+
+    parm = bcmcli_find_named_parm(session, "count");
+    if (parm != NULL)
+    {
+        count = (uint16_t)parm->value.unumber;
+    }
+
+    bcmcli_session_print(
+        session,
+        "Sending %u proxy packet%s to %u GEM port%s...\n",
+        count,
+        count == 1 ? "" : "s",
+        gem_count,
+        gem_count == 1 ? "" : "s");
+
+    /* build the proxy API message, except the GEM port list */
+    BCMOLT_PROXY_INIT(&proxy, xgpon_ni, cpu_packets, pon_ni_key);
+    BCMOLT_PROXY_PROP_SET(&proxy, xgpon_ni, cpu_packets, packet_type, saved_packet_type);
+    BCMOLT_PROXY_PROP_SET(&proxy, xgpon_ni, cpu_packets, calc_crc, saved_packet_calc_crc);
+    BCMOLT_PROXY_PROP_SET(&proxy, xgpon_ni, cpu_packets, buffer, buffer);
+
+    /* call the proxy API <count> times */
+    for (i = 0; i < count; i++)
+    {
+        /* we may need to split this up into multiple API calls (only 16 GEM ports are allowed per proxy API) */
+        for (gem_idx = 0; gem_idx < gem_count; gem_idx += 16)
+        {
+            gem_port_list.len = MIN(16, gem_count - gem_idx);
+            gem_port_list.val = &gems[gem_idx];
+            BCMOLT_PROXY_PROP_SET(&proxy, xgpon_ni, cpu_packets, gem_port_list, gem_port_list);
+
+            err = bcmolt_proxy_send(device, &proxy.hdr);
+            if (err != BCM_ERR_OK)
+            {
+                bcmcli_session_print(
+                    session,
+                    "[Error] proxy send: packet index %u GEM index %u: %s\n",
+                    i,
+                    gem_idx,
+                    bcmos_strerror(err));
+                return err;
+            }
+        }
+    }
+
+    bcmcli_session_print(session, "%u proxy packet%s sent successfully!\n", count, count == 1 ? "" : "s");
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno send_to_gem_cb(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *gem_parm = bcmcli_find_named_parm(session, "gem");
+    bcmolt_pon_ni pon_id = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+    uint16_t i;
+    pon_mode mode = pon_mode_get(pon_id);
+
+    if (mode == PON_MODE_GPON)
+    {
+        static bcmolt_gpon_gem_id gem_id_buf[MAX_GEM_PORTS_FOR_SEND];
+        for (i = 0; i < gem_parm->array_size; i++)
+        {
+            gem_id_buf[i] = (bcmolt_gpon_gem_id)gem_parm->values[i].unumber;
+        }
+        return send_packet_to_gpon_gem_list(session, (uint16_t)gem_parm->array_size, gem_id_buf);
+    } 
+    else if (mode == PON_MODE_XGPON)
+    {
+        static bcmolt_xgpon_gem_id gem_id_buf[MAX_GEM_PORTS_FOR_SEND];
+        for (i = 0; i < gem_parm->array_size; i++)
+        {
+            gem_id_buf[i] = (bcmolt_xgpon_gem_id)gem_parm->values[i].unumber;
+        }
+        return send_packet_to_xgpon_gem_list(session, (uint16_t)gem_parm->array_size, gem_id_buf);
+    }
+    else
+    {
+        bcmcli_session_print(session, "[Error] Invalid system mode\n");
+        return BCM_ERR_INTERNAL;
+    }
+}
+
+static bcmos_errno send_packet_to_gpon_onu_list(bcmcli_session *session, uint16_t onu_count, bcmolt_gpon_onu_id *onus)
+{
+    uint16_t count = 1;
+    uint16_t i;
+    uint16_t onu_idx;
+    bcmos_errno err;
+    bcmcli_cmd_parm *parm;
+    bcmolt_devid device = (bcmolt_devid)bcmcli_find_named_parm(session, "device")->value.unumber;
+    bcmolt_pon_ni pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+
+    if (saved_packet_len == 0)
+    {
+        bcmcli_session_print(session, "[Error] you must build/save a packet before you can send it\n");
+        return BCM_ERR_PARM;
+    }
+
+    parm = bcmcli_find_named_parm(session, "count");
+    if (parm != NULL)
+    {
+        count = (uint16_t)parm->value.unumber;
+    }
+
+    bcmcli_session_print(
+        session,
+        "Sending %u proxy packet%s to %u ONU%s...\n",
+        count,
+        count == 1 ? "" : "s",
+        onu_count,
+        onu_count == 1 ? "" : "s");
+
+    /* call the proxy API (potentially several times) for each ONU */
+    for (onu_idx = 0; onu_idx < onu_count; onu_idx++)
+    {
+        bcmolt_gpon_onu_key onu_key = { .pon_ni = pon_ni, .onu_id = onus[onu_idx] };
+        bcmolt_u8_list_u32_max_2048 buffer = { .val = saved_packet };
+        bcmolt_gpon_onu_cpu_packets proxy;
+        uint16_t packets_per_proxy;
+
+        /* build the proxy API message, except the buffer/number of packets */
+        BCMOLT_PROXY_INIT(&proxy, gpon_onu, cpu_packets, onu_key);
+        BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, packet_type, saved_packet_type);
+        BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, calc_crc, saved_packet_calc_crc);
+        BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, packet_size, saved_packet_len);
+
+        /* calculate the number of packets we can fit per invocation of the proxy API */
+        packets_per_proxy = MIN(count, 2048 / saved_packet_len);
+        BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, number_of_packets, packets_per_proxy);
+
+        /* prepare the buffer data with several copies of the same packet in a row */
+        for (i = 1; i < packets_per_proxy; i++)
+        {
+            memcpy(&saved_packet[i * saved_packet_len], saved_packet, saved_packet_len); 
+        }
+
+        /* call the proxy API enough times to send the requested number of packets */
+        for (i = 0; i < count; i += packets_per_proxy)
+        {
+            uint16_t number_of_packets = MIN(packets_per_proxy, count - i);
+            BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, number_of_packets, number_of_packets);
+
+            buffer.len = number_of_packets * saved_packet_len;
+            BCMOLT_PROXY_PROP_SET(&proxy, gpon_onu, cpu_packets, buffer, buffer);
+
+            err = bcmolt_proxy_send(device, &proxy.hdr);
+            if (err != BCM_ERR_OK)
+            {
+                bcmcli_session_print(
+                    session,
+                    "[Error] proxy send: ONU %u packet index %u: %s\n",
+                    onus[onu_idx],
+                    i,
+                    bcmos_strerror(err));
+                return err;
+            }
+        }
+    }
+
+    bcmcli_session_print(session, "%u proxy packet%s sent successfully!\n", count, count == 1 ? "" : "s");
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno send_packet_to_xgpon_onu_list(bcmcli_session *session, uint16_t onu_count, bcmolt_xgpon_onu_id *onus)
+{
+    uint16_t count = 1;
+    uint16_t i;
+    uint16_t onu_idx;
+    bcmos_errno err;
+    bcmcli_cmd_parm *parm;
+    bcmolt_devid device = (bcmolt_devid)bcmcli_find_named_parm(session, "device")->value.unumber;
+    bcmolt_pon_ni pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+
+    if (saved_packet_len == 0)
+    {
+        bcmcli_session_print(session, "[Error] you must build/save a packet before you can send it\n");
+        return BCM_ERR_PARM;
+    }
+
+    parm = bcmcli_find_named_parm(session, "count");
+    if (parm != NULL)
+    {
+        count = (uint16_t)parm->value.unumber;
+    }
+
+    bcmcli_session_print(
+        session,
+        "Sending %u proxy packet%s to %u ONU%s...\n",
+        count,
+        count == 1 ? "" : "s",
+        onu_count,
+        onu_count == 1 ? "" : "s");
+
+    /* call the proxy API (potentially several times) for each ONU */
+    for (onu_idx = 0; onu_idx < onu_count; onu_idx++)
+    {
+        bcmolt_xgpon_onu_key onu_key = { .pon_ni = pon_ni, .onu_id = onus[onu_idx] };
+        bcmolt_u8_list_u32_max_2048 buffer = { .val = saved_packet };
+        bcmolt_xgpon_onu_cpu_packets proxy;
+        uint16_t packets_per_proxy;
+
+        /* build the proxy API message, except the buffer/number of packets */
+        BCMOLT_PROXY_INIT(&proxy, xgpon_onu, cpu_packets, onu_key);
+        BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, packet_type, saved_packet_type);
+        BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, calc_crc, saved_packet_calc_crc);
+        BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, packet_size, saved_packet_len);
+
+        /* calculate the number of packets we can fit per invocation of the proxy API */
+        packets_per_proxy = MIN(count, 2048 / saved_packet_len);
+        BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, number_of_packets, packets_per_proxy);
+
+        /* prepare the buffer data with several copies of the same packet in a row */
+        for (i = 1; i < packets_per_proxy; i++)
+        {
+            memcpy(&saved_packet[i * saved_packet_len], saved_packet, saved_packet_len); 
+        }
+
+        /* call the proxy API enough times to send the requested number of packets */
+        for (i = 0; i < count; i += packets_per_proxy)
+        {
+            uint16_t number_of_packets = MIN(packets_per_proxy, count - i);
+            BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, number_of_packets, number_of_packets);
+
+            buffer.len = number_of_packets * saved_packet_len;
+            BCMOLT_PROXY_PROP_SET(&proxy, xgpon_onu, cpu_packets, buffer, buffer);
+
+            err = bcmolt_proxy_send(device, &proxy.hdr);
+            if (err != BCM_ERR_OK)
+            {
+                bcmcli_session_print(
+                    session,
+                    "[Error] proxy send: ONU %u packet index %u: %s\n",
+                    onus[onu_idx],
+                    i,
+                    bcmos_strerror(err));
+                return err;
+            }
+        }
+    }
+
+    bcmcli_session_print(session, "%u proxy packet%s sent successfully!\n", count, count == 1 ? "" : "s");
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno send_to_onu_cb(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *onu_parm = bcmcli_find_named_parm(session, "onu");
+    bcmolt_pon_ni pon_id = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "pon_ni")->value.unumber;
+    uint16_t i;
+    pon_mode mode = pon_mode_get(pon_id);
+
+    if (mode == PON_MODE_GPON)
+    {
+        static bcmolt_gpon_onu_id onu_id_buf[MAX_ONUS_FOR_SEND];
+        for (i = 0; i < onu_parm->array_size; i++)
+        {
+            onu_id_buf[i] = (bcmolt_gpon_onu_id)onu_parm->values[i].unumber;
+        }
+        return send_packet_to_gpon_onu_list(session, (uint16_t)onu_parm->array_size, onu_id_buf);
+    } 
+    else if (mode == PON_MODE_XGPON)
+    {
+        static bcmolt_xgpon_onu_id onu_id_buf[MAX_ONUS_FOR_SEND];
+        for (i = 0; i < onu_parm->array_size; i++)
+        {
+            onu_id_buf[i] = (bcmolt_xgpon_onu_id)onu_parm->values[i].unumber;
+        }
+        return send_packet_to_xgpon_onu_list(session, (uint16_t)onu_parm->array_size, onu_id_buf);
+    }
+    else
+    {
+        bcmcli_session_print(session, "[Error] Invalid system mode\n");
+        return BCM_ERR_STATE;
+    }
+}
+
+static void dump_saved_mac(bcmcli_session *session, uint16_t offset)
+{
+    uint16_t i;
+    for (i = 0; i < 6; i++)
+    {
+        if (i != 0)
+        {
+            bcmcli_session_print(session, ":");
+        }
+        bcmcli_session_print(session, "%02X", saved_packet[offset + i]);
+    }
+}
+
+static bcmos_errno dump_packet_cb(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    uint16_t i;
+    uint16_t data_offset;
+
+    if (saved_packet_len == 0)
+    {
+        bcmcli_session_print(session, "No packet has been built yet\n");
+        return BCM_ERR_OK;
+    }
+
+    bcmcli_session_print(session, "Saved packet parameters:\n");
+    bcmcli_session_print(session, "    Type: %s\n", saved_packet_type == BCMOLT_PACKET_TYPE_CPU ? "CPU" : "OMCI");
+    bcmcli_session_print(session, "    Length: %u\n", saved_packet_len);
+    bcmcli_session_print(session, "    Auto-calculate CRC: %s\n", saved_packet_calc_crc ? "yes" : "no");
+
+    if (saved_packet_type == BCMOLT_PACKET_TYPE_CPU)
+    {
+        bcmcli_session_print(session, "    Ethernet DA: ");
+        dump_saved_mac(session, 0);
+        bcmcli_session_print(session, "\n");
+
+        bcmcli_session_print(session, "    Ethernet SA: ");
+        dump_saved_mac(session, 6);
+        bcmcli_session_print(session, "\n");
+
+        bcmcli_session_print(session, "    EtherType: 0x%02X%02X\n", saved_packet[12], saved_packet[13]);
+        data_offset = 14;
+    }
+    else
+    {
+        data_offset = 0;
+    }
+
+    bcmcli_session_print(session, "Data bytes (starting at offset %u):", data_offset);
+    for (i = data_offset; i < saved_packet_len; i++)
+    {
+        if ((i - data_offset) % 16 == 0)
+        {
+            bcmcli_session_print(session, "\n    ");
+        }
+        else
+        {
+            bcmcli_session_print(session, " ");
+        }
+        bcmcli_session_print(session, "%02X", saved_packet[i]);
+    }
+    bcmcli_session_print(session, "\n");
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_user_appl_gpon_utils_init(bcmcli_entry *top_dir)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    if (!gpon_utils_is_registered)
+    {
+        gpon_utils_is_registered = BCMOS_TRUE;
+
+        /* Subscribe for device change indication */
+        err = bcmolt_dev_sel_ind_register(bcmolt_user_appl_gpon_utils_device_change_ind);
+    }
+    return err ? err : bcmolt_user_appl_gpon_utils_create(top_dir);
+}
+
+static bcmos_errno bcmolt_user_appl_gpon_utils_create(bcmcli_entry *top_dir)
+{
+    static bcmcli_enum_val generated_packet_pattern_table[] =
+    {
+        { .name = "byte_cycle", .val = GENERATED_PACKET_PATTERN_BYTE_CYCLE },
+        { .name = "word_cycle", .val = GENERATED_PACKET_PATTERN_WORD_CYCLE },
+        { .name = "const",      .val = GENERATED_PACKET_PATTERN_CONST },
+        BCMCLI_ENUM_LAST
+    };
+
+    static bcmcli_parm_value gem_port_ids[MAX_GEM_PORTS_FOR_SEND];
+    static bcmcli_parm_value onu_ids[MAX_ONUS_FOR_SEND];
+
+    bcmcli_entry *dir;
+
+    if (bcmcli_dir_find(top_dir, "gpon"))
+    {
+        return BCM_ERR_OK;
+    }
+
+    gpon_utils_cli_dir_cli_destroy();
+
+    dir = bcmcli_dir_add(top_dir, "gpon", "Common GPON/XGPON functions", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "build_cpu_packet",
+        "Build and save CPU packet",
+        build_cpu_packet_cb,
+        BCMCLI_MAKE_PARM_ENUM("calc_crc", "Auto-calculate CRC", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_RANGE(
+            "packet_size", "Full packet size", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE, 0, MAX_PACKET_SIZE),
+        BCMCLI_MAKE_PARM_ENUM("pattern", "Background pattern", generated_packet_pattern_table, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_RANGE("const_value", "Const value", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE, 0, 0xFF),
+        BCMCLI_MAKE_PARM("eth_da", "Ethernet destination address", BCMCLI_PARM_MAC, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM("eth_sa", "Ethernet source address", BCMCLI_PARM_MAC, BCMCLI_PARM_FLAG_NONE));
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "build_omci_packet",
+        "Build and save OMCI packet",
+        build_omci_packet_cb,
+        BCMCLI_MAKE_PARM_ENUM("calc_crc", "Auto-calculate CRC", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_RANGE(
+            "packet_size", "Full packet size", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE, 0, MAX_PACKET_SIZE),
+        BCMCLI_MAKE_PARM_ENUM("pattern", "Background pattern", generated_packet_pattern_table, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_RANGE("const_value", "Const value", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE, 0, 0xFF));
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "send_to_gem",
+        "Send saved CPU/OMCI packet to GEM port(s)",
+        send_to_gem_cb,
+        BCMCLI_MAKE_PARM("device", "Device ID", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM("pon_ni", "PON NI", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ARRAY(
+            "gem",
+            "GEM port IDs, comma separated",
+            BCMCLI_PARM_UNUMBER,
+            BCMCLI_PARM_FLAG_NONE,
+            MAX_GEM_PORTS_FOR_SEND,
+            gem_port_ids),
+        BCMCLI_MAKE_PARM("count", "Number of packets (default 1)", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_OPTIONAL));
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "send_to_onu",
+        "Send saved CPU/OMCI packet to ONU(s) (OMCI GEM port)",
+        send_to_onu_cb,
+        BCMCLI_MAKE_PARM("device", "Device ID", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM("pon_ni", "PON NI", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ARRAY(
+            "onu",
+            "ONU IDs, comma separated",
+            BCMCLI_PARM_UNUMBER,
+            BCMCLI_PARM_FLAG_NONE,
+            MAX_ONUS_FOR_SEND,
+            onu_ids),
+        BCMCLI_MAKE_PARM("count", "Number of packets (default 1)", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_OPTIONAL));
+
+    BCMCLI_MAKE_CMD_NOPARM(
+        dir,
+        "dump_packet",
+        "Print saved CPU/OMCI packet data",
+        dump_packet_cb);
+
+    gpon_utils_cli_dir = dir;
+    gpon_utils_cli_top_dir = top_dir;
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.h b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.h
new file mode 100644
index 0000000..b1380c9
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/bcmolt_user_appl_gpon_utils.h
@@ -0,0 +1,37 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_GPON_UTILS_H_
+#define _BCMOLT_USER_APPL_GPON_UTILS_H_
+
+#include <bcmolt_host_api.h>
+
+bcmos_errno bcmolt_user_appl_gpon_utils_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/Makefile b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/Makefile
new file mode 100644
index 0000000..8167e23
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/Makefile
@@ -0,0 +1,12 @@
+# DPoE Security
+
+ifeq ("$(ENABLE_CLI)", "y")
+
+	MOD_NAME = bcm_user_appl_dpoe_sec
+	MOD_TYPE = lib
+	MOD_DEPS = os host_api bcm_user_appl_epon_oam
+
+	srcs = bcmolt_user_appl_dpoe_sec.c dpoe_eap_tls.c dpoe_sec_fsm.c dpoe_sec_mka_fsm.c dpoe_sec_util.c mka.c
+
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.c
new file mode 100644
index 0000000..14bcb12
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.c
@@ -0,0 +1,136 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "bcmos_system.h"
+#include "bcmolt_model_types.h"
+#include "bcmolt_epon_oam_types.h"
+#include "bcmolt_user_appl_dpoe_sec.h"
+#include "dpoe_sec_fsm.h"
+
+static bcmos_bool is_running = BCMOS_FALSE;
+
+static bcmos_bool cert_authority_trust_placeholder(dpoe_sec_link_rec* link_rec)
+{
+    BCM_LOG(INFO, dpoe_sec_log_id[link_rec->device], "This function should validate the certificate information stored in the link record and decide whether to accept this authentication.\n");
+    return BCMOS_TRUE;
+}
+
+static void fsm_complete_placeholder(bcmolt_devid device, bcmolt_epon_link_key link_key, bcmos_errno status)
+{
+    if (status == BCM_ERR_OK)
+    {
+        BCM_LOG(INFO, dpoe_sec_log_id[device], "The DPoE Security FSM completed successfully. This link can now be configured for service.\n");
+    }
+    else
+    {
+        BCM_LOG(INFO, dpoe_sec_log_id[device], "The DPoE Security FSM failed due to %s (%d). Appropriate action should be taken to correct this error.\n", bcmos_strerror(status), status);
+    }
+}
+
+#ifdef ENABLE_CLI
+static bcmos_errno dpoe_sec_cli_auth(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmolt_epon_link_key link_key;
+    dpoe_sec_fsm_start_data cfg;
+
+    link_key.epon_ni = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "pon")->value.unumber;
+    link_key.mac_address = bcmcli_find_named_parm(session, "onu")->value.mac;
+    cfg.enc_mode = (bcmolt_epon_oam_dpoe_encryption_mode)bcmcli_find_named_parm(session, "mode")->value.enum_val;
+    cfg.auth = BCMOS_TRUE;
+
+    return dpoe_sec_fsm_link_start(current_device, link_key, &cfg);
+}
+#endif
+
+void bcmolt_user_appl_dpoe_sec_cli_init(bcmcli_entry *top_dir)
+{
+#ifdef ENABLE_CLI
+    static const char *dir_name = "dpoe_sec";
+
+    if (bcmcli_dir_find(top_dir, dir_name))
+    {
+        return;
+    }
+
+    bcmcli_entry *dpoe_sec_dir = bcmcli_dir_add(top_dir, dir_name, "DPoE Security", BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(NULL == dpoe_sec_dir);
+
+    static bcmcli_enum_val enc_mode_table[] =
+    {
+        {"none", (long)BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_NONE},
+        {"1down", (long)BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_ONE_DOWN},
+        {"10down", (long)BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_DOWN},
+        {"10bi", (long)BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI},
+        BCMCLI_ENUM_LAST
+    };
+
+    BCMCLI_MAKE_CMD(dpoe_sec_dir, "auth", "Authenticate ONU", dpoe_sec_cli_auth,
+        BCMCLI_MAKE_PARM("pon", "PON NI", BCMCLI_PARM_UDECIMAL, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM("onu", "ONU MAC", BCMCLI_PARM_MAC, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ENUM("mode", "Encryption mode", enc_mode_table, BCMCLI_PARM_FLAG_NONE));
+#endif
+}
+
+void bcmolt_user_appl_dpoe_sec_process_proxy_rx(bcmolt_devid device_id, bcmolt_proxy_rx *proxy_rx)
+{
+    const bcmolt_epon_link_frame_captured *cap = (bcmolt_epon_link_frame_captured*)proxy_rx;
+
+    if ((BCMOLT_OBJ_ID_EPON_LINK == proxy_rx->hdr.obj_type) &&
+        (BCMOLT_EPON_LINK_PROXY_RX_ID_FRAME_CAPTURED == proxy_rx->hdr.subgroup))
+    {
+        if ((cap->data.frame.val[12] == 0x88) &&
+            (cap->data.frame.val[13] == 0x09) &&
+            (cap->data.frame.val[14] == 0x03))
+        {
+            uint8_t *frame_copy;
+
+            frame_copy = bcmos_alloc(cap->data.frame.len);
+            memcpy(frame_copy, cap->data.frame.val, cap->data.frame.len);
+
+            dpoe_sec_fsm_link_rx_oam(device_id, cap->key, frame_copy, cap->data.frame.len);
+        }
+    }
+    else
+    {
+        return; /* not a frame captured on a link - ignore */
+    }
+}
+
+void bcmolt_user_appl_dpoe_sec_init(void)
+{
+    if (is_running)
+    {
+        return;
+    }
+
+    dpoe_sec_fsm_init(cert_authority_trust_placeholder, fsm_complete_placeholder);
+
+    is_running = BCMOS_TRUE;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.h
new file mode 100644
index 0000000..6872f8c
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/bcmolt_user_appl_dpoe_sec.h
@@ -0,0 +1,43 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_DPOE_SEC_H_
+#define _BCMOLT_USER_APPL_DPOE_SEC_H_
+
+#include "bcmcli.h"
+
+void bcmolt_user_appl_dpoe_sec_process_proxy_rx(bcmolt_devid device_id, bcmolt_proxy_rx *proxy_rx);
+
+void bcmolt_user_appl_dpoe_sec_cli_init(bcmcli_entry *top_dir);
+
+void bcmolt_user_appl_dpoe_sec_init(void);
+
+#endif /* _BCMOLT_USER_APPL_DPOE_SEC_H_ */
+
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.c
new file mode 100644
index 0000000..22ea80a
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.c
@@ -0,0 +1,1483 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "dpoe_eap_tls.h"
+#include "bcmolt_buf.h"
+#include "dpoe_sec_fsm.h"
+
+/* The destination multicast MAC address for EAPOL packets. */
+static const bcmos_mac_address eapol_dst_mac = { { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 } };
+
+/* The length of the RSA key used in the server_key_exchange TLS message sent to the ONU during DPoE bi-directional
+   authentication. */
+#define DPOE_BI_RSA_KEY_SIZE 2048
+
+/* 2 1200 byte certs + some overhead.... */
+#define EAPOL_PKT_SIZE 5000
+
+typedef enum
+{
+    EAP_CODE_REQUEST = 1,
+    EAP_CODE_RESPONSE,
+    EAP_CODE_SUCCESS,
+    EAP_CODE_FAILURE
+} eap_code;
+
+typedef struct
+{
+    uint8_t eap_code;
+    uint8_t id;
+    uint16_t length;
+} eap_frame;
+
+#define EAP_FRAME_SIZE 4
+
+typedef enum
+{
+    EAP_TYPE_IDENTITY = 1,
+    EAP_TYPE_NOTIFICATION,
+    EAP_TYPE_NAK,
+    EAP_TYPE_MD5,
+    EAP_TYPE_OTP,
+    EAP_TYPE_GENERIC_TOKEN_CARD,
+    EAP_TYPE_TLS = 13
+} eap_type;
+
+#define TLS_MAJOR_VERSION 3
+#define TLS_MINOR_VERSION 2
+
+/* First the EAP structures.... */
+typedef struct
+{
+    uint8_t auth_sub_type;
+    uint8_t eap_tls_flags;
+} eap_tls_hdr;
+
+#define EAP_TLS_HDR_SIZE 2
+
+#define EAP_HDR_SIZE (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE)
+
+typedef enum
+{
+    TLS_CHANGE_CIPHER_SPEC_ID = 20,
+    TLS_ALERT_ID = 21,
+    TLS_HANDSHAKE_ID = 22,
+    TLS_APPLICATION_DATA_ID = 23,
+} tls_content_type;
+
+typedef struct
+{
+    uint8_t content_type;
+    tls_protocol_version tls_version;
+    uint16_t tls_record_length;
+} tls_record_hdr;
+
+#define TLS_RECORD_HDR_SIZE (3 + PROTOCOL_VERSION_SIZE)
+
+#define EAP_TLS_FLAG_LENGTH_OFFSET 7
+#define EAP_TLS_FLAG_MORE_OFFSET 6
+#define EAP_TLS_FLAG_START_OFFSET 5
+#define EAP_TLS_FLAG_LENGTH_BIT (1U << EAP_TLS_FLAG_LENGTH_OFFSET)
+#define EAP_TLS_FLAG_MORE_BIT (1U << EAP_TLS_FLAG_MORE_OFFSET)
+#define EAP_TLS_FLAG_START_BIT (1U << EAP_TLS_FLAG_START_OFFSET)
+
+typedef struct
+{
+    eap_frame hdr;
+    eap_tls_hdr tls;
+} eap_msg_buf;
+
+typedef struct
+{
+    eapol_msg_hdr hdr;
+    eap_msg_buf eap_msg;
+} eapol_msg;
+
+/* Now the TLS records. */
+typedef struct tls_session_id
+{
+    uint8_t length;
+    uint8_t session_id[SIZE_OF_TLS_SESSION_ID];
+} tls_session_id;
+
+typedef enum
+{
+    cm_null = 0
+} compression_method;
+
+typedef struct
+{
+    uint8_t length;
+    uint8_t compression_method;
+} compression_methods;
+
+typedef enum
+{
+    RSA_SIGN                          = 1,
+    DSS_SIGN                          = 2,
+    RSA_FIXED_DH                      = 3,
+    DSS_FIXED_DH                      = 4,
+    RSA_EPHEMERAL_DH_RESERVED         = 5,
+    DSS_EPHEMERAL_DH_RESERVED         = 6,
+    FORTEZZA_DMS_RESERVED             = 20,
+} client_certificate_type;
+
+/* For 2048 bit RSA key */
+#define SIZE_OF_RSA_MODULUS 256
+#define SIZE_OF_RSA_EXPONENT 3
+
+typedef enum
+{
+    HS_HELLO_REQUEST = 0,
+    HS_CLIENT_HELLO = 1,
+    HS_SERVER_HELLO = 2,
+    HS_CERTIFICATE = 11,
+    HS_SERVER_KEY_EXCHANGE = 12,
+    HS_CERTIFICATE_REQUEST = 13,
+    HS_SERVER_HELLO_DONE = 14,
+    HS_CERTIFICATE_VERIFY = 15,
+    HS_CLIENT_KEY_EXCHANGE = 16,
+    HS_FINISHED = 20,
+} handshake_type;
+
+typedef struct
+{
+    uint8_t msg_id;
+    uint32_t msg_length; /* actually U24 */
+} tls_handshake;
+
+#define TLS_HANDSHAKE_SIZE 4
+
+typedef struct
+{
+    uint8_t *eapol;
+    uint8_t *eap;
+    uint8_t *eap_tls;
+    uint8_t *tls_rec;
+} eap_length_fields;
+
+static f_dpoe_sec_cert_trust_cb _cert_trust;
+static f_dpoe_sec_auth_cb _auth_complete;
+
+static void _buffer_hash_data(dpoe_sec_link_rec *link_rec, const uint8_t *buf, uint32_t len)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    dpoe_sec_sha1_update(&link_rec->auth_ctrl.trans_data.sha1_hash, buf, len);
+    dpoe_sec_md5_update(&link_rec->auth_ctrl.trans_data.md5_hash, buf, len);
+}
+
+static void _dpoe_eap_tls_compute_master_session_key(auth_trans_data *trans_data)
+{
+    uint8_t seed[(2* COUNT_OF_RANDOM_BYTES)];
+    uint32_t seed_len = 0;
+
+    /* Parameter checks. */
+    BUG_ON(trans_data == NULL);
+
+    /* The seed to the PRF is the client random value concatenated with the server random value. */
+    memcpy(&seed[seed_len], trans_data->client_random, COUNT_OF_RANDOM_BYTES);
+    seed_len += COUNT_OF_RANDOM_BYTES;
+    memcpy(&seed[seed_len], trans_data->server_random, COUNT_OF_RANDOM_BYTES);
+    seed_len += COUNT_OF_RANDOM_BYTES;
+
+    dpoe_sec_prf_expand_4346(
+        trans_data->pre_master_secret,
+        sizeof(trans_data->pre_master_secret),
+        (const uint8_t *)"master secret",
+        strlen("master secret"),
+        seed,
+        seed_len,
+        trans_data->master_secret,
+        sizeof(trans_data->master_secret));
+    dpoe_sec_prf_expand_4346(
+        trans_data->master_secret,
+        sizeof(trans_data->master_secret),
+        (const uint8_t *)"client EAP encryption",
+        strlen("client EAP encryption"),
+        seed,
+        seed_len,
+        trans_data->key_material,
+        sizeof(trans_data->key_material));
+
+    memcpy(trans_data->master_session_key, trans_data->key_material, sizeof(trans_data->master_session_key));
+}
+
+static bcmos_bool _dpoe_eap_tls_prepare_security_data(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    /* If bidirectional, compute the master session key and generate the RSA public/private key pair */
+    if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+    {
+        _dpoe_eap_tls_compute_master_session_key(&link_rec->auth_ctrl.trans_data);
+    }
+
+    return BCMOS_TRUE;
+}
+
+static bcmos_bool _dpoe_eap_tls_skip_length(bcmolt_buf *buf, uint32_t skip, uint8_t** len)
+{
+    *len = bcmolt_buf_snap_get(buf);
+    return bcmolt_buf_skip(buf, skip);
+}
+
+static bcmos_bool _dpoe_eap_tls_init_eap_hdr(
+    dpoe_sec_link_rec *link_rec,
+    bcmolt_buf *buf,
+    eap_code code,
+    eap_length_fields *lf)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    return
+        bcmolt_buf_write_mac_address(buf, eapol_dst_mac) &&
+        bcmolt_buf_write_mac_address(buf, link_rec->ni_mac) &&
+        bcmolt_buf_write_u16_be(buf, ETHERTYPE_EAPOL) &&
+        bcmolt_buf_write_u8(buf, EAPOL_PROTOCOL_VERSION_DPOE) &&
+        bcmolt_buf_write_u8(buf, EAPOL_TYPE_PACKET) &&
+        /* Eapol Length - we re-compute this after building the packet */
+        _dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->eapol) &&
+        /* EAP layer headers */
+        bcmolt_buf_write_u8(buf, code) &&
+        bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.current_packet_id++) &&
+        /* EAP length - again, computed when the packet is packed! */
+        _dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->eap);
+}
+
+static bcmos_bool _dpoe_eap_tls_init_tls_hdr(bcmolt_buf *buf, uint8_t eapTlsFlags, eap_length_fields *lf)
+{
+    bcmos_bool rc = BCMOS_FALSE;
+
+    /* Parameter checks. */
+    BUG_ON(buf == NULL);
+
+    if (bcmolt_buf_write_u8(buf, EAP_TYPE_TLS) &&
+        bcmolt_buf_write_u8(buf, eapTlsFlags))
+    {
+        if ((eapTlsFlags & EAP_TLS_FLAG_LENGTH_BIT) == EAP_TLS_FLAG_LENGTH_BIT)
+        {
+            /* EAP-TLS length - computed at pack completion only allocate room for this if EapTlsFlagLengthBit is set
+               But if the EapTlsFlagLengthBit is set, we also write the TLS Record header. */
+            if (_dpoe_eap_tls_skip_length(buf, sizeof(uint32_t), &lf->eap_tls) &&
+                bcmolt_buf_write_u8(buf, TLS_HANDSHAKE_ID) &&
+                bcmolt_buf_write_u8(buf, TLS_MAJOR_VERSION) &&
+                bcmolt_buf_write_u8(buf, TLS_MINOR_VERSION) &&
+                /* Another length field, the TLS record length */
+                _dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->tls_rec))
+            {
+                rc = BCMOS_TRUE;
+            }
+        }
+        else
+        {
+            rc = BCMOS_TRUE;
+        }
+    }
+
+    return rc;
+}
+
+static void _dpoe_eap_tls_pack_handshake_length(
+    dpoe_sec_link_rec *link_rec,
+    bcmolt_buf *buf,
+    uint8_t *start,
+    uint8_t *len)
+{
+    uint32_t rec_len;
+    uint8_t *end;
+
+    /* Now go back and fill in the length field.  This is the length of the TlsHandshake record */
+    end = bcmolt_buf_snap_get(buf);
+    rec_len = end - start;
+    bcmolt_buf_snap_restore(buf, len);
+    bcmolt_buf_write_u24(buf, uint32_to_24(rec_len - TLS_HANDSHAKE_SIZE));
+    bcmolt_buf_snap_restore(buf, end);
+    _buffer_hash_data(link_rec, start, rec_len);
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_server_hello_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint8_t *start;
+    uint8_t *len;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    start = bcmolt_buf_snap_get(buf);
+    if (bcmolt_buf_write_u8(buf, HS_SERVER_HELLO) &&
+        (len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
+        bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.version.major) &&
+        bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.version.minor) &&
+        bcmolt_buf_write(buf, link_rec->auth_ctrl.trans_data.server_random, COUNT_OF_RANDOM_BYTES) &&
+        bcmolt_buf_write_u8(buf, COUNT_OF_RANDOM_BYTES) &&
+        bcmolt_buf_write(buf, link_rec->auth_ctrl.trans_data.server_random, COUNT_OF_RANDOM_BYTES) &&
+
+        /* Cipher Suites - we write 0 as the length of the Cipher Suites
+           Short circuit if using pre-release authentication */
+        ((link_rec->auth_ctrl.version.minor == 3) || bcmolt_buf_write_u16_be(buf, 0)) &&
+
+        /* CompressionMethods also write 0 as the length */
+        bcmolt_buf_write_u8(buf, 0))
+    {
+        _dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_server_key_exchange_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint8_t *start;
+    uint8_t *len;
+    uint8_t rsa_modulus[SIZE_OF_RSA_MODULUS] = {};
+    uint8_t rsa_exponent[SIZE_OF_RSA_EXPONENT] = {};
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    /* If not bidirectional, just return BCMOS_TRUE */
+    if (link_rec->cfg.enc_mode != BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+    {
+        return BCMOS_TRUE;
+    }
+
+    /* Verify that the length of the public key (modulus) of the private key matches the expected length. Also, verify
+       that the modulus is successfully read from the RSA key. */
+    start = bcmolt_buf_snap_get(buf);
+    if ((dpoe_sec_rsa_public_get(link_rec->auth_ctrl.trans_data.rsa, rsa_modulus, rsa_exponent)) &&
+        bcmolt_buf_write_u8(buf, HS_SERVER_KEY_EXCHANGE) &&
+        (len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
+        bcmolt_buf_write(buf, rsa_modulus, sizeof(rsa_modulus)) &&
+        bcmolt_buf_write(buf, rsa_exponent, sizeof(rsa_exponent)))
+    {
+        _dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_cert_req_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint8_t *start;
+    uint8_t *len;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    start = bcmolt_buf_snap_get(buf);
+    if (bcmolt_buf_write_u8(buf, HS_CERTIFICATE_REQUEST) &&
+        (len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
+        bcmolt_buf_write_u8(buf, 1) && /* length of 'Certificate Type' */
+        bcmolt_buf_write_u8(buf, RSA_SIGN) && /* Certificate Type */
+        bcmolt_buf_write_u16_be(buf, 0)) /* zero length for cert authorities */
+    {
+        _dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_server_hello_done_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint8_t *start;
+    uint8_t *len;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    start = bcmolt_buf_snap_get(buf);
+    if (bcmolt_buf_write_u8(buf, HS_SERVER_HELLO_DONE) &&
+        (len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))))
+    {
+        _dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_finished_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint8_t *start;
+    uint8_t *len;
+
+    start = bcmolt_buf_snap_get(buf);
+    if (bcmolt_buf_write_u8(buf, HS_FINISHED) &&
+        (len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))))
+    {
+        _dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_pack_msg_lengths(bcmolt_buf *buf, uint8_t eap_tls_flags, eap_length_fields *lf)
+{
+    uint16_t eapol_length;
+    uint32_t eap_tls_length;
+    uint8_t *snap = bcmolt_buf_snap_get(buf);
+
+    /* Parameter checks. */
+    BUG_ON(buf == NULL);
+    BUG_ON(lf == NULL);
+
+    eapol_length = bcmolt_buf_get_used(buf) - EAPOL_MSG_HDR_SIZE;
+
+    /* Seems odd that these two would be the same. But eapolLength is defined as the length of the body fields of the
+       Eapol buffer, which does not include the Eapol header. EAP is different, the EAP length is define to include the
+       entire EAP packet, including the header. */
+    bcmolt_buf_snap_restore(buf, lf->eapol);
+    if (!bcmolt_buf_write_u16_be(buf, eapol_length))
+    {
+        bcmolt_buf_snap_restore(buf, snap);
+        return BCMOS_FALSE;
+    }
+
+    bcmolt_buf_snap_restore(buf, lf->eap);
+    if (!bcmolt_buf_write_u16_be(buf, eapol_length))
+    {
+        bcmolt_buf_snap_restore(buf, snap);
+        return BCMOS_FALSE;
+    }
+
+    if ((eap_tls_flags & EAP_TLS_FLAG_LENGTH_BIT) == EAP_TLS_FLAG_LENGTH_BIT)
+    {
+        /* The TLS message length - take away the EapTlsHdr, AND the EapMsgHdr which, surprisingly was not removed in
+           the previous step. */
+        eap_tls_length = eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE + sizeof(uint32_t));
+
+        bcmolt_buf_snap_restore(buf, lf->eap_tls);
+        if (!bcmolt_buf_write_u32_be(buf, eap_tls_length))
+        {
+            bcmolt_buf_snap_restore(buf, snap);
+            return BCMOS_FALSE;
+        }
+
+        bcmolt_buf_snap_restore(buf, lf->tls_rec);
+        if (!bcmolt_buf_write_u16_be(buf, (uint16_t)eap_tls_length - TLS_RECORD_HDR_SIZE))
+        {
+            bcmolt_buf_snap_restore(buf, snap);
+            return BCMOS_FALSE;
+        }
+    }
+
+    bcmolt_buf_snap_restore(buf, snap);
+    return BCMOS_TRUE;
+}
+
+static bcmos_errno _dpoe_eap_tls_pack_start(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    bcmos_errno rc = BCM_ERR_NOMEM;
+    uint8_t flags = EAP_TLS_FLAG_START_BIT;
+    eap_length_fields lf;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (_dpoe_eap_tls_init_eap_hdr(link_rec, buf, EAP_CODE_REQUEST, &lf) &&
+        _dpoe_eap_tls_init_tls_hdr(buf, flags, &lf) &&
+        _dpoe_eap_tls_pack_msg_lengths(buf, flags, &lf))
+    {
+        rc = BCM_ERR_OK;
+    }
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_send_cert_request(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_bool rc = BCMOS_FALSE;
+    bcmos_errno err;
+    bcmolt_buf buf;
+    uint8_t *msg = NULL;
+    eap_length_fields lf;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    msg = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (msg == NULL)
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* Initialize the maximum supported length EAP-TLS message buffer. The various TLS records contained in an EAP-TLS
+       message are variable size. However, this can only be a combined maximum length. */
+    bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
+    if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
+        _dpoe_eap_tls_init_tls_hdr(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf))
+    {
+        /* The Cert request contains 3 or 4 TLS records (3 for DS-only and 4 for bi-directional). The TLS records are
+           ordered as:
+           - Server Hello
+           - Server Key Exchange (if bi-directional)
+           - Certificate Request
+           - Server Hello Done */
+        if (_dpoe_eap_tls_pack_server_hello_hs(link_rec, &buf) &&
+            _dpoe_eap_tls_pack_server_key_exchange_hs(link_rec, &buf) &&
+            _dpoe_eap_tls_pack_cert_req_hs(link_rec, &buf) &&
+            _dpoe_eap_tls_pack_server_hello_done_hs(link_rec, &buf))
+        {
+            _dpoe_eap_tls_pack_msg_lengths(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf);
+
+            /* Send the message */
+            err = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
+            if (err != BCM_ERR_OK)
+            {
+                bcmos_free(msg);
+                return BCMOS_FALSE;
+            }
+
+            /* Authentication has begun. */
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_EAP_START;
+            rc = BCMOS_TRUE;
+        }
+    }
+
+    /* Free the message. */
+    bcmos_free(msg);
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_eap_tls_send_finished_hs(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OVERFLOW;
+    bcmolt_buf buf;
+    uint8_t *msg = NULL;
+    eap_length_fields lf;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    msg = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (msg == NULL)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    /* Initialize the EAP-TLS message buffer. */
+    bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
+    if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
+        _dpoe_eap_tls_init_tls_hdr(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf) &&
+        _dpoe_eap_tls_pack_finished_hs(link_rec, &buf))
+    {
+        _dpoe_eap_tls_pack_msg_lengths(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf);
+        /* No error detection here. We have already authenticated. this message is just for standards compliance */
+        rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
+    }
+
+    /* Free the message. */
+    bcmos_free(msg);
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_eap_tls_send_result(dpoe_sec_link_rec *link_rec, eap_code result)
+{
+    uint8_t *msg;
+    bcmolt_buf buf;
+    bcmos_errno rc = BCM_ERR_INTERNAL;
+    eap_length_fields lf;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    msg = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (msg == NULL)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    /* Initialize the EAP-TLS message buffer. */
+    bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
+    /* Encode the EAP-Success message to the buffer. */
+    if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, result, &lf) &&
+        _dpoe_eap_tls_pack_msg_lengths(&buf, 0, &lf))
+    {
+        rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
+    }
+
+    bcmos_free(msg);
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_parse_certificate(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    bcmos_bool            rc = BCMOS_FALSE;
+    uint24_t length;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    link_rec->auth_ctrl.certificate = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (link_rec->auth_ctrl.certificate == NULL)
+    {
+        return BCMOS_FALSE;
+    }
+
+    if (bcmolt_buf_read_u24(buf, &length) &&
+        ((link_rec->auth_ctrl.certLen = (uint16_t)uint24_to_32(length)), (link_rec->auth_ctrl.certLen <= EAPOL_PKT_SIZE)) &&
+        bcmolt_buf_read(buf, link_rec->auth_ctrl.certificate, link_rec->auth_ctrl.certLen))
+    {
+        rc = BCMOS_TRUE;
+    }
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_parse_client_key_exchange(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    bcmos_bool rc = BCMOS_FALSE;
+    uint16_t length;
+    uint8_t encrypted_buf[SIZE_OF_RSA_ENCRYPTED_BLOCK] = {};
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (bcmolt_buf_read_u16_be(buf, &length) &&
+        (length == (SIZE_OF_RSA_ENCRYPTED_BLOCK)) &&
+        bcmolt_buf_read(buf, encrypted_buf, sizeof(encrypted_buf)))
+    {
+        int val;
+
+        val = dpoe_sec_rsa_private_decrypt(
+            sizeof(encrypted_buf),
+            encrypted_buf,
+            link_rec->auth_ctrl.trans_data.pre_master_secret,
+            link_rec->auth_ctrl.trans_data.rsa);
+
+        if (val == SIZE_OF_PRE_MASTER_SECRET)
+        {
+            /* We now need the security data - we have the "client random", "server random", and "pre master secret". */
+            _dpoe_eap_tls_prepare_security_data(link_rec);
+            rc = BCMOS_TRUE;
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "RSA decrypt pre-master secret failed: %d\n", val);
+        }
+    }
+    else
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to parse client key exchange: %u\n", length);
+    }
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_parse_cert_verify(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    uint16_t length;
+    bcmos_bool ret = BCMOS_FALSE;
+
+    if (bcmolt_buf_read_u16_be(buf, &length) &&
+        (length <= (2 * SIZE_OF_RSA_ENCRYPTED_BLOCK)))
+    {
+        bcmolt_buf cert_buf;
+        uint24_t temp;
+        const uint8_t *cert;
+        uint32_t cert_len = 0;
+        uint8_t cert_verify[sizeof(dpoe_sec_sha1_digest) + sizeof(dpoe_sec_md5_digest)];
+        uint8_t *enc_buf;
+        dpoe_sec_rsa_key *rsa;
+
+        bcmolt_buf_init(&cert_buf, link_rec->auth_ctrl.certLen, link_rec->auth_ctrl.certificate, BCMOLT_BUF_ENDIAN_FIXED);
+
+        /* Get the pointer to the ONU cert and its length */
+        bcmolt_buf_read_u24(&cert_buf, &temp);
+        cert_len = uint24_to_32(temp);
+        cert = bcmolt_buf_snap_get(&cert_buf);
+
+        enc_buf = bcmolt_buf_snap_get(buf);
+        bcmolt_buf_skip(buf, length);
+
+        /* Get the public key from the ONU cert. */
+        rsa = dpoe_sec_x509_pub_key_get(cert, cert_len);
+
+        /* decrypt the remainder of the buf with the ONU cert. */
+        if (rsa != NULL)
+        {
+            if (dpoe_sec_rsa_public_decrypt(length, enc_buf, cert_verify, rsa) >= 0)
+            {
+                /* RSA_size() returns the size of the modulus in bytes. Convert to bits. */
+                link_rec->auth_ctrl.onu_cert_key_size = dpoe_sec_rsa_size(rsa) * 8;
+
+                /* Compare the output (should be 36 bytes) to sha1/md5 messages digests If they match, we are good */
+                if ((memcmp(&cert_verify[0],
+                            link_rec->auth_ctrl.trans_data.md5_digest,
+                            sizeof(dpoe_sec_md5_digest)) == 0) &&
+                    (memcmp(&cert_verify[sizeof(dpoe_sec_md5_digest)],
+                            link_rec->auth_ctrl.trans_data.sha1_digest,
+                            sizeof(dpoe_sec_sha1_digest)) == 0))
+                {
+                    bcmolt_buf_init(&cert_buf, link_rec->auth_ctrl.certLen, link_rec->auth_ctrl.certificate, BCMOLT_BUF_ENDIAN_FIXED);
+
+                    /* Get the pointer to the ONU certificate. */
+                    bcmolt_buf_read_u24(&cert_buf, &temp);
+                    link_rec->auth_ctrl.onu_cert_len = uint24_to_32(temp);
+                    link_rec->auth_ctrl.onu_cert = bcmolt_buf_snap_get(&cert_buf);
+
+                    bcmolt_buf_skip(&cert_buf, link_rec->auth_ctrl.onu_cert_len);
+
+                    /* Get the pointer to the Manufacturer CA certificate. */
+                    bcmolt_buf_read_u24(&cert_buf, &temp);
+                    link_rec->auth_ctrl.mfg_cert_len = uint24_to_32(temp);
+                    link_rec->auth_ctrl.mfg_cert = bcmolt_buf_snap_get(&cert_buf);
+
+                    ret = BCMOS_TRUE;
+                }
+                else
+                {
+                    DPOE_SEC_LINK_LOG(ERROR, link_rec, "Cert Verify failed\n");
+                }
+            }
+            else
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "RSA public decrypt/verify failed\n");
+            }
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to retrieve RSA key\n");
+        }
+
+        dpoe_sec_rsa_key_free(rsa);
+    }
+
+    return ret;
+}
+
+static bcmos_bool _dpoe_eap_tls_parse_verify_data(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_bool rc = BCMOS_FALSE;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
+    {
+        /* This version of authentication does not parse verify data */
+        rc = BCMOS_TRUE;
+    }
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_parse_client_hello(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    tls_protocol_version version;
+    tls_session_id session_id;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (bcmolt_buf_read_u8(buf, &version.major) &&
+        (version.major == TLS_MAJOR_VERSION) &&
+        bcmolt_buf_read_u8(buf, &version.minor) &&
+        (version.minor >= TLS_MINOR_VERSION) &&
+        bcmolt_buf_read(buf, link_rec->auth_ctrl.trans_data.client_random, COUNT_OF_RANDOM_BYTES) &&
+        bcmolt_buf_read_u8(buf, &session_id.length) &&
+        (session_id.length <= sizeof(session_id.session_id)) &&
+        bcmolt_buf_read(buf, session_id.session_id, session_id.length))
+    {
+        compression_methods comp_methods;
+        uint16_t tlvLen;
+        uint8_t cipher_suites[2];
+        uint8_t *cm_snap = bcmolt_buf_snap_get(buf);
+
+        memset(&comp_methods, 0, sizeof(comp_methods));
+
+        /* Cipher Suites may be present -if so parse over it and it will be ignored. Still check that comp methods is
+           NULL */
+        if (bcmolt_buf_read_u16_be(buf, &tlvLen) && /* Cipher Suites */
+            (tlvLen <= sizeof(uint16_t)) &&
+            bcmolt_buf_read(buf, cipher_suites, tlvLen) &&
+            bcmolt_buf_read_u8(buf, &comp_methods.length) && /* Compression Methods */
+            (comp_methods.length == sizeof(comp_methods.compression_method)) &&
+            bcmolt_buf_read(buf, &comp_methods.compression_method, comp_methods.length) &&
+            (comp_methods.compression_method == cm_null) )
+        {
+            link_rec->auth_ctrl.version.major = version.major;
+            link_rec->auth_ctrl.version.minor = version.minor;
+            return BCMOS_TRUE;
+        }
+
+        /* No cipher suites, check that comp method is null */
+        memset(&comp_methods, 0, sizeof(comp_methods));
+        bcmolt_buf_snap_restore(buf, cm_snap);
+        if (bcmolt_buf_read_u8(buf, &comp_methods.length) &&  /* Compression Methods */
+           (comp_methods.length == sizeof(comp_methods.compression_method)) &&
+           bcmolt_buf_read(buf, &comp_methods.compression_method, comp_methods.length) &&
+           (comp_methods.compression_method == cm_null) )
+        {
+            link_rec->auth_ctrl.version.major = version.major;
+            link_rec->auth_ctrl.version.minor = version.minor;
+            return BCMOS_TRUE;
+        }
+
+        /* Ok we failed to decode what was sent as cipher suites and comp methods. Do we care? Absolutely Not! */
+        link_rec->auth_ctrl.version.major = version.major;
+        link_rec->auth_ctrl.version.minor = version.minor;
+        return BCMOS_TRUE;
+    }
+
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_handle_client_hello_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (_dpoe_eap_tls_parse_client_hello(link_rec, buf))
+    {
+        /* We now have the data, build the EAP session ID (see RFC 5216) */
+        link_rec->auth_ctrl.trans_data.session_id[0] = 0xd;
+        memcpy(
+            &link_rec->auth_ctrl.trans_data.session_id[1],
+            link_rec->auth_ctrl.trans_data.client_random,
+            COUNT_OF_RANDOM_BYTES);
+        memcpy(
+            &link_rec->auth_ctrl.trans_data.session_id[COUNT_OF_RANDOM_BYTES + 1],
+            link_rec->auth_ctrl.trans_data.server_random,
+            COUNT_OF_RANDOM_BYTES);
+
+        if (_dpoe_eap_tls_send_cert_request(link_rec))
+        {
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_REQUEST;
+            return BCMOS_TRUE;
+        }
+    }
+
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_handle_certificate_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (_dpoe_eap_tls_parse_certificate(link_rec, buf))
+    {
+        if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
+        {
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_RECEIVED;
+            return BCMOS_TRUE;
+        }
+    }
+    return BCMOS_FALSE;
+}
+
+/******************************************************************************/
+static bcmos_bool _dpoe_eap_tls_handle_client_key_exchange_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    if (_dpoe_eap_tls_parse_client_key_exchange(link_rec, buf))
+    {
+        if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
+        {
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CLIENT_KEY_RECEIVED;
+            return BCMOS_TRUE;
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "Wrong TLS version in client key exchange: %u\n",
+                              link_rec->auth_ctrl.version.minor);
+        }
+    }
+    else
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to parse client key exchange\n");
+    }
+
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool _dpoe_eap_tls_handle_cert_verify_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    bcmos_bool success;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+
+    success = _dpoe_eap_tls_parse_cert_verify(link_rec, buf);
+
+    if (success)
+    {
+        link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_VALIDATED;
+    }
+    else
+    {
+        link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
+        if (_auth_complete != NULL)
+        {
+            _auth_complete(link_rec, BCM_ERR_ONU_ERR_RESP);
+        }
+    }
+
+    return success;
+}
+
+static bcmos_bool _dpoe_eap_tls_handle_finished_hs(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_bool success;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    success = _dpoe_eap_tls_parse_verify_data(link_rec);
+
+    /* And send the final Finished Hs to the ONU */
+    if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
+    {
+        _dpoe_eap_tls_send_finished_hs(link_rec);
+    }
+
+    if (success)
+    {
+        if ((_cert_trust == NULL) || (_cert_trust(link_rec)))
+        {
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_AUTHENTICATED;
+            _dpoe_eap_tls_send_result(link_rec, EAP_CODE_SUCCESS);
+            if (_auth_complete != NULL)
+            {
+                _auth_complete(link_rec, BCM_ERR_OK);
+            }
+        }
+        else
+        {
+            link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
+            _dpoe_eap_tls_send_result(link_rec, EAP_CODE_FAILURE);
+            success = BCMOS_FALSE;
+            if (_auth_complete != NULL)
+            {
+                _auth_complete(link_rec, BCM_ERR_INTERNAL);
+            }
+        }
+    }
+    else
+    {
+        /* Reset ALL the state variables! */
+        memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
+    }
+
+    return success;
+}
+
+static bcmos_bool _dpoe_eap_tls_alloc_tls_frag_buffer(onu_auth_control *auth_ctrl)
+{
+    /* Parameter checks. */
+    BUG_ON(auth_ctrl == NULL);
+
+    if (auth_ctrl->tls_frag_buffer == NULL)
+    {
+        auth_ctrl->tls_frag_buffer = bcmos_calloc(EAPOL_PKT_SIZE);
+        auth_ctrl->tls_frag_length = 0;
+        auth_ctrl->tls_total_length = 0;
+    }
+
+    if (auth_ctrl->tls_frag_buffer == NULL)
+    {
+        return BCMOS_FALSE;
+    }
+
+    return BCMOS_TRUE;
+}
+
+static void _dpoe_eap_tls_free_tls_frag_buffer(onu_auth_control *auth_ctrl)
+{
+    /* Parameter checks. */
+    BUG_ON(auth_ctrl == NULL);
+
+    if (auth_ctrl->tls_frag_buffer != NULL)
+    {
+        bcmos_free(auth_ctrl->tls_frag_buffer);
+        auth_ctrl->tls_frag_buffer = NULL;
+        auth_ctrl->tls_frag_length = 0;
+        auth_ctrl->tls_total_length = 0;
+    }
+}
+
+static bcmos_errno _dpoe_eap_tls_send_tls_frag_ack_to_onu(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc;
+    bcmolt_buf buf;
+    uint8_t *msg;
+    eap_length_fields lf;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    /* Allocate a message buffer */
+    msg = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (NULL == msg)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    /* Initialize the EAP-TLS buffer. */
+    bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
+    if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
+        _dpoe_eap_tls_init_tls_hdr(&buf, 0, &lf) &&
+        _dpoe_eap_tls_pack_msg_lengths(&buf, 0, &lf))
+    {
+        /* Send the message */
+        rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
+        if (rc != BCM_ERR_OK)
+        {
+            bcmos_free(msg);
+            return rc;
+        }
+    }
+    else
+    {
+        rc = BCM_ERR_OVERFLOW;
+    }
+
+    bcmos_free(msg);
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_unpack_tls_handshake(bcmolt_buf *buf, tls_handshake* hs)
+{
+    uint24_t temp;
+    bcmos_bool rc = BCMOS_FALSE;
+
+    if (bcmolt_buf_read_u8(buf, &hs->msg_id) &&
+        bcmolt_buf_read_u24(buf, &temp))
+    {
+        hs->msg_length = uint24_to_32(temp);
+        rc = BCMOS_TRUE;
+    }
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_eap_tls_run_auth_state_machine(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    uint8_t *hs_snap;
+    tls_handshake handshake;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    while (hs_snap = bcmolt_buf_snap_get(buf), _dpoe_eap_tls_unpack_tls_handshake(buf, &handshake))
+    {
+        bcmos_bool success = BCMOS_TRUE;
+
+        if (handshake.msg_length > EAPOL_PKT_SIZE)
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "Handshake length is insane: msg %u, len %u\n",
+                              handshake.msg_id, handshake.msg_length);
+            return BCM_ERR_PARSE;
+        }
+
+        if ((handshake.msg_id == HS_CLIENT_HELLO) &&
+            (link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_EAP_START))
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            _buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
+            success = _dpoe_eap_tls_handle_client_hello_hs(link_rec, buf);
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
+        }
+        else if ((handshake.msg_id == HS_CERTIFICATE) &&
+                 (link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_REQUEST))
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            _buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
+            success = _dpoe_eap_tls_handle_certificate_hs(link_rec, buf);
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
+        }
+        else if ((handshake.msg_id == HS_CLIENT_KEY_EXCHANGE) &&
+                 (link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_RECEIVED))
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            _buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
+            success = _dpoe_eap_tls_handle_client_key_exchange_hs(link_rec, buf);
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
+        }
+        else if ((handshake.msg_id == HS_CERTIFICATE_VERIFY) &&
+                 ((link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_RECEIVED) ||
+                  (link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CLIENT_KEY_RECEIVED)))
+
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            dpoe_sec_sha1_final(link_rec->auth_ctrl.trans_data.sha1_digest, &link_rec->auth_ctrl.trans_data.sha1_hash);
+            dpoe_sec_md5_final(link_rec->auth_ctrl.trans_data.md5_digest, &link_rec->auth_ctrl.trans_data.md5_hash);
+            success = _dpoe_eap_tls_handle_cert_verify_hs(link_rec, buf);
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
+        }
+        else if ((handshake.msg_id == HS_FINISHED) &&
+                 (link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_VALIDATED))
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            success = _dpoe_eap_tls_handle_finished_hs(link_rec);
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
+                              handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
+            success = BCMOS_FALSE;
+        }
+
+        if (!success)
+        {
+            /* Something went wrong, just abort... */
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS handshake processing failed: %u\n", handshake.msg_id);
+            rc = BCM_ERR_PARSE;
+        }
+    }
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_unpack_tls_record_hdr(bcmolt_buf *buf, tls_record_hdr *msg)
+{
+    return bcmolt_buf_read_u8(buf, &msg->content_type) &&
+        bcmolt_buf_read_u8(buf, &msg->tls_version.major) &&
+        bcmolt_buf_read_u8(buf, &msg->tls_version.minor) &&
+        bcmolt_buf_read_u16_be(buf, &msg->tls_record_length);
+}
+
+static bcmos_errno _dpoe_eap_tls_process_tls_records(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    bcmolt_buf buf;
+    tls_record_hdr tls_hdr;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    bcmolt_buf_init(&buf, link_rec->auth_ctrl.tls_frag_length, link_rec->auth_ctrl.tls_frag_buffer, BCMOLT_BUF_ENDIAN_FIXED);
+    while ((rc == BCM_ERR_OK) && _dpoe_eap_tls_unpack_tls_record_hdr(&buf, &tls_hdr))
+    {
+        rc = _dpoe_eap_tls_run_auth_state_machine(link_rec, &buf);
+    }
+
+    return rc;
+}
+
+static bcmos_bool _dpoe_eap_tls_check_tls_version(const tls_record_hdr *tls_rec_hdr)
+{
+    /* Parameter checks. */
+    BUG_ON(tls_rec_hdr == NULL);
+
+    if ((tls_rec_hdr->content_type != TLS_HANDSHAKE_ID) ||
+        (tls_rec_hdr->tls_version.major != TLS_MAJOR_VERSION) ||
+        (tls_rec_hdr->tls_version.minor < TLS_MINOR_VERSION))
+    {
+        return BCMOS_TRUE;
+    }
+    return BCMOS_FALSE;
+}
+
+static void _dpoe_eap_tls_buffer_tls_data(dpoe_sec_link_rec *link_rec, const void *data, uint32_t length)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(data == NULL);
+
+    /* do not overrun the buffer, no matter how much data received */
+    if ((link_rec->auth_ctrl.tls_frag_length + length) > EAPOL_PKT_SIZE)
+    {
+        length = EAPOL_PKT_SIZE - link_rec->auth_ctrl.tls_frag_length;
+    }
+
+    memcpy(&link_rec->auth_ctrl.tls_frag_buffer[link_rec->auth_ctrl.tls_frag_length], data, length);
+    link_rec->auth_ctrl.tls_frag_length += length;
+}
+
+static bcmos_bool _dpoe_eap_tls_unpack_msg(bcmolt_buf *buf, eapol_msg *msg)
+{
+    return dpoe_sec_eapol_unpack(buf, &msg->hdr) &&
+        bcmolt_buf_read_u8(buf, &msg->eap_msg.hdr.eap_code) &&
+        bcmolt_buf_read_u8(buf, &msg->eap_msg.hdr.id) &&
+        bcmolt_buf_read_u16_be(buf, &msg->eap_msg.hdr.length) &&
+        bcmolt_buf_read_u8(buf, &msg->eap_msg.tls.auth_sub_type) &&
+        bcmolt_buf_read_u8(buf, &msg->eap_msg.tls.eap_tls_flags);
+}
+
+static bcmos_errno _dpoe_eap_tls_gen_key(auth_trans_data *trans_data)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    dpoe_sec_rsa_key *rsa = NULL;
+
+    /* Parameter checks */
+    BUG_ON(trans_data == NULL);
+
+    /* Generate the RSA key to be used in the ServerKeyExchange and ClientKeyExchange */
+    rsa = dpoe_sec_rsa_generate_key(DPOE_BI_RSA_KEY_SIZE);
+    if (rsa == NULL)
+    {
+        rc = BCM_ERR_INTERNAL;
+    }
+    else
+    {
+        /* Store the pointer to the key. */
+        trans_data->rsa = rsa;
+    }
+
+    return rc;
+}
+
+bcmos_errno dpoe_eap_tls_send_start(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    bcmolt_buf buf;
+    uint8_t *msg;
+    time_t unix_time;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    /* Initialize the authentication control data. */
+    memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
+    dpoe_sec_sha1_init(&link_rec->auth_ctrl.trans_data.sha1_hash);
+    dpoe_sec_md5_init(&link_rec->auth_ctrl.trans_data.md5_hash);
+
+    /* Per standard, the first four bytes of the server random value should be "unix time" */
+    unix_time = time(NULL);
+    memcpy(link_rec->auth_ctrl.trans_data.server_random, &unix_time, sizeof(unix_time));
+    dpoe_sec_generate_n_byte_random_number(&link_rec->auth_ctrl.trans_data.server_random[sizeof(unix_time)], COUNT_OF_RANDOM_BYTES - sizeof(unix_time));
+
+    /* If bidirectional, generate the RSA public/private key pair for the ServerKeyExchange and ClientKeyExchange. */
+    if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+    {
+        rc = _dpoe_eap_tls_gen_key(&link_rec->auth_ctrl.trans_data);
+        BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+    }
+
+    /* Allocate a message buffer */
+    msg = bcmos_calloc(EAPOL_PKT_SIZE);
+    if (NULL == msg)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    /* Initialize the EAP-TLS buffer. */
+    bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
+
+    /* Encode the EAP-TLS Start message to the buffer. */
+    _dpoe_eap_tls_pack_start(link_rec, &buf);
+
+    /* Send the message */
+    rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
+    bcmos_free(msg);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+
+    /* Authentication has begun. */
+    link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_EAP_START;
+
+    return rc;
+}
+
+bcmos_errno dpoe_eap_tls_process_eapol_pkt(dpoe_sec_link_rec *link_rec, uint8_t *msg, uint32_t msg_len)
+{
+    bcmos_errno rc = BCM_ERR_INTERNAL;
+    uint32_t eap_tls_length;
+    tls_record_hdr tls_rec_hdr;
+    eapol_msg unpacked_msg;
+    bcmolt_buf buf;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(msg == NULL);
+
+    bcmolt_buf_init(&buf, msg_len, msg, BCMOLT_BUF_ENDIAN_FIXED);
+    _dpoe_eap_tls_unpack_msg(&buf, &unpacked_msg);
+
+    if (unpacked_msg.eap_msg.tls.auth_sub_type == EAP_TYPE_NAK)
+    {
+        link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
+        return BCM_ERR_ONU_ERR_RESP;
+    }
+
+    if (unpacked_msg.hdr.eapol_length != unpacked_msg.eap_msg.hdr.length)
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    /* We should only read the length if the length bit is set. */
+    if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_LENGTH_BIT) != 0)
+    {
+        bcmolt_buf_read_u32_be(&buf, &eap_tls_length);
+        uint8_t *tls_data = bcmolt_buf_snap_get(&buf);
+        _dpoe_eap_tls_unpack_tls_record_hdr(&buf, &tls_rec_hdr);
+        if (_dpoe_eap_tls_check_tls_version(&tls_rec_hdr))
+        {
+            /* Invalid TLS record, discard... */
+            return BCM_ERR_PARSE;
+        }
+
+        /* Allocate the fragmentation buffer. Multiple calls to the frag alloc function is allowed since it allocates a
+           new buffer only if there is no frag buffer currently allocated. The frag buffer is also freed if the EAP-TLS
+           session goes down for some reason. */
+        if (!_dpoe_eap_tls_alloc_tls_frag_buffer(&link_rec->auth_ctrl))
+        {
+            return BCM_ERR_NOMEM;
+        }
+
+        /* If the length bit is set, and the more bit is not, this is likely a single fragment packet.  In that case
+           the TlsMsg length must be present, and match the size of the buffer. It is also possible that this is a
+           subsequent fragment and the client always adds the length (which per standard is the length of the set of
+           fragments), in which case we cannot validate this now. The best we can do is save this size, and validate
+           when all fragments have been received. */
+        if (link_rec->auth_ctrl.tls_total_length == 0)
+        {
+            link_rec->auth_ctrl.tls_total_length = eap_tls_length;
+        }
+
+        if (link_rec->auth_ctrl.tls_total_length != eap_tls_length)
+        {
+            return BCM_ERR_PARSE;
+        }
+
+        _dpoe_eap_tls_buffer_tls_data(
+            link_rec,
+            tls_data,
+            unpacked_msg.hdr.eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE + sizeof(uint32_t)));
+
+        if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_MORE_BIT) != 0)
+        {
+            /* Need to acknowledge the fragment */
+            _dpoe_eap_tls_send_tls_frag_ack_to_onu(link_rec);
+            rc = BCM_ERR_IN_PROGRESS;
+        }
+        else
+        {
+            /* Either a single fragment packet, or the last fragment of a multi-fragment packet. If it is the last
+               fragment of a multi-fragment, then all we can check is that eapTlsLength is one TlsRecordHdr bigger than
+               the buffered (authCtrl->tlsFragLen) data stream. The same logic works on a single frag packet. */
+            if ((eap_tls_length != link_rec->auth_ctrl.tls_total_length) ||
+                (eap_tls_length != link_rec->auth_ctrl.tls_frag_length))
+            {
+                return BCM_ERR_PARSE;
+            }
+
+            rc = _dpoe_eap_tls_process_tls_records(link_rec);
+            _dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
+        }
+    }
+    else
+    {
+        uint8_t *tls_data = bcmolt_buf_snap_get(&buf);
+
+        /* No length field - must be fragmented, and not first */
+        if (link_rec->auth_ctrl.tls_frag_buffer == NULL)
+        {
+            return BCM_ERR_OUT_OF_SYNC;
+        }
+
+        _dpoe_eap_tls_buffer_tls_data(
+            link_rec,
+            tls_data,
+            unpacked_msg.hdr.eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE));
+
+        /* If the more bit is set, wait for the next fragment. Otherwise dump what we have into the state machine. */
+        if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_MORE_BIT) != 0)
+        {
+            /* Need to acknowledge the fragment? The standard does not define a message set that will get us here. */
+            _dpoe_eap_tls_send_tls_frag_ack_to_onu(link_rec);
+            rc = BCM_ERR_IN_PROGRESS;
+        }
+        else
+        {
+            if (link_rec->auth_ctrl.tls_total_length != link_rec->auth_ctrl.tls_frag_length)
+            {
+                return BCM_ERR_PARSE;
+            }
+
+            rc = _dpoe_eap_tls_process_tls_records(link_rec);
+            _dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
+        }
+    }
+
+    return rc;
+}
+
+void dpoe_eap_tls_cleanup(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    // We are done with this.
+    if (link_rec->auth_ctrl.certificate != NULL)
+    {
+        bcmos_free(link_rec->auth_ctrl.certificate);
+    }
+
+    if (link_rec->auth_ctrl.trans_data.rsa != NULL)
+    {
+        dpoe_sec_rsa_key_free(link_rec->auth_ctrl.trans_data.rsa);
+        link_rec->auth_ctrl.trans_data.rsa = NULL;
+    }
+
+    link_rec->auth_ctrl.certLen = 0;
+
+    _dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
+
+    memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
+}
+
+bcmos_errno dpoe_eap_tls_init(f_dpoe_sec_auth_cb auth_cb, f_dpoe_sec_cert_trust_cb cert_trust_cb)
+{
+    _auth_complete = auth_cb;
+    _cert_trust = cert_trust_cb;
+
+    return dpoe_sec_rng_seed();
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.h
new file mode 100644
index 0000000..984d555
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_eap_tls.h
@@ -0,0 +1,122 @@
+/*
+<: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.
+
+:>
+ */
+
+#if !defined(_DPOE_EAP_TLS_H_)
+#define _DPOE_EAP_TLS_H_
+
+#include "bcmos_system.h"
+#include "dpoe_sec_util.h"
+
+#define COUNT_OF_RANDOM_BYTES 32
+#define SIZE_OF_MASTER_SECRET 48
+#define SIZE_OF_MASTER_SESSION_KEY 64
+#define SIZE_OF_KEY_MATERIAL 128
+#define SIZE_OF_RSA_ENCRYPTED_BLOCK 256
+#define SIZE_OF_EAP_SESSION_ID 65
+#define SIZE_OF_TLS_SESSION_ID 32
+
+/* Total length of the PreMasterSecret */
+#define SIZE_OF_PRE_MASTER_SECRET 48
+
+typedef enum
+{
+    DPOE_ONU_AUTH_STATE_UNAUTHENTICATED,
+    DPOE_ONU_AUTH_STATE_EAP_START,
+    DPOE_ONU_AUTH_STATE_CERT_REQUEST,
+    DPOE_ONU_AUTH_STATE_CLIENT_KEY_RECEIVED,
+    DPOE_ONU_AUTH_STATE_CERT_RECEIVED,
+    DPOE_ONU_AUTH_STATE_CERT_VALIDATED,
+    DPOE_ONU_AUTH_STATE_AUTHENTICATED,
+    DPOE_ONU_AUTH_STATE_FAILED
+} dpoe_onu_auth_state;
+
+typedef struct
+{
+    uint8_t major;
+    uint8_t minor;
+} tls_protocol_version;
+
+#define PROTOCOL_VERSION_SIZE 2
+
+/* DPoE Authentication transient data */
+typedef struct
+{
+    uint8_t client_random[COUNT_OF_RANDOM_BYTES];
+    uint8_t server_random[COUNT_OF_RANDOM_BYTES];
+    uint8_t session_id[SIZE_OF_EAP_SESSION_ID];
+    uint8_t master_secret[SIZE_OF_MASTER_SECRET];
+    uint8_t key_material[SIZE_OF_KEY_MATERIAL];
+    uint8_t pre_master_secret[SIZE_OF_PRE_MASTER_SECRET];
+    dpoe_sec_sha1_hash sha1_hash;
+    dpoe_sec_sha1_digest sha1_digest;
+    dpoe_sec_md5_hash md5_hash;
+    dpoe_sec_md5_digest md5_digest;
+    /*  Allow the key to double in size before we break. */
+    uint8_t encrypted_cak[2 * SIZE_OF_RSA_ENCRYPTED_BLOCK];
+    uint8_t master_session_key[SIZE_OF_MASTER_SESSION_KEY];
+    dpoe_sec_rsa_key *rsa;
+} auth_trans_data;
+
+typedef struct onu_auth_control
+{
+    dpoe_onu_auth_state onu_auth_state;
+    tls_protocol_version version;
+    uint32_t current_packet_id;
+    uint8_t *certificate;
+    uint16_t certLen;
+    uint8_t *onu_cert;
+    uint16_t onu_cert_len;
+    uint32_t onu_cert_key_size;
+    uint8_t *mfg_cert;
+    uint16_t mfg_cert_len;
+    uint8_t *tls_frag_buffer;
+    uint32_t tls_frag_length;
+    uint32_t tls_total_length;
+    auth_trans_data trans_data;
+} onu_auth_control;
+
+struct dpoe_sec_link_rec;
+
+/* callbacks */
+
+typedef void (*f_dpoe_sec_auth_cb)(struct dpoe_sec_link_rec*, bcmos_errno status);
+
+typedef bcmos_bool (*f_dpoe_sec_cert_trust_cb)(struct dpoe_sec_link_rec*);
+
+/* functions */
+
+bcmos_errno dpoe_eap_tls_send_start(struct dpoe_sec_link_rec *link);
+
+bcmos_errno dpoe_eap_tls_process_eapol_pkt(struct dpoe_sec_link_rec *link, uint8_t *msg, uint32_t msg_len);
+
+void dpoe_eap_tls_cleanup(struct dpoe_sec_link_rec *link);
+
+bcmos_errno dpoe_eap_tls_init(f_dpoe_sec_auth_cb auth_cb, f_dpoe_sec_cert_trust_cb cert_trust_cb);
+
+#endif /* _DPOE_EAP_TLS_H_ */
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.c
new file mode 100644
index 0000000..a6c96de
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.c
@@ -0,0 +1,826 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "dpoe_sec_fsm.h"
+#include "dpoe_sec_util.h"
+#include "bcmos_hash_table.h"
+#include "bcmolt_epon_oam_types.h"
+#include "bcmolt_utils.h"
+#include "bcmolt_user_appl_epon_oam.h"
+#include "bcmolt_api.h"
+
+#define DPOE_SEC_FSM_MAX_LINKS 64
+#define DPOE_SEC_FSM_TASK_MSG_Q_SIZE 64
+
+static const uint16_t DPOE_SEC_FSM_DEFAULT_OAM_KEY_EXCHANGE_PERIOD = 600; /* 10 minutes */
+/**
+ * @brief  Maximum ONU Authentication timeout value
+ * DPoE specification indicates that this value is 5 minutes.
+ */
+static const uint32_t DPOE_SEC_AUTH_TIMEOUT = 5 * 60 * 1000 * 1000;
+
+typedef struct
+{
+    bcmos_task task;
+    char task_name[MAX_TASK_NAME_SIZE];
+    char msg_queue_name[MAX_MSG_QUEUE_NAME_SIZE];
+    hash_table *link_map;
+} dpoe_sec_fsm_ctxt;
+
+dpoe_sec_fsm_ctxt ctxt[BCMTR_MAX_OLTS];
+
+typedef void (*f_dpoe_sec_fsm)(dpoe_sec_link_rec*, dpoe_sec_fsm_event_data*);
+
+/* DPoE Security FSM Event type to string definitions used for debug output */
+static const char *dpoe_sec_fsm_event_name[DPOE_SEC_FSM_EVENT__COUNT] =
+{
+    [DPOE_SEC_FSM_EVENT_ENCRYPT_START] = "Start Encryption",
+    [DPOE_SEC_FSM_EVENT_AUTH_START] = "Start Authentication",
+    [DPOE_SEC_FSM_EVENT_RX_OAM] = "Receive OAM",
+    [DPOE_SEC_FSM_EVENT_RX_EAPOL] = "Receive EAPOL",
+    [DPOE_SEC_FSM_EVENT_AUTH_COMPLETE] = "Authentication Complete",
+    [DPOE_SEC_FSM_EVENT_MKA_COMPLETE] = "MKA Complete",
+    [DPOE_SEC_FSM_EVENT_TIMEOUT] = "Timeout"
+};
+
+
+/* DPoE Security FSM State to string definitions used for debug output */
+static const char *dpoe_sec_fsm_state_name[DPOE_SEC_FSM_STATE__COUNT] =
+{
+    [DPOE_SEC_FSM_STATE_IDLE] = "Idle",
+    [DPOE_SEC_FSM_STATE_AUTH_WAIT] = "Authentication in progress",
+    [DPOE_SEC_FSM_STATE_ENCRYPT_DOWN_WAIT] = "OAM Key Exchange in progress",
+    [DPOE_SEC_FSM_STATE_ENCRYPT_BI_WAIT] = "MKA in progress",
+    [DPOE_SEC_FSM_STATE_COMPLETE] = "Authentication/Encryption Complete"
+};
+
+static f_dpoe_sec_fsm_cb _fsm_complete;
+
+static bcmos_bool _dpoe_sec_fsm_event_type_is_valid(dpoe_sec_fsm_event type)
+{
+    return type < DPOE_SEC_FSM_EVENT__COUNT;
+}
+
+static bcmos_bool _dpoe_sec_fsm_state_is_valid(dpoe_sec_fsm_state state)
+{
+    return state < DPOE_SEC_FSM_STATE__COUNT;
+}
+
+static const char *_dpoe_sec_fsm_event_name(dpoe_sec_fsm_event type)
+{
+    if (_dpoe_sec_fsm_event_type_is_valid(type))
+    {
+        return dpoe_sec_fsm_event_name[type];
+    }
+    else
+    {
+        return "Unknown Event";
+    }
+}
+
+static const char *_dpoe_sec_fsm_state_name(dpoe_sec_fsm_state state)
+{
+    if (_dpoe_sec_fsm_state_is_valid(state))
+    {
+        return dpoe_sec_fsm_state_name[state];
+    }
+    else
+    {
+        return "Unknown State";
+    }
+}
+
+static void _dpoe_sec_fsm_complete(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    if (_fsm_complete != NULL)
+    {
+        _fsm_complete(link_rec->device, link_rec->key, link_rec->status);
+    }
+}
+
+static void _dpoe_sec_fsm_success(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    link_rec->fsm_state = DPOE_SEC_FSM_STATE_COMPLETE;
+    link_rec->status = BCM_ERR_OK;
+
+    switch (link_rec->cfg.enc_mode)
+    {
+        case BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_NONE:
+        case BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI:
+            break;
+        case BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_ONE_DOWN:
+        case BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_DOWN:
+            bcmos_timer_start(
+                &link_rec->timeout,
+                (DPOE_SEC_FSM_DEFAULT_OAM_KEY_EXCHANGE_PERIOD * 1000 * 1000) + DPOE_SEC_OAM_TIMEOUT);
+            break;
+        default:
+            BUG();
+    }
+
+    _dpoe_sec_fsm_complete(link_rec);
+}
+
+static void _dpoe_sec_fsm_stop(dpoe_sec_link_rec *link_rec, bcmos_errno reason)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+
+    link_rec->fsm_state = DPOE_SEC_FSM_STATE_IDLE;
+    link_rec->status = reason;
+
+    bcmos_timer_stop(&link_rec->timeout);
+
+    /* Cleanup any EAP-TLS state. */
+    dpoe_eap_tls_cleanup(link_rec);
+
+    _dpoe_sec_fsm_complete(link_rec);
+}
+
+static void _dpoe_sec_fsm_auth_start(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    /* Start ONU Authentication */
+    rc = dpoe_eap_tls_send_start(link_rec);
+    if (BCM_ERR_OK != rc)
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to start authentication: %s (%d)\n", bcmos_strerror(rc), rc);
+        _dpoe_sec_fsm_stop(link_rec, rc);
+    }
+    else
+    {
+        link_rec->fsm_state = DPOE_SEC_FSM_STATE_AUTH_WAIT;
+        bcmos_timer_start(&link_rec->timeout, DPOE_SEC_AUTH_TIMEOUT);
+        DPOE_SEC_LINK_LOG(DEBUG, link_rec, "Starting authentication\n");
+    }
+}
+
+static void _dpoe_sec_fsm_mka_start(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = dpoe_sec_mka_fsm_start(link_rec);
+    if (BCM_ERR_OK != rc)
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to start MKA: %s (%d)\n", bcmos_strerror(rc), rc);
+        _dpoe_sec_fsm_stop(link_rec, rc);
+    }
+    else
+    {
+        link_rec->fsm_state = DPOE_SEC_FSM_STATE_ENCRYPT_BI_WAIT;
+        DPOE_SEC_LINK_LOG(DEBUG, link_rec, "Starting MKA\n");
+    }
+}
+
+static void _dpoe_sec_fsm_encrypt_start(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+    {
+        _dpoe_sec_fsm_mka_start(link_rec);
+    }
+    else
+    {
+        rc = epon_oam_dpoe_set_encryption(
+            link_rec->device,
+            link_rec->key.epon_ni,
+            &link_rec->key.mac_address,
+            link_rec->cfg.enc_mode,
+            DPOE_SEC_FSM_DEFAULT_OAM_KEY_EXCHANGE_PERIOD);
+        if (BCM_ERR_OK != rc)
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to set encryption: %s (%d)\n", bcmos_strerror(rc), rc);
+            _dpoe_sec_fsm_stop(link_rec, rc);
+        }
+        else
+        {
+            link_rec->fsm_state = DPOE_SEC_FSM_STATE_ENCRYPT_DOWN_WAIT;
+            bcmos_timer_start(&link_rec->timeout, DPOE_SEC_OAM_TIMEOUT);
+        }
+    }
+}
+
+static void _dpoe_sec_fsm_auth_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    rc = dpoe_eap_tls_process_eapol_pkt(link_rec, evt->data.rx_frame.val, evt->data.rx_frame.len);
+    if ((BCM_ERR_OK != rc) && (BCM_ERR_IN_PROGRESS != rc))
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Error processing EAPOL packet: %s (%d)\n", bcmos_strerror(rc), rc);
+        _dpoe_sec_fsm_stop(link_rec, rc);
+    }
+}
+
+static void _dpoe_sec_fsm_auth_complete(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    bcmos_timer_stop(&link_rec->timeout);
+    if (BCM_ERR_OK == evt->data.sub_status)
+    {
+        if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+        {
+            _dpoe_sec_fsm_mka_start(link_rec);
+        }
+        else
+        {
+            _dpoe_sec_fsm_success(link_rec);
+        }
+    }
+    else
+    {
+        _dpoe_sec_fsm_stop(link_rec, evt->data.sub_status);
+    }
+}
+
+static bcmos_errno _dpoe_sec_fsm_install_dn_key(
+    dpoe_sec_link_rec *link_rec,
+    bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe)
+{
+    bcmolt_encryption_information_container key_info;
+
+    if (BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_ONE_DOWN == link_rec->cfg.enc_mode)
+    {
+        key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CFB;
+        memcpy(key_info.u.cfb.key, dpoe->u.key_exchange.key_data, sizeof(key_info.u.cfb.key));
+    }
+    else
+    {
+        key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+        memcpy(key_info.u.ctr.key, dpoe->u.key_exchange.key_data, sizeof(key_info.u.ctr.key));
+        memcpy(key_info.u.ctr.sci, link_rec->ni_mac.u8, BCMOS_ETH_ALEN);
+        key_info.u.ctr.sci[6] = (link_rec->llid >> 8) & 0xff;
+        key_info.u.ctr.sci[7] = (link_rec->llid >> 0) & 0xff;
+    }
+
+    return bcmolt_epon_link_encryption_key_set(
+        link_rec->device,
+        &link_rec->key,
+        BCMOS_FALSE,
+        BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES,
+        (bcmolt_epon_key_choice)dpoe->u.key_exchange.key_number,
+        &key_info);
+}
+
+static void _dpoe_sec_fsm_enc_dn_rx_oam(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    bcmolt_epon_oam_ethernet_frame *oam_frame;
+    bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    oam_frame = epon_oam_unpack(link_rec->device, evt->data.rx_frame.len, evt->data.rx_frame.val);
+    if (NULL == oam_frame)
+    {
+        DPOE_SEC_LINK_LOG(WARNING, link_rec, "Failed to parse OAM frame\n");
+        return;
+    }
+
+    if (!epon_oam_is_dpoe(oam_frame))
+    {
+        bcmos_free(oam_frame);
+        return;
+    }
+
+    dpoe = &oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value;
+
+    if (epon_oam_is_dpoe_encrypt_response(dpoe))
+    {
+        if ((dpoe->u.set_response.vars[0].u.extended_attribute.attribute.width == 0x80) &&
+            (dpoe->u.set_response.vars[1].u.extended_attribute.attribute.width == 0x80))
+        {
+            bcmos_timer_start(&link_rec->timeout, DPOE_SEC_OAM_TIMEOUT); /* restart timer to wait for key */
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "ONU rejected encryption: [0] = 0x%x, [1] = 0x%x\n",
+                              dpoe->u.set_response.vars[0].u.extended_attribute.attribute.width,
+                              dpoe->u.set_response.vars[1].u.extended_attribute.attribute.width);
+            _dpoe_sec_fsm_stop(link_rec, BCM_ERR_ONU_ERR_RESP);
+        }
+    }
+    else if (BCMOLT_EPON_OAM_DPOE_OPCODE_KEY_EXCHANGE == dpoe->op)
+    {
+        _dpoe_sec_fsm_install_dn_key(link_rec, dpoe);
+        if (link_rec->cfg.auth)
+        {
+            _dpoe_sec_fsm_auth_start(link_rec, evt);
+        }
+        else
+        {
+            _dpoe_sec_fsm_success(link_rec);
+        }
+    }
+    else
+    {
+        /* ignore other OAM */
+    }
+
+    bcmos_free(oam_frame);
+}
+
+static void _dpoe_sec_fsm_enc_bi_rx_oam(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    dpoe_sec_mka_fsm_rx_oam(link_rec, evt->data.rx_frame.val, evt->data.rx_frame.len);
+}
+
+static void _dpoe_sec_fsm_enc_bi_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    dpoe_sec_mka_fsm_rx_eapol(link_rec, evt->data.rx_frame.val, evt->data.rx_frame.len);
+}
+
+static void _dpoe_sec_fsm_mka_complete(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if (BCM_ERR_OK == evt->data.sub_status)
+    {
+        _dpoe_sec_fsm_success(link_rec);
+    }
+    else
+    {
+        _dpoe_sec_fsm_stop(link_rec, evt->data.sub_status);
+    }
+}
+
+static void _dpoe_sec_fsm_comp_rx_oam(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if ((link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_ONE_DOWN) ||
+        (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_DOWN))
+    {
+        bcmolt_epon_oam_ethernet_frame *oam_frame;
+        bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe;
+
+        oam_frame = epon_oam_unpack(link_rec->device, evt->data.rx_frame.len, evt->data.rx_frame.val);
+        if (NULL == oam_frame)
+        {
+            DPOE_SEC_LINK_LOG(WARNING, link_rec, "Failed to parse OAM frame\n");
+            return;
+        }
+
+        if (!epon_oam_is_dpoe(oam_frame))
+        {
+            bcmos_free(oam_frame);
+            return;
+        }
+
+        dpoe = &oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value;
+
+        if (BCMOLT_EPON_OAM_DPOE_OPCODE_KEY_EXCHANGE == dpoe->op)
+        {
+            _dpoe_sec_fsm_install_dn_key(link_rec, dpoe);
+        }
+    }
+}
+
+static void _dpoe_sec_fsm_comp_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
+    {
+        dpoe_sec_mka_fsm_rx_eapol(link_rec, evt->data.rx_frame.val, evt->data.rx_frame.len);
+    }
+}
+
+static void _dpoe_sec_fsm_timeout(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    _dpoe_sec_fsm_stop(link_rec, BCM_ERR_TIMEOUT);
+}
+
+static f_dpoe_sec_fsm dpoe_sec_fsm[DPOE_SEC_FSM_STATE__COUNT][DPOE_SEC_FSM_EVENT__COUNT] =
+{
+    [DPOE_SEC_FSM_STATE_IDLE] =
+    {
+        [DPOE_SEC_FSM_EVENT_AUTH_START] = _dpoe_sec_fsm_auth_start,
+        [DPOE_SEC_FSM_EVENT_ENCRYPT_START] = _dpoe_sec_fsm_encrypt_start
+    },
+    [DPOE_SEC_FSM_STATE_AUTH_WAIT] =
+    {
+        [DPOE_SEC_FSM_EVENT_RX_EAPOL] = _dpoe_sec_fsm_auth_rx_eapol,
+        [DPOE_SEC_FSM_EVENT_AUTH_COMPLETE] = _dpoe_sec_fsm_auth_complete,
+        [DPOE_SEC_FSM_EVENT_TIMEOUT] = _dpoe_sec_fsm_timeout
+    },
+    [DPOE_SEC_FSM_STATE_ENCRYPT_DOWN_WAIT] =
+    {
+        [DPOE_SEC_FSM_EVENT_RX_OAM] = _dpoe_sec_fsm_enc_dn_rx_oam,
+        [DPOE_SEC_FSM_EVENT_TIMEOUT] = _dpoe_sec_fsm_timeout
+    },
+    [DPOE_SEC_FSM_STATE_ENCRYPT_BI_WAIT] =
+    {
+        [DPOE_SEC_FSM_EVENT_RX_OAM] = _dpoe_sec_fsm_enc_bi_rx_oam,
+        [DPOE_SEC_FSM_EVENT_RX_EAPOL] = _dpoe_sec_fsm_enc_bi_rx_eapol,
+        [DPOE_SEC_FSM_EVENT_MKA_COMPLETE] = _dpoe_sec_fsm_mka_complete,
+        [DPOE_SEC_FSM_EVENT_TIMEOUT] = _dpoe_sec_fsm_timeout
+    },
+    [DPOE_SEC_FSM_STATE_COMPLETE] =
+    {
+        [DPOE_SEC_FSM_EVENT_RX_OAM] = _dpoe_sec_fsm_comp_rx_oam,
+        [DPOE_SEC_FSM_EVENT_RX_EAPOL] = _dpoe_sec_fsm_comp_rx_eapol,
+        [DPOE_SEC_FSM_EVENT_TIMEOUT] = _dpoe_sec_fsm_timeout
+    }
+};
+
+static void _dpoe_sec_fsm_unexpected(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    DPOE_SEC_LINK_LOG(WARNING, link_rec, "Unexpected event %s (%u) in state %s (%u)\n",
+                      _dpoe_sec_fsm_event_name(evt->type), evt->type, _dpoe_sec_fsm_state_name(link_rec->fsm_state), link_rec->fsm_state);
+}
+
+static void _dpoe_sec_fsm_exec(dpoe_sec_link_rec *link_rec, dpoe_sec_fsm_event_data *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if (evt->type < DPOE_SEC_FSM_EVENT__COUNT)
+    {
+        dpoe_sec_fsm_state pre_state = link_rec->fsm_state;
+
+        if (dpoe_sec_fsm[link_rec->fsm_state][evt->type] != NULL)
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "processing event %s in state %s\n",
+                              _dpoe_sec_fsm_event_name(evt->type), _dpoe_sec_fsm_state_name(link_rec->fsm_state));
+            dpoe_sec_fsm[link_rec->fsm_state][evt->type](link_rec, evt);
+        }
+        else
+        {
+            _dpoe_sec_fsm_unexpected(link_rec, evt);
+        }
+
+        if (link_rec->fsm_state != pre_state)
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "transition from %s to %s\n",
+                              _dpoe_sec_fsm_state_name(pre_state), _dpoe_sec_fsm_state_name(link_rec->fsm_state));
+        }
+    }
+    else
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Unknown event: %u", evt->type);
+    }
+}
+
+static bcmos_timer_rc _dpoe_sec_fsm_link_timer_expired(bcmos_timer *timer, long data)
+{
+    dpoe_sec_link_rec *link_rec = (dpoe_sec_link_rec*)data;
+    dpoe_sec_fsm_event_data evt;
+
+    evt.type = DPOE_SEC_FSM_EVENT_TIMEOUT;
+
+    _dpoe_sec_fsm_exec(link_rec, &evt);
+
+    return BCMOS_TIMER_OK;
+}
+
+static void _dpoe_sec_fsm_link_start_handle(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    dpoe_sec_fsm_msg *msg = (dpoe_sec_fsm_msg*)os_msg;
+    bcmolt_devid device = os_msg->instance;
+    dpoe_sec_link_rec *link_rec;
+    dpoe_sec_fsm_event_data evt;
+    bcmos_errno err;
+
+    link_rec = hash_table_get(ctxt[device].link_map, (const uint8_t*)&msg->link);
+    if (link_rec == NULL)
+    {
+        dpoe_sec_link_rec new_link;
+        bcmolt_epon_ni_cfg pon_cfg;
+        bcmolt_epon_link_cfg link_cfg;
+        bcmolt_epon_ni_key pon_key = { .epon_ni = msg->link.epon_ni };
+        bcmos_timer_parm tp =
+        {
+            .name = "dpoe_sec_fsm_timer",
+            .owner = BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + device,
+            .periodic = BCMOS_FALSE,
+            .handler = _dpoe_sec_fsm_link_timer_expired,
+            .flags = BCMOS_TIMER_PARM_FLAGS_NON_URGENT
+        };
+
+        /* retrieve the EPON NI MAC address */
+        BCMOLT_CFG_INIT(&pon_cfg, epon_ni, pon_key);
+        BCMOLT_CFG_PROP_GET(&pon_cfg, epon_ni, mac_address);
+        err = bcmolt_cfg_get(device, &pon_cfg.hdr);
+        BUG_ON(BCM_ERR_OK != err);
+
+        BCMOLT_CFG_INIT(&link_cfg, epon_link, msg->link);
+        BCMOLT_CFG_PROP_GET(&link_cfg, epon_link, llid);
+        err = bcmolt_cfg_get(device, &link_cfg.hdr);
+        if (BCM_ERR_OK != err)
+        {
+            BCM_LOG(ERROR, dpoe_sec_log_id[device], "Failed to get LLID\n");
+        }
+
+        new_link.device = device;
+        new_link.key = msg->link;
+        new_link.ni_mac = pon_cfg.data.mac_address;
+        new_link.llid = link_cfg.data.llid;
+        new_link.fsm_state = DPOE_SEC_FSM_STATE_IDLE;
+        new_link.status = BCM_ERR_IN_PROGRESS;
+
+        /* add new rec to hash table */
+        link_rec = hash_table_put(ctxt[device].link_map, (uint8_t*)&msg->link, &new_link);
+        if (link_rec == NULL)
+        {
+            DPOE_SEC_LINK_LOG(ERROR, &new_link, "Failed to store link record\n");
+        }
+
+        tp.data = (long)link_rec;
+        err = bcmos_timer_create(&link_rec->timeout, &tp);
+        BUG_ON(BCM_ERR_OK != err);
+    }
+
+    link_rec->cfg = msg->data.start;
+
+    if (!link_rec->cfg.auth || (link_rec->cfg.enc_mode != BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI))
+    {
+        evt.type = DPOE_SEC_FSM_EVENT_ENCRYPT_START;
+    }
+    else
+    {
+        evt.type = DPOE_SEC_FSM_EVENT_AUTH_START;
+    }
+
+    _dpoe_sec_fsm_exec(link_rec, &evt);
+
+    bcmos_free(os_msg);
+}
+
+static void _dpoe_sec_fsm_link_rx_oam_handle(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    dpoe_sec_fsm_msg *msg = (dpoe_sec_fsm_msg*)os_msg;
+    bcmolt_devid device = os_msg->instance;
+    dpoe_sec_link_rec *link_rec;
+    dpoe_sec_fsm_event_data evt;
+
+    link_rec = hash_table_get(ctxt[device].link_map, (uint8_t*)&msg->link);
+    if (link_rec == NULL)
+    {
+        BCM_LOG(DEBUG, dpoe_sec_log_id[device], "Failed to find link: PON %u, "BCMOS_MACADDR_FMT_STR"\n",
+                msg->link.epon_ni, BCMOS_MACADDR_PARAMS(&msg->link.mac_address));
+    }
+    else
+    {
+        evt.type = DPOE_SEC_FSM_EVENT_RX_OAM;
+        evt.data.rx_frame.val = msg->data.rx_frame.frame;
+        evt.data.rx_frame.len = msg->data.rx_frame.length;
+        _dpoe_sec_fsm_exec(link_rec, &evt);
+    }
+
+    bcmos_free(msg->data.rx_frame.frame);
+    bcmos_free(os_msg);
+}
+
+static void _dpoe_sec_fsm_link_rx_eapol_handle(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    dpoe_sec_fsm_msg *msg = (dpoe_sec_fsm_msg*)os_msg;
+    bcmolt_devid device = os_msg->instance;
+    dpoe_sec_link_rec *link_rec;
+    dpoe_sec_fsm_event_data evt;
+
+    link_rec = hash_table_get(ctxt[device].link_map, (uint8_t*)&msg->link);
+    if (link_rec == NULL)
+    {
+        BCM_LOG(INFO, dpoe_sec_log_id[device], "Failed to find link: PON %u, "BCMOS_MACADDR_FMT_STR"\n",
+                msg->link.epon_ni, BCMOS_MACADDR_PARAMS(&msg->link.mac_address));
+    }
+    else
+    {
+        evt.type = DPOE_SEC_FSM_EVENT_RX_EAPOL;
+        evt.data.rx_frame.val = msg->data.rx_frame.frame;
+        evt.data.rx_frame.len = msg->data.rx_frame.length;
+        _dpoe_sec_fsm_exec(link_rec, &evt);
+    }
+
+    bcmos_free(os_msg);
+}
+
+static void _dpoe_sec_fsm_link_auth_complete(dpoe_sec_link_rec* link_rec, bcmos_errno status)
+{
+    dpoe_sec_fsm_event_data evt;
+
+    evt.type = DPOE_SEC_FSM_EVENT_AUTH_COMPLETE;
+    evt.data.sub_status = status;
+
+    _dpoe_sec_fsm_exec(link_rec, &evt);
+}
+
+static void _dpoe_sec_fsm_link_mka_complete(dpoe_sec_link_rec* link_rec, bcmos_errno status)
+{
+    dpoe_sec_fsm_event_data evt;
+
+    evt.type = DPOE_SEC_FSM_EVENT_MKA_COMPLETE;
+    evt.data.sub_status = status;
+
+    _dpoe_sec_fsm_exec(link_rec, &evt);
+}
+
+static bcmos_errno _dpoe_sec_fsm_init_task(bcmolt_devid device)
+{
+    bcmos_task_parm task_params =
+    {
+        .name         = ctxt[device].task_name,
+        .priority     = TASK_PRIORITY_USER_APPL_DPOE_SEC,
+        .core         = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+    snprintf(ctxt[device].task_name, sizeof(ctxt[device].task_name), "user_appl_dpoe_sec%u", device);
+    bcmos_errno rc = bcmos_task_create(&ctxt[device].task, &task_params);
+
+    return rc;
+}
+
+
+static bcmos_errno _dpoe_sec_fsm_init_module(bcmolt_devid device)
+{
+    bcmos_module_parm module_params =
+    {
+        .qparm = { .name = ctxt[device].msg_queue_name, .size = DPOE_SEC_FSM_TASK_MSG_Q_SIZE },
+        .init = NULL
+    };
+    snprintf(ctxt[device].msg_queue_name, sizeof(ctxt[device].msg_queue_name), "dpoe_sec_msg_q%u", device);
+    return bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + device, &ctxt[device].task, &module_params);
+}
+
+static bcmos_errno _dpoe_sec_fsm_send_os_msg(
+    bcmolt_devid device,
+    bcmolt_epon_link_key link_key,
+    bcmos_msg_id type,
+    dpoe_sec_fsm_msg_data *data)
+{
+    bcmos_errno rc;
+    dpoe_sec_fsm_msg *msg = bcmos_calloc(sizeof(*msg));
+    if (msg == NULL)
+    {
+        BCM_LOG(ERROR, dpoe_sec_log_id[device], "OS Message calloc failed\n");
+        return BCM_ERR_NOMEM;
+    }
+
+    msg->os_msg.instance = device;
+    msg->os_msg.type = type;
+    msg->link = link_key;
+    msg->data = *data;
+    rc = bcmos_msg_dispatch(&msg->os_msg, BCMOS_MSG_SEND_AUTO_FREE);
+    if (BCM_ERR_OK != rc)
+    {
+        BCM_LOG(ERROR, dpoe_sec_log_id[device], "OS Message send failed (%s)\n", bcmos_strerror(rc));
+    }
+
+    return rc;
+}
+
+bcmos_errno dpoe_sec_fsm_link_start(bcmolt_devid device, bcmolt_epon_link_key link_key, dpoe_sec_fsm_start_data *cfg)
+{
+    dpoe_sec_fsm_msg_data data;
+
+    data.start = *cfg;
+
+    return _dpoe_sec_fsm_send_os_msg(device, link_key, BCMOS_MSG_ID_DPOE_SEC_START, &data);
+}
+
+bcmos_errno dpoe_sec_fsm_link_rx_oam(bcmolt_devid device, bcmolt_epon_link_key link_key, uint8_t *oam, uint16_t length)
+{
+    dpoe_sec_fsm_msg_data data;
+
+    data.rx_frame.frame = oam;
+    data.rx_frame.length = length;
+
+    return _dpoe_sec_fsm_send_os_msg(device, link_key, BCMOS_MSG_ID_DPOE_SEC_RX_OAM, &data);
+}
+
+bcmos_errno dpoe_sec_fsm_link_rx_eapol(
+    bcmolt_devid device,
+    bcmolt_epon_link_key link_key,
+    uint8_t *eapol,
+    uint16_t length)
+{
+    dpoe_sec_fsm_msg_data data;
+
+    data.rx_frame.frame = eapol;
+    data.rx_frame.length = length;
+
+    return _dpoe_sec_fsm_send_os_msg(device, link_key, BCMOS_MSG_ID_DPOE_SEC_RX_EAPOL, &data);
+}
+
+bcmos_errno dpoe_sec_fsm_init(
+    f_dpoe_sec_cert_trust_cb cert_trust_cb,
+    f_dpoe_sec_fsm_cb fsm_complete_cb)
+{
+    bcmos_errno rc;
+
+    for (uint32_t device = 0; device < BCMTR_MAX_OLTS; device++)
+    {
+        rc = _dpoe_sec_fsm_init_task(device);
+        BUG_ON(rc != BCM_ERR_OK);
+
+        rc = _dpoe_sec_fsm_init_module(device);
+        BUG_ON(rc != BCM_ERR_OK);
+
+        char log_name[MAX_DEV_LOG_ID_NAME];
+        snprintf(log_name, sizeof(log_name), "user_appl_dpoe_sec%u", device);
+        dpoe_sec_log_id[device] = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+
+        bcmos_msg_register(
+            BCMOS_MSG_ID_DPOE_SEC_START,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + device,
+            _dpoe_sec_fsm_link_start_handle);
+        bcmos_msg_register(
+            BCMOS_MSG_ID_DPOE_SEC_RX_OAM,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + device,
+            _dpoe_sec_fsm_link_rx_oam_handle);
+        bcmos_msg_register(
+            BCMOS_MSG_ID_DPOE_SEC_RX_EAPOL,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + device,
+            _dpoe_sec_fsm_link_rx_eapol_handle);
+
+        ctxt[device].link_map = hash_table_create(
+            DPOE_SEC_FSM_MAX_LINKS,
+            sizeof(dpoe_sec_link_rec),
+            sizeof(bcmolt_epon_link_key),
+            "dpoe_sec_link_state");
+        BUG_ON(ctxt[device].link_map == NULL);
+    }
+
+    _fsm_complete = fsm_complete_cb;
+
+    dpoe_eap_tls_init(_dpoe_sec_fsm_link_auth_complete, cert_trust_cb);
+    dpoe_sec_mka_fsm_init(_dpoe_sec_fsm_link_mka_complete);
+
+    return rc;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.h
new file mode 100644
index 0000000..fa8b3ad
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_fsm.h
@@ -0,0 +1,130 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _DPOE_SEC_FSM_H_
+#define _DPOE_SEC_FSM_H_
+
+#include "bcmos_system.h"
+#include "bcmolt_model_types.h"
+#include "bcmolt_epon_oam_types.h"
+#include "dpoe_eap_tls.h"
+#include "dpoe_sec_mka_fsm.h"
+
+/* DPoE Security FSM Event Type definitions */
+typedef enum dpoe_sec_fsm_event
+{
+    DPOE_SEC_FSM_EVENT__FIRST,
+    DPOE_SEC_FSM_EVENT_ENCRYPT_START = DPOE_SEC_FSM_EVENT__FIRST,
+    DPOE_SEC_FSM_EVENT_AUTH_START,
+    DPOE_SEC_FSM_EVENT_RX_OAM,
+    DPOE_SEC_FSM_EVENT_RX_EAPOL,
+    DPOE_SEC_FSM_EVENT_AUTH_COMPLETE,
+    DPOE_SEC_FSM_EVENT_MKA_COMPLETE,
+    DPOE_SEC_FSM_EVENT_TIMEOUT,
+
+    DPOE_SEC_FSM_EVENT__COUNT
+} dpoe_sec_fsm_event;
+
+/* DPoE Security FSM State definitions */
+typedef enum dpoe_sec_fsm_state
+{
+    DPOE_SEC_FSM_STATE__FIRST,
+    DPOE_SEC_FSM_STATE_IDLE = DPOE_SEC_FSM_STATE__FIRST,
+    DPOE_SEC_FSM_STATE_AUTH_WAIT,
+    DPOE_SEC_FSM_STATE_ENCRYPT_DOWN_WAIT,
+    DPOE_SEC_FSM_STATE_ENCRYPT_BI_WAIT,
+    DPOE_SEC_FSM_STATE_COMPLETE,
+
+    DPOE_SEC_FSM_STATE__COUNT
+} dpoe_sec_fsm_state;
+
+/* DPoE Security FSM Event contents. This is a union of information required by the DPoE Security FSM. This data type
+ * permits a common function prototype for the state machine function table. */
+typedef struct dpoe_sec_fsm_event_data
+{
+    dpoe_sec_fsm_event type;
+    union
+    {
+        bcmolt_u8_list_u16 rx_frame;
+        bcmos_errno sub_status;
+    } data;
+} dpoe_sec_fsm_event_data;
+
+struct dpoe_sec_link_rec;
+
+typedef struct
+{
+    bcmolt_epon_oam_dpoe_encryption_mode enc_mode;
+    bcmos_bool auth;
+} dpoe_sec_fsm_start_data;
+
+typedef struct dpoe_sec_link_rec
+{
+    bcmolt_devid device;
+    bcmolt_epon_link_key key;
+    dpoe_sec_fsm_start_data cfg;
+    bcmos_mac_address ni_mac;
+    bcmolt_epon_llid llid;
+    dpoe_sec_fsm_state fsm_state;
+    bcmos_errno status;
+    bcmos_timer timeout;
+    onu_auth_control auth_ctrl;
+    dpoe_sec_mka_fsm_ctrl mka_ctrl;
+} dpoe_sec_link_rec;
+
+typedef struct
+{
+    uint8_t *frame;
+    uint16_t length;
+} dpoe_sec_fsm_rx_frame_data;
+
+typedef union
+{
+    dpoe_sec_fsm_start_data start;
+    dpoe_sec_fsm_rx_frame_data rx_frame;
+} dpoe_sec_fsm_msg_data;
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    bcmolt_epon_link_key link;
+    dpoe_sec_fsm_msg_data data;
+} dpoe_sec_fsm_msg;
+
+typedef void (*f_dpoe_sec_fsm_cb)(bcmolt_devid, bcmolt_epon_link_key, bcmos_errno);
+
+bcmos_errno dpoe_sec_fsm_link_start(bcmolt_devid device, bcmolt_epon_link_key link_key, dpoe_sec_fsm_start_data *cfg);
+
+bcmos_errno dpoe_sec_fsm_link_rx_oam(bcmolt_devid device, bcmolt_epon_link_key link, uint8_t *oam, uint16_t length);
+
+bcmos_errno dpoe_sec_fsm_link_rx_eapol(bcmolt_devid device, bcmolt_epon_link_key link, uint8_t *eapol, uint16_t length);
+
+bcmos_errno dpoe_sec_fsm_init(f_dpoe_sec_cert_trust_cb cert_trust_cb, f_dpoe_sec_fsm_cb fsm_complete_cb);
+
+#endif /* _DPOE_SEC_FSM_H_ */
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.c
new file mode 100644
index 0000000..3174d63
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.c
@@ -0,0 +1,830 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "dpoe_sec_fsm.h"
+#include "dpoe_sec_mka_fsm.h"
+#include "bcmolt_user_appl_epon_oam.h"
+#include "dpoe_sec_util.h"
+
+typedef enum dpoe_sec_mka_fsm_event_type
+{
+    DPOE_SEC_MKA_FSM_EVENT__INVALID     = -1, /**< permits event validation */
+
+    DPOE_SEC_MKA_FSM_EVENT_INIT         =  0,
+    DPOE_SEC_MKA_FSM_EVENT_RX_OAM       ,
+    DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL     ,
+    DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT  ,
+    DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT  ,
+    DPOE_SEC_MKA_FSM_EVENT_STOP         ,
+
+    DPOE_SEC_MKA_FSM_EVENT__COUNT
+} dpoe_sec_mka_fsm_event_type;
+
+/* MKA FSM Event contents. This is a union of information required by the MKA FSM. This data type permits a common
+ * function prototype for the state machine function table. */
+typedef struct dpoe_sec_mka_fsm_event
+{
+    dpoe_sec_mka_fsm_event_type type; /**< Type of event*/
+    union
+    {
+        bcmolt_u8_list_u16 rx_frame;
+    } data;
+} dpoe_sec_mka_fsm_event;
+
+typedef bcmos_errno (*f_dpoe_sec_mka_fsm)(dpoe_sec_link_rec *mka_ctrl, dpoe_sec_mka_fsm_event *evt);
+
+static const char *dpoe_sec_mka_fsm_event_name[DPOE_SEC_MKA_FSM_EVENT__COUNT] =
+{
+    [DPOE_SEC_MKA_FSM_EVENT_INIT] = "Initialization",
+    [DPOE_SEC_MKA_FSM_EVENT_RX_OAM] = "OAM Response",
+    [DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL] = "RX EAPOL",
+    [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = "FSM Timeout",
+    [DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT] = "MKA Timeout",
+    [DPOE_SEC_MKA_FSM_EVENT_STOP] = "Stop"
+};
+
+static const char *dpoe_sec_mka_fsm_state_name[DPOE_SEC_MKA_FSM_STATE__COUNT] =
+{
+    [DPOE_SEC_MKA_FSM_STATE_NULL] = "Null",
+    [DPOE_SEC_MKA_FSM_STATE_SET_ENCRYPTION] = "Set Encryption",
+    [DPOE_SEC_MKA_FSM_STATE_START_MKA ] = "Start MKA",
+    [DPOE_SEC_MKA_FSM_STATE_SEND_SAK] = "Send SAK",
+    [DPOE_SEC_MKA_FSM_STATE_OPERATIONAL] = "Operational",
+    [DPOE_SEC_MKA_FSM_STATE_SEND_NEW_SAK] = "Send New SAK"
+};
+
+static f_dpoe_sec_mka_cb _mka_done_cb;
+
+static bcmos_errno _dpoe_sec_mka_fsm_exec(dpoe_sec_link_rec *link, dpoe_sec_mka_fsm_event *evt);
+
+static bcmos_bool _dpoe_sec_mka_fsm_event_type_is_valid(dpoe_sec_mka_fsm_event_type type)
+{
+    return (type > DPOE_SEC_MKA_FSM_EVENT__INVALID) && (type < DPOE_SEC_MKA_FSM_EVENT__COUNT);
+}
+
+static bcmos_bool _dpoe_sec_mka_fsm_state_is_valid(dpoe_sec_mka_fsm_state state)
+{
+    return (state > DPOE_SEC_MKA_FSM_STATE__INVALID) && (state < DPOE_SEC_MKA_FSM_STATE__COUNT);
+}
+
+static const char *_dpoe_sec_mka_fsm_event_name(dpoe_sec_mka_fsm_event_type type)
+{
+    if (_dpoe_sec_mka_fsm_event_type_is_valid(type))
+    {
+        return dpoe_sec_mka_fsm_event_name[type];
+    }
+    else
+    {
+        return "Unknown Event";
+    }
+}
+
+static const char *_dpoe_sec_mka_fsm_state_name(dpoe_sec_mka_fsm_state state)
+{
+    if (_dpoe_sec_mka_fsm_state_is_valid(state))
+    {
+        return dpoe_sec_mka_fsm_state_name[state];
+    }
+    else
+    {
+        return "Unknown State";
+    }
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_error(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    /* TODO: log unexpected event */
+
+    return BCM_ERR_OK;
+}
+
+static void _dpoe_sec_mka_fsm_cleanup(dpoe_sec_mka_fsm_ctrl *mka_ctrl)
+{
+    /* Parameter checks. */
+    BUG_ON(mka_ctrl == NULL);
+
+    if (mka_ctrl->mka_info != NULL)
+    {
+        /* Clear all MKA information before freeing. */
+        memset(mka_ctrl->mka_info, 0, sizeof(*mka_ctrl->mka_info));
+        bcmos_free(mka_ctrl->mka_info);
+        mka_ctrl->mka_info = NULL;
+    }
+
+    if (mka_ctrl->mka_fsm_info != NULL)
+    {
+        /* Clear all MKA FSM information before freeing. */
+        memset(mka_ctrl->mka_fsm_info, 0, sizeof(*mka_ctrl->mka_fsm_info));
+        bcmos_free(mka_ctrl->mka_fsm_info);
+        mka_ctrl->mka_fsm_info = NULL;
+    }
+
+    mka_ctrl->mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_NULL;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_stop(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    /* The stop function is called each time the Link deregisters regardless of whether the MKA FSM is running or not.
+       Handle the case where it is not running. */
+    if (link_rec->mka_ctrl.mka_fsm_info == NULL)
+    {
+        return BCM_ERR_OK;
+    }
+
+    /* Stop any timers that may be running. */
+    bcmos_timer_stop(&link_rec->mka_ctrl.mka_fsm_info->rx_timer);
+    bcmos_timer_stop(&link_rec->mka_ctrl.mka_fsm_info->tx_timer);
+
+    /* Change to the NULL state */
+    link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_NULL;
+
+    /* Free dynamic MKA resources associated with the link. */
+    _dpoe_sec_mka_fsm_cleanup(&link_rec->mka_ctrl);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_timer_rc _dpoe_sec_mka_fsm_rx_timer_expire(bcmos_timer *timer, long data)
+{
+    dpoe_sec_link_rec *link_rec = (dpoe_sec_link_rec*)data;
+    dpoe_sec_mka_fsm_event evnt = {};
+
+    /* Parameter Checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+
+    /* Set up the event */
+    evnt.type = DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT;
+
+    /* Execute the state machine */
+    _dpoe_sec_mka_fsm_exec(link_rec, &evnt);
+
+    return BCMOS_TIMER_OK;
+}
+
+static bcmos_timer_rc _dpoe_sec_mka_fsm_tx_timer_expire(bcmos_timer *timer, long data)
+{
+    dpoe_sec_link_rec *link_rec = (dpoe_sec_link_rec*)data;
+    dpoe_sec_mka_fsm_event evnt = {};
+
+    /* Parameter Checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+
+    /* Set up the event */
+    evnt.type = DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT;
+
+    /* Execute the state machine */
+    _dpoe_sec_mka_fsm_exec(link_rec, &evnt);
+
+    return BCMOS_TIMER_OK;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_initialize(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno err;
+    bcmos_timer_parm rx_tp =
+    {
+        .name = "dpoe_sec_mka_fsm_timer",
+        .owner = BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + link_rec->device,
+        .periodic = BCMOS_FALSE,
+        .handler = _dpoe_sec_mka_fsm_rx_timer_expire,
+        .data = (long)link_rec,
+        .flags = BCMOS_TIMER_PARM_FLAGS_NON_URGENT
+    };
+    bcmos_timer_parm tx_tp =
+    {
+        .name = "dpoe_sec_mka_mka_timer",
+        .owner = BCMOS_MODULE_ID_USER_APPL_DPOE_SEC + link_rec->device,
+        .periodic = BCMOS_TRUE,
+        .handler = _dpoe_sec_mka_fsm_tx_timer_expire,
+        .data = (long)link_rec,
+        .flags = BCMOS_TIMER_PARM_FLAGS_NON_URGENT
+    };
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    /* Allocate the MKA information structure. */
+    link_rec->mka_ctrl.mka_info = bcmos_calloc(sizeof(*link_rec->mka_ctrl.mka_info));
+    if (link_rec->mka_ctrl.mka_info == NULL)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    /* Allocate the MKA FSM information structure. */
+    link_rec->mka_ctrl.mka_fsm_info = bcmos_calloc(sizeof(*link_rec->mka_ctrl.mka_fsm_info));
+    if (link_rec->mka_ctrl.mka_fsm_info == NULL)
+    {
+        _dpoe_sec_mka_fsm_cleanup(&link_rec->mka_ctrl);
+        return BCM_ERR_NOMEM;
+    }
+
+    err = bcmos_timer_create(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, &rx_tp);
+    if (BCM_ERR_OK != err)
+    {
+        _dpoe_sec_mka_fsm_cleanup(&link_rec->mka_ctrl);
+        return err;
+    }
+
+    err = bcmos_timer_create(&link_rec->mka_ctrl.mka_fsm_info->tx_timer, &tx_tp);
+    if (BCM_ERR_OK != err)
+    {
+        _dpoe_sec_mka_fsm_cleanup(&link_rec->mka_ctrl);
+        return err;
+    }
+
+    /* Change to the SET_LINK_ENCRYPTION_MODE state */
+    link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_SET_ENCRYPTION;
+    bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, DPOE_SEC_OAM_TIMEOUT);
+
+    return epon_oam_dpoe_set_encryption(
+        link_rec->device,
+        link_rec->key.epon_ni,
+        &link_rec->key.mac_address,
+        link_rec->cfg.enc_mode,
+        0);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_send_success(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(_mka_done_cb == NULL);
+
+    _mka_done_cb(link_rec, BCM_ERR_OK);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_send_failure(dpoe_sec_link_rec *link_rec, bcmos_errno reason)
+{
+    dpoe_sec_mka_fsm_event event = { .type = DPOE_SEC_MKA_FSM_EVENT_STOP };
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(_mka_done_cb == NULL);
+
+    DPOE_SEC_LINK_LOG(ERROR, link_rec, "MKA stopping!\n");
+
+    /* Notify the DPoE Security FSM of the MKA error. */
+    _mka_done_cb(link_rec, reason);
+
+    /* Stop the FSM due to the error. */
+    return _dpoe_sec_mka_fsm_stop(link_rec, &event);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_rx_timeout(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    DPOE_SEC_LINK_LOG(ERROR, link_rec, "TIMEOUT error!\n");
+
+    /* Notify the DPoE Security FSM of the MKA error. */
+    return _dpoe_sec_mka_fsm_send_failure(link_rec, BCM_ERR_TIMEOUT);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_rx_oam(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    bcmolt_epon_oam_ethernet_frame *oam_frame;
+    bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+    BUG_ON(evt->data.rx_frame.val == NULL);
+
+    oam_frame = epon_oam_unpack(link_rec->device, evt->data.rx_frame.len, evt->data.rx_frame.val);
+    if (NULL == oam_frame)
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "OAM unpack failed!\n");
+        return BCM_ERR_OK;
+    }
+
+    if (!epon_oam_is_dpoe(oam_frame))
+    {
+        bcmos_free(oam_frame);
+        return BCM_ERR_OK;
+    }
+
+    dpoe = &oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value;
+
+    if (epon_oam_is_dpoe_encrypt_response(dpoe))
+    {
+        if ((dpoe->u.set_response.vars[0].u.extended_attribute.attribute.width == 0x80) &&
+            (dpoe->u.set_response.vars[1].u.extended_attribute.attribute.width == 0x80))
+        {
+            bcmos_timer_stop(&link_rec->mka_ctrl.mka_fsm_info->rx_timer);
+
+            /* Change to the START_MKA state */
+            link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_START_MKA;
+
+            /* Send the initial MKA packet to the ONU for the specified link. */
+            rc = mka_start(link_rec);
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "ONU rejected encryption: [0] = 0x%x, [1] = 0x%x\n",
+                              dpoe->u.set_response.vars[0].u.extended_attribute.attribute.width,
+                              dpoe->u.set_response.vars[1].u.extended_attribute.attribute.width);
+            /* Notify the DPoE Security FSM of the MKA error. */
+            rc = _dpoe_sec_mka_fsm_send_failure(link_rec, BCM_ERR_ONU_ERR_RESP);
+        }
+    }
+    else
+    {
+        /* ignore other OAM */
+    }
+
+    bcmos_free(oam_frame);
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_start_timeout(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    return mka_process_timeout(link_rec, MKA_OP_START_TIMEOUT);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_start_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    rc = mka_process_packet(link_rec, evt->data.rx_frame, MKA_OP_START_RSP);
+    if (rc == BCM_ERR_OK)
+    {
+        bcmolt_encryption_information_container key_info;
+
+        /* Re-start the rx timer */
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, MKA_LIFE_TIME * 1000);
+
+        /* Generate the initial SAK. */
+        mka_generate_sak(link_rec, BCMOS_TRUE);
+
+        key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+        memcpy(key_info.u.ctr.key, link_rec->mka_ctrl.mka_info->sak, sizeof(key_info.u.ctr.key));
+        memcpy(key_info.u.ctr.sci, link_rec->mka_ctrl.mka_info->onu_sci, sizeof(key_info.u.ctr.sci));
+
+        /* Install the SAK as the US encryption key for this link. */
+        rc = bcmolt_epon_link_encryption_key_set(
+            link_rec->device,
+            &link_rec->key,
+            BCMOS_TRUE,
+            BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES,
+            (bcmolt_epon_key_choice)((link_rec->mka_ctrl.mka_info->key_number + 1) % 2),
+            &key_info);
+        if (rc == BCM_ERR_OK)
+        {
+            rc = mka_send_sak(link_rec, link_rec->mka_ctrl.mka_info->sak);
+            if (rc == BCM_ERR_OK)
+            {
+                /* Change to the SEND_SAK state */
+                link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_SEND_SAK;
+            }
+            else
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "send SAK failed!\n");
+            }
+        }
+        else
+        {
+            DPOE_SEC_LINK_LOG(ERROR, link_rec, "key set failed %d!\n", rc);
+        }
+    }
+    else
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "MKA process packet failed!\n");
+    }
+
+    if (rc != BCM_ERR_OK)
+    {
+        /* Notify the DPoE Security FSM of the MKA error. */
+        _dpoe_sec_mka_fsm_send_failure(link_rec, rc);
+    }
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_sak_timeout(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    return mka_process_timeout(link_rec, MKA_OP_SAK_TIMEOUT);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_sak_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    rc = mka_process_packet(link_rec, evt->data.rx_frame, MKA_OP_SAK_RSP);
+    if (rc == BCM_ERR_OK)
+    {
+        bcmolt_encryption_information_container key_info;
+
+        /* Re-start the rx timer */
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, MKA_LIFE_TIME * 1000);
+
+        key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+        memcpy(key_info.u.ctr.key, link_rec->mka_ctrl.mka_info->sak, sizeof(key_info.u.ctr.key));
+        memcpy(key_info.u.ctr.sci, link_rec->ni_mac.u8, BCMOS_ETH_ALEN);
+        key_info.u.ctr.sci[6] = (link_rec->llid >> 8) & 0xff;
+        key_info.u.ctr.sci[7] = (link_rec->llid >> 0) & 0xff;
+
+        /* Install the SAK as the US and DS encryption key for this link. */
+        rc = bcmolt_epon_link_encryption_key_set(
+            link_rec->device,
+            &link_rec->key,
+            BCMOS_FALSE,
+            BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES,
+            (bcmolt_epon_key_choice)((link_rec->mka_ctrl.mka_info->key_number + 1) % 2),
+            &key_info);
+        if (rc == BCM_ERR_OK)
+        {
+            rc = mka_send_sak_confirm(link_rec);
+            if (rc == BCM_ERR_OK)
+            {
+                /* Notify the DPoE Security FSM of the MKA result. */
+                rc = _dpoe_sec_mka_fsm_send_success(link_rec);
+
+                /* Change to the OPERATIONAL state */
+                link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_OPERATIONAL;
+            }
+        }
+    }
+
+    if (rc != BCM_ERR_OK)
+    {
+        /* Notify the DPoE Security FSM of the MKA error. */
+        _dpoe_sec_mka_fsm_send_failure(link_rec, rc);
+    }
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_send_keep_alive(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    return mka_process_timeout(link_rec, MKA_OP_SEND_KEEP_ALIVE);
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_sak_refresh(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(evt == NULL);
+
+    bcmolt_encryption_information_container key_info;
+
+    /* Generate the new SAK. */
+    mka_generate_sak(link_rec, BCMOS_FALSE);
+
+    key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+    memcpy(key_info.u.ctr.key, link_rec->mka_ctrl.mka_info->sak, sizeof(key_info.u.ctr.key));
+    memcpy(key_info.u.ctr.sci, link_rec->mka_ctrl.mka_info->onu_sci, sizeof(key_info.u.ctr.sci));
+
+    /* Install the new SAK as the US encryption key for this link. */
+    rc = bcmolt_epon_link_encryption_key_set(
+        link_rec->device,
+        &link_rec->key,
+        BCMOS_TRUE,
+        BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES,
+        (bcmolt_epon_key_choice)((link_rec->mka_ctrl.mka_info->key_number + 1) % 2),
+        &key_info);
+    if (rc == BCM_ERR_OK)
+    {
+        rc = mka_send_sak(link_rec, link_rec->mka_ctrl.mka_info->new_sak);
+        if (rc == BCM_ERR_OK)
+        {
+            /* Change to the SEND_SAK state */
+            link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_SEND_NEW_SAK;
+        }
+    }
+
+    if (BCM_ERR_OK != rc)
+    {
+        /* Notify the DPoE Security FSM of the MKA error. */
+        _dpoe_sec_mka_fsm_send_failure(link_rec, rc);
+    }
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_handle_keep_alive(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(evt == NULL);
+
+    /* Pass the keep alive message to the MKA proper code. It's okay to ignore the return status. Failure to maintain
+       the MKA connection through MKA keep alive messages will tear down the MKA connection. */
+    rc = mka_process_packet(link_rec, evt->data.rx_frame, MKA_OP_KEEP_ALIVE);
+
+    /* Check if a SAK refresh is needed. */
+    if (rc == BCM_ERR_OK)
+    {
+        /* Re-start the rx timer */
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, MKA_LIFE_TIME * 1000);
+
+        if (link_rec->mka_ctrl.mka_info->sak_refresh_needed)
+        {
+            /* Start the SAK refresh process. */
+            _dpoe_sec_mka_fsm_sak_refresh(link_rec, evt);
+
+            /* Clear the SAK refresh flag since the SAK refresh process has been started. */
+            link_rec->mka_ctrl.mka_info->sak_refresh_needed = BCMOS_FALSE;
+        }
+    }
+
+    return rc;
+}
+
+static bcmos_errno _dpoe_sec_mka_fsm_new_sak_rx_eapol(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_fsm_info == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(evt == NULL);
+
+    rc = mka_process_packet(link_rec, evt->data.rx_frame, MKA_OP_SAK_RSP);
+    if (rc == BCM_ERR_OK)
+    {
+        /* Re-start the rx timer */
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, MKA_LIFE_TIME * 1000);
+
+        bcmolt_encryption_information_container key_info;
+
+        key_info.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+        memcpy(key_info.u.ctr.key, link_rec->mka_ctrl.mka_info->sak, sizeof(key_info.u.ctr.key));
+        memcpy(key_info.u.ctr.sci, link_rec->ni_mac.u8, BCMOS_ETH_ALEN);
+        key_info.u.ctr.sci[6] = (link_rec->llid >> 8) & 0xff;
+        key_info.u.ctr.sci[7] = (link_rec->llid >> 0) & 0xff;
+
+        /* Install the new SAK as the US and DS encryption key for this link. */
+        rc = bcmolt_epon_link_encryption_key_set(
+            link_rec->device,
+            &link_rec->key,
+            BCMOS_FALSE,
+            BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES,
+            (bcmolt_epon_key_choice)((link_rec->mka_ctrl.mka_info->key_number + 1) % 2),
+            &key_info);
+        if (rc == BCM_ERR_OK)
+        {
+            rc = mka_send_sak_confirm(link_rec);
+            if (rc == BCM_ERR_OK)
+            {
+                /* Update the current SAK with the new SAK */
+                memcpy(link_rec->mka_ctrl.mka_info->sak, link_rec->mka_ctrl.mka_info->new_sak,
+                       sizeof(link_rec->mka_ctrl.mka_info->sak));
+
+                /* Change to the OPERATIONAL state */
+                link_rec->mka_ctrl.mka_fsm_state = DPOE_SEC_MKA_FSM_STATE_OPERATIONAL;
+            }
+        }
+    }
+
+    if (rc != BCM_ERR_OK)
+    {
+        /* Notify the DPoE Security FSM of the MKA error. */
+        _dpoe_sec_mka_fsm_send_failure(link_rec, rc);
+    }
+
+    return rc;
+}
+
+/** This is the ONU FSM Jump Table, indexed by STATE and EVENT. */
+static f_dpoe_sec_mka_fsm dpoe_sec_mka_fsm[DPOE_SEC_MKA_FSM_STATE__COUNT][DPOE_SEC_MKA_FSM_EVENT__COUNT] =
+{
+    [DPOE_SEC_MKA_FSM_STATE_NULL] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_INIT] = _dpoe_sec_mka_fsm_initialize
+    },
+    [DPOE_SEC_MKA_FSM_STATE_SET_ENCRYPTION] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_RX_OAM] = _dpoe_sec_mka_fsm_rx_oam,
+        [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = _dpoe_sec_mka_fsm_rx_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_STOP] = _dpoe_sec_mka_fsm_stop,
+    },
+    [DPOE_SEC_MKA_FSM_STATE_START_MKA] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL] = _dpoe_sec_mka_fsm_start_rx_eapol,
+        [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = _dpoe_sec_mka_fsm_rx_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT] = _dpoe_sec_mka_fsm_start_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_STOP] = _dpoe_sec_mka_fsm_stop
+    },
+    [DPOE_SEC_MKA_FSM_STATE_SEND_SAK] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL] = _dpoe_sec_mka_fsm_sak_rx_eapol,
+        [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = _dpoe_sec_mka_fsm_rx_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT] = _dpoe_sec_mka_fsm_sak_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_STOP] = _dpoe_sec_mka_fsm_stop
+    },
+    [DPOE_SEC_MKA_FSM_STATE_OPERATIONAL] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL] = _dpoe_sec_mka_fsm_handle_keep_alive,
+        [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = _dpoe_sec_mka_fsm_rx_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT] = _dpoe_sec_mka_fsm_send_keep_alive,
+        [DPOE_SEC_MKA_FSM_EVENT_STOP] = _dpoe_sec_mka_fsm_stop
+    },
+    [DPOE_SEC_MKA_FSM_STATE_SEND_NEW_SAK] =
+    {
+        [DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL] = _dpoe_sec_mka_fsm_new_sak_rx_eapol,
+        [DPOE_SEC_MKA_FSM_EVENT_RX_TIMEOUT] = _dpoe_sec_mka_fsm_rx_timeout,
+        [DPOE_SEC_MKA_FSM_EVENT_TX_TIMEOUT] = _dpoe_sec_mka_fsm_send_keep_alive,
+        [DPOE_SEC_MKA_FSM_EVENT_STOP] = _dpoe_sec_mka_fsm_stop
+    }
+};
+
+static bcmos_errno _dpoe_sec_mka_fsm_exec(dpoe_sec_link_rec *link_rec, dpoe_sec_mka_fsm_event *evt)
+{
+    bcmos_errno rc = BCM_ERR_PARM;
+
+    /* Parameter checks */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(evt == NULL);
+
+    if (_dpoe_sec_mka_fsm_event_type_is_valid(evt->type))
+    {
+        dpoe_sec_mka_fsm_state pre_state = link_rec->mka_ctrl.mka_fsm_state;
+
+        DPOE_SEC_LINK_LOG(DEBUG, link_rec, "MKA: processing event %s in state %s\n",
+                          _dpoe_sec_mka_fsm_event_name(evt->type), _dpoe_sec_mka_fsm_state_name(pre_state));
+
+        /* call the FSM */
+        if (NULL != dpoe_sec_mka_fsm[link_rec->mka_ctrl.mka_fsm_state][evt->type])
+        {
+            rc = dpoe_sec_mka_fsm[link_rec->mka_ctrl.mka_fsm_state][evt->type](link_rec, evt);
+        }
+        else
+        {
+            rc = _dpoe_sec_mka_fsm_error(link_rec, evt);
+        }
+
+        if (pre_state != link_rec->mka_ctrl.mka_fsm_state)
+        {
+            DPOE_SEC_LINK_LOG(DEBUG, link_rec, "MKA: transitioning from %s to %s\n",
+                              _dpoe_sec_mka_fsm_state_name(pre_state),
+                              _dpoe_sec_mka_fsm_state_name(link_rec->mka_ctrl.mka_fsm_state));
+        }
+    }
+
+    return rc;
+}
+
+void dpoe_sec_mka_fsm_rx_oam(dpoe_sec_link_rec *link_rec, uint8_t *frame, uint16_t length)
+{
+    dpoe_sec_mka_fsm_event evnt = {};
+
+    BUG_ON(link_rec == NULL);
+
+    /* Send the OAM PDU to the state machine. */
+    evnt.type = DPOE_SEC_MKA_FSM_EVENT_RX_OAM;
+    evnt.data.rx_frame.val = frame;
+    evnt.data.rx_frame.len = length;
+
+    /* Execute the state machine */
+    _dpoe_sec_mka_fsm_exec(link_rec, &evnt);
+}
+
+void dpoe_sec_mka_fsm_rx_eapol(dpoe_sec_link_rec *link_rec, uint8_t *frame, uint16_t length)
+{
+    dpoe_sec_mka_fsm_event evnt = {};
+
+    BUG_ON(link_rec == NULL);
+
+    /* Send the OAM PDU to the state machine. */
+    evnt.type = DPOE_SEC_MKA_FSM_EVENT_RX_EAPOL;
+    evnt.data.rx_frame.val = frame;
+    evnt.data.rx_frame.len = length;
+
+    /* Execute the state machine */
+    _dpoe_sec_mka_fsm_exec(link_rec, &evnt);
+}
+
+bcmos_errno dpoe_sec_mka_fsm_start(dpoe_sec_link_rec *link_rec)
+{
+    dpoe_sec_mka_fsm_event event = {};
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    /* Inject an INIT_EVENT into the FSM. */
+    event.type = DPOE_SEC_MKA_FSM_EVENT_INIT;
+
+    return _dpoe_sec_mka_fsm_exec(link_rec, &event);
+}
+
+bcmos_bool dpoe_sec_mka_fsm_running(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_bool rc = BCMOS_FALSE;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    if ((link_rec->mka_ctrl.mka_fsm_info != NULL) &&
+        (link_rec->mka_ctrl.mka_fsm_state > DPOE_SEC_MKA_FSM_STATE_NULL))
+    {
+        rc = BCMOS_TRUE;
+    }
+
+    return rc;
+}
+
+bcmos_errno dpoe_sec_mka_fsm_deregister(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    dpoe_sec_mka_fsm_event evnt = {};
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+
+    /* The deregister function is called each time the Link deregisters regardless of whether the MKA FSM is running or
+       not. Handle the case where it is not running. */
+    if (link_rec->mka_ctrl.mka_fsm_info != NULL)
+    {
+        /* Inject a stop into the MKA FSM. */
+        evnt.type = DPOE_SEC_MKA_FSM_EVENT_STOP;
+        rc = _dpoe_sec_mka_fsm_exec(link_rec, &evnt);
+    }
+
+    return rc;
+}
+
+void dpoe_sec_mka_fsm_init(f_dpoe_sec_mka_cb mka_cb)
+{
+    _mka_done_cb = mka_cb;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.h
new file mode 100644
index 0000000..f7209bb
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_mka_fsm.h
@@ -0,0 +1,90 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _DPOE_SEC_MKA_FSM_H_
+#define _DPOE_SEC_MKA_FSM_H_
+
+#include "bcmos_system.h"
+#include "dpoe_sec_util.h"
+#include "mka.h"
+
+/* --- Types --- */
+
+typedef enum dpoe_sec_mka_fsm_state
+{
+    DPOE_SEC_MKA_FSM_STATE__INVALID         = -1, /**< permits event validation */
+
+    DPOE_SEC_MKA_FSM_STATE_NULL             =  0,
+    DPOE_SEC_MKA_FSM_STATE_SET_ENCRYPTION   ,
+    DPOE_SEC_MKA_FSM_STATE_START_MKA        ,
+    DPOE_SEC_MKA_FSM_STATE_SEND_SAK         ,
+    DPOE_SEC_MKA_FSM_STATE_OPERATIONAL      ,
+    DPOE_SEC_MKA_FSM_STATE_SEND_NEW_SAK     ,
+
+    DPOE_SEC_MKA_FSM_STATE__COUNT
+} dpoe_sec_mka_fsm_state;
+
+/* DPoE Security MKA information stored with each link. */
+typedef struct dpoe_sec_mka_fsm_info
+{
+    bcmos_timer rx_timer; /**< Timer entry for MKA FSM */
+    bcmos_timer tx_timer; /**< Timer entry for MKA messages */
+} dpoe_sec_mka_fsm_info;
+
+typedef struct
+{
+    mka_link_info *mka_info; /**< MKA information */
+    dpoe_sec_mka_fsm_info *mka_fsm_info; /**< MKA FSM information */
+    dpoe_sec_mka_fsm_state mka_fsm_state; /**< State of the DPoE Security MKA FSM */
+} dpoe_sec_mka_fsm_ctrl;
+
+struct dpoe_sec_link_rec;
+
+typedef void (*f_dpoe_sec_mka_cb)(struct dpoe_sec_link_rec*, bcmos_errno status);
+
+/* --- Function prototypes --- */
+
+/* Start MKA on a link */
+bcmos_errno dpoe_sec_mka_fsm_start(struct dpoe_sec_link_rec *link);
+
+/* Handle reception of an OAM frame */
+void dpoe_sec_mka_fsm_rx_oam(struct dpoe_sec_link_rec *link, uint8_t *frame, uint16_t length);
+
+/* Handle reception of an EAPOL frame */
+void dpoe_sec_mka_fsm_rx_eapol(struct dpoe_sec_link_rec *link, uint8_t *frame, uint16_t length);
+
+/* Is the MKA FSM running on a link? */
+bcmos_bool dpoe_sec_mka_fsm_running(struct dpoe_sec_link_rec *link);
+
+/* Handle link de-registration (stop the MKA FSM) */
+bcmos_errno dpoe_sec_mka_fsm_deregister(struct dpoe_sec_link_rec *link);
+
+void dpoe_sec_mka_fsm_init(f_dpoe_sec_mka_cb mka_cb);
+
+#endif /* _DPOE_SEC_MKA_FSM_H_ */
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_user_cfg.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_user_cfg.h
new file mode 100644
index 0000000..e06d3e9
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_user_cfg.h
@@ -0,0 +1,200 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _DPOE_SEC_USER_CFG_H_
+#define _DPOE_SEC_USER_CFG_H_
+
+#include "bcm_dev_log.h"
+
+extern dev_log_id dpoe_sec_log_id[];
+
+typedef void* dpoe_sec_sha1_hash; /* A data type for holding an SHA1 hash context */
+typedef uint8_t dpoe_sec_sha1_digest[20]; /* A data type for holding an SHA1 hash */
+typedef void* dpoe_sec_md5_hash; /* A data type for holding an MD5 hash context */
+typedef uint8_t dpoe_sec_md5_digest[16]; /* A data type for holding an MD5 hash */
+typedef void* dpoe_sec_rsa_key; /* A data type for holding an RSA key */
+typedef void* dpoe_sec_aes_key; /* A data type for holding an AES key */
+
+static inline void dpoe_sec_sha1_init(dpoe_sec_sha1_hash *sha1)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should initialize/reset an SHA1 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_sha1_update(dpoe_sec_sha1_hash *sha1, const void* data, size_t len)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should update an SHA1 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_sha1_final(dpoe_sec_sha1_digest md, dpoe_sec_sha1_hash *sha1)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should finalize an SHA1 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_md5_init(dpoe_sec_md5_hash *md5)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should initialize/reset an MD5 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_md5_update(dpoe_sec_md5_hash *md5, const void* data, size_t len)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should update an MD5 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_md5_final(dpoe_sec_md5_digest md, dpoe_sec_md5_hash *md5)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should finalize an MD5 hash context\n");
+    BUG();
+}
+
+static inline void dpoe_sec_prf_expand_4346(
+    uint8_t *secret,
+    uint32_t secret_len,
+    const uint8_t *label,
+    uint32_t label_len,
+    const uint8_t *seed,
+    uint32_t seed_len,
+    uint8_t *output,
+    uint32_t output_len)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform the pseudo-random function for expansion of secrets as defined in RFC 4346, section 5\n");
+    BUG();
+}
+
+static inline dpoe_sec_rsa_key *dpoe_sec_rsa_generate_key(int bits)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should generate an RSA key\n");
+    BUG();
+}
+
+static inline void dpoe_sec_rsa_key_free(dpoe_sec_rsa_key *rsa)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should release any resources allocated for an RSA key by either dpoe_sec_rsa_generate_key or dpoe_sec_x509_pub_key_get\n");
+    BUG();
+}
+
+static inline bcmos_bool dpoe_sec_rsa_public_get(dpoe_sec_rsa_key *rsa, uint8_t *rsa_modulus, uint8_t *rsa_exp)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should retrieve the public modulus and exponent of an RSA key\n");
+    BUG();
+}
+
+static inline int dpoe_sec_rsa_size(dpoe_sec_rsa_key *rsa)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should return the size of an RSA key in bytes\n");
+    BUG();
+}
+
+static inline int dpoe_sec_rsa_private_decrypt(
+    int flen,
+    const uint8_t *from,
+    uint8_t *to,
+    dpoe_sec_rsa_key *rsa)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform RSA decryption using a private key\n");
+    BUG();
+}
+
+static inline int dpoe_sec_rsa_public_decrypt(
+    int flen,
+    const uint8_t *from,
+    uint8_t *to,
+    dpoe_sec_rsa_key *rsa)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform RSA decryption using a public key (also known as signature verification)\n");
+    BUG();
+}
+
+static inline dpoe_sec_rsa_key *dpoe_sec_x509_pub_key_get(const uint8_t *cert, uint32_t cert_len)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should retrieve an RSA public key from an X.509 cetificate\n");
+    BUG();
+}
+
+static inline void dpoe_sec_aes_set_encrypt_key(const uint8_t *user_key, const int bits, dpoe_sec_aes_key *key)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should initialize the AES key with the provided data\n");
+    BUG();
+}
+
+static inline void dpoe_sec_aes_wrap_key(
+    dpoe_sec_aes_key *key,
+    uint8_t *out,
+    const uint8_t *in,
+    uint32_t len)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform an AES key wrap on in\n");
+    BUG();
+}
+
+static inline void dpoe_sec_aes_cmac(uint8_t *key, uint8_t *msg, uint32_t len, uint8_t *code)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform AES CMAC\n");
+    BUG();
+}
+
+static inline void dpoe_sec_aes_cmac_kdf(
+    uint8_t *key,
+    const char *label,
+    uint8_t *ctx,
+    uint32_t ctx_len,
+    uint8_t *out_buf)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should perform the AES CMAC key derivation function\n");
+    BUG();
+}
+
+static inline bcmos_errno dpoe_sec_send_eapol(
+    bcmolt_devid device,
+    bcmolt_epon_link_key *link_key,
+    uint8_t *eapol,
+    uint16_t length)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should transmit an EAPOL frame to the specified link\n");
+    BUG();
+}
+
+static inline bcmos_errno dpoe_sec_rng_seed(void)
+{
+    BCM_LOG(INFO, dpoe_sec_log_id[current_device], "This function should seed a cryptographically valid pseudo-random number generator\n");
+    return BCM_ERR_OK;
+}
+
+static inline void dpoe_sec_generate_n_byte_random_number(uint8_t *output, int length)
+{
+    BCM_LOG(ERROR, dpoe_sec_log_id[current_device], "This function should generate a pseudo-random number of the specified length\n");
+    BUG();
+}
+
+#endif /* _DPOE_SEC_USER_CFG_H_ */
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.c
new file mode 100644
index 0000000..593047a
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.c
@@ -0,0 +1,77 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "dpoe_sec_util.h"
+#include "bcmolt_api.h"
+
+dev_log_id dpoe_sec_log_id[BCMTR_MAX_OLTS];
+
+bcmos_bool dpoe_sec_eapol_unpack(bcmolt_buf *buf, eapol_msg_hdr *eapol)
+{
+    return
+        bcmolt_buf_read_mac_address(buf, &eapol->da) &&
+        bcmolt_buf_read_mac_address(buf, &eapol->sa) &&
+        bcmolt_buf_read_u16_be(buf, &eapol->ether_type) &&
+        bcmolt_buf_read_u8(buf, &eapol->eapol_version) &&
+        bcmolt_buf_read_u8(buf, &eapol->eapol_packet_type) &&
+        bcmolt_buf_read_u16_be(buf, &eapol->eapol_length);
+}
+
+bcmos_errno bcmolt_epon_link_encryption_key_set(
+    bcmolt_devid device,
+    bcmolt_epon_link_key *link_key,
+    bcmos_bool up, /* TODO: create explicit up/down type for clarity */
+    bcmolt_epon_encryption_mode mode,
+    bcmolt_epon_key_choice key_choice,
+    bcmolt_encryption_information_container *key_info)
+{
+    bcmos_errno rc;
+    bcmolt_epon_link_cfg cfg;
+
+    BCMOLT_CFG_INIT(&cfg, epon_link, *link_key);
+    BCMOLT_CFG_PROP_GET(&cfg, epon_link, epon_encryption);
+    rc = bcmolt_cfg_get(device, &cfg.hdr);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+
+    if (up)
+    {
+        cfg.data.epon_encryption.upstream_mode = mode;
+        cfg.data.epon_encryption.upstream_key_choice = key_choice;
+        cfg.data.epon_encryption.upstream_encryption_information = *key_info;
+    }
+    else
+    {
+        cfg.data.epon_encryption.downstream_mode = mode;
+        cfg.data.epon_encryption.downstream_key_choice = key_choice;
+        cfg.data.epon_encryption.downstream_encryption_information = *key_info;
+    }
+
+    return bcmolt_cfg_set(device, &cfg.hdr);
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.h
new file mode 100644
index 0000000..b42fddc
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/dpoe_sec_util.h
@@ -0,0 +1,86 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _DPOE_SEC_UTIL_H_
+#define _DPOE_SEC_UTIL_H_
+
+#include "bcmos_system.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_buf.h"
+#include "bcmolt_utils.h"
+#include "bcmolt_model_types.h"
+#include "dpoe_sec_user_cfg.h"
+
+/* the EAPOL Ethernet type */
+#define ETHERTYPE_EAPOL 0x888e
+
+#define EAPOL_PROTOCOL_VERSION_DPOE 3
+
+typedef enum
+{
+    EAPOL_TYPE_PACKET,
+    EAPOL_TYPE_START,
+    EAPOL_TYPE_LOG_OFF,
+    EAPOL_TYPE_KEY,
+    EAPOL_TYPE_ASF_ALERT,
+    EAPOL_TYPE_MKA
+} eapol_type;
+
+typedef struct
+{
+    bcmos_mac_address da;
+    bcmos_mac_address sa;
+    uint16_t ether_type;
+    uint8_t eapol_version;
+    uint8_t eapol_packet_type;
+    uint16_t eapol_length;
+} eapol_msg_hdr;
+
+#define EAPOL_MSG_HDR_SIZE 18
+
+#define LINK_KEY_FMT_STR "PON %u, "BCMOS_MACADDR_FMT_STR
+#define LINK_KEY_DATA(lr) (lr)->key.epon_ni, BCMOS_MACADDR_PARAMS(&(lr)->key.mac_address)
+
+#define DPOE_SEC_LINK_LOG(level, lr, fmt, args...) \
+    BCM_LOG(level, dpoe_sec_log_id[(lr)->device], LINK_KEY_FMT_STR": "fmt, LINK_KEY_DATA(lr), ##args)
+
+static const uint32_t DPOE_SEC_OAM_TIMEOUT = 2 * 1000 * 1000; /* 2 seconds */
+
+bcmos_bool dpoe_sec_eapol_unpack(bcmolt_buf *buf, eapol_msg_hdr *eapol);
+
+bcmos_errno bcmolt_epon_link_encryption_key_set(
+    bcmolt_devid device,
+    bcmolt_epon_link_key *link_key,
+    bcmos_bool up,
+    bcmolt_epon_encryption_mode mode,
+    bcmolt_epon_key_choice key_choice,
+    bcmolt_encryption_information_container *key_info);
+
+#endif /* _DPOE_SEC_UTIL_H_ */
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.c b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.c
new file mode 100644
index 0000000..999eb23
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.c
@@ -0,0 +1,1434 @@
+/*
+  <: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.
+
+  :>
+*/
+
+/** @file mka.c
+ *  @brief Publicly accessible APIs provided to process MKA request/responses.
+ *
+ *  This file contains all of the public/private APIs used to process MKA frames.
+ *
+ * MKA Exchange Sequence :
+ *
+ * 1.  If bidirectional encryption is enabled, the OLT will generate an MKPDU
+ *     containing the basic parameter set and empty live and potential peer
+ *     lists.
+ * 2.  When the ONU receives this, it will add the OLT to its potential peers
+ *     list.
+ * 3.  Having discovered a new potential peer, the ONU will transmit an MKPDU
+ *     containing the basic parameter set, and empty live peers list, and a
+ *     potential peer list containing the OLT.
+ * 4.  When the OLT receives this, it will add the ONU to its live peers list.
+ * 5.  Having added a new live peer, the OLT will generate a new SAK and install
+ *     it for receive (using the ONUs SCI).
+ * 6.  The OLT then transmits an MKPDU containing the basic parameter set, a
+ *     live peer list containing the ONU, an empty potential peer list, a
+ *     distributed SAK, and a SAK usage parameter set indicating that the new
+ *     SAK has been installed for receive.
+ * 7.  When the ONU receives this, it will:
+ *     a. Add the OLT to its live peers list
+ *     b. Install the new SAK for receive (using the OLTs SCI)
+ *     c. Because the OLT reported the SAK in use for receive, the ONU will
+ *        install the SAK for transmit (using the ONUs SCI).
+ * 8.  The ONU will then transmit an MKPDU containing the basic parameter set,
+ *     a live peers list containing the OLT, an empty potential peer list,
+ *     and a SAK usage parameter set indicating that the SAK has been installed
+ *     for receive and transmit.
+ * 9.  When the OLT receives this, it will see that the ONU has installed the
+ *     SAK for receive and will install the SAK for transmit
+ *     (using the OLTs SCI).
+ * 10. The OLT will then transmit an MKPDU containing the basic parameter set,
+ *     a live peer list containing the ONU, an empty potential peer list and
+ *     a SAK usage parameter set indicating the SAK has been installed for
+ *     receive and transmit.  (I¡®m not sure if this step is necessary)
+ * 11. This message will produce no state change at the ONU, so it will not
+ *     respond immediately. (both the ONU and OLT will need to start a 2 second
+ *     timer every time an MKPDU is transmitted ? if they have not transmitted
+ *     an MKPDU due to a state change when the timer expires, they will
+ *     transmit an MKPDU containing the basic parameter set and live and
+ *     potential peer lists.  If either side does not receive an MKPDU from
+ *     the other for 6 seconds they must remove them from their peer lists.)
+ * 12. When the OLT determines that a new SAK is needed this protocol will
+ *     repeat steps 5-11. (modifications to the peer lists, such as 7a, should
+ *     not be required)
+ *
+ *  @defgroup mka MKA
+ *  @ingroup cmCtrl
+ *
+ */
+
+#include "mka.h"
+#include "dpoe_sec_util.h"
+#include "dpoe_sec_fsm.h"
+
+/* The destination multicast MAC address for MKA packets. */
+static const bcmos_mac_address mka_dst_mac_addr = { .u8 = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 } };
+
+#if 0
+static const char *mka_state_names[MKA_STATE__COUNT] =
+{
+    "Initial",
+    "WaitingInitialPeerResp",
+    "SakSent",
+    "MkaDone"
+};
+#endif
+
+#define MKA_VERSION                 1
+#define MKA_SERVER_PRIORITY         0x30
+#define MKA_INIT_RETRY_CNT          3
+#define MKA_BASIC_PARAM_SET_LEN     44 /* 48 - header len (4 byte) */
+#define MKA_BASIC_PARAM_SET_OPT     0x60 /* KeyServer:0, MACsecDesired:1, MACsec Capability:2 & high 4 bits of
+                                            Parameter set body length (0) */
+
+#define MKA_ALGORITHM_AGILITY       0x0080c201
+#define MKA_MN_LEN                  4 /* Message Number */
+#define MKA_KN_LEN                  4 /* Key Number */
+#define MKA_PDU_MAX_FRAME_LEN       300
+#define MKA_HELLO_TIME              2000 /* Ms, 2 seconds */
+#define MKA_MAX_HELLO_RETRIES       3 /* Max Hello retries */
+#define MKA_OLT_INIT_FRAME_LEN      76 /* basic param set(48) + null live peer list (4) + null potential peer list (4) +
+                                          Icv Indicator (4) + ICV(16) */
+#define MKA_ONE_PEER_LEN            16 /* one live peer len */
+#define MKA_DISTRIBUTED_SAK_LEN     32
+#define MKA_ICV_LEN                 16
+#define MKA_PDU_MIN_LENGTH          32
+#define MKA_KEY_WRAPPED_SAK_LEN     24
+#define MKA_SAK_USE_PARAM_BODY_LEN  40
+#define MKA_SAK_USE_PARAM_LEM       44
+#define MKA_MAX_ACCEPTABLE_PN       0xC0000000
+
+#define MKA_SAK_USE_PARAM_AN_SHIFT          6
+#define MKA_SAK_USE_PARAM_LK_TX_SHIFT       5
+#define MKA_SAK_USE_PARAM_LK_RX_SHIFT       4
+#define MKA_SAK_USE_PARAM_OLD_KEY_AN_MASK   3
+#define MKA_SAK_USE_PARAM_OLD_KEY_AN_SFT    2
+#define MKA_SAK_USE_PARAM_OLD_KEY_TX_RX     3
+#define MKA_SAK_USE_PARAM_SECOND_OPT_BYTE   0xC0
+
+#define MKA_KEY_LEN                 16 /* common key size */
+
+#define MKA_KEK_LABEL_LEN           12 /* label length */
+#define MKA_KEK_CONTEXT_LEN         16
+
+#define MKA_SAK_KEY_WRAPPED_LEN     24
+
+#define MKA_ICK_LABEL_LEN           12 /* label length */
+#define MKA_ICK_CONTEXT_LEN         16
+
+#define MKA_CKN_LABEL_LEN           16 /* label length */
+#define MKA_CKN_CONTEXT_LEN         (SIZE_OF_EAP_SESSION_ID + (BCMOS_ETH_ALEN * 2)) /* 77 */
+
+#define MKA_CAK_LABEL_LEN           16 /* label length */
+#define MKA_CAK_CONTEXT_LEN         12 /* context length */
+
+/* MKPDU Parameter set type */
+typedef enum
+{
+    MKPDU_PARAM_SET_LIVE_PEER_LIST      = 1,
+    MKPDU_PARAM_SET_POTENTIAL_PEER_LIST = 2,
+    MKPDU_PARAM_SET_MACSEC_SAK_USE      = 3,
+    MKPDU_PARAM_SET_DIST_SAK            = 4,
+    MKPDU_PARAM_SET_DIST_CAK            = 5,
+    MKPDU_PARAM_SET_KMD                 = 6,
+    MKPDU_PARAM_SET_ANNOUNCEMENT        = 7,
+    MKPDU_PARAM_SET_ICV_INDICATOR       = 255,
+} mkpdu_param_set_type;
+
+typedef enum
+{
+    MKA_MSG_TIMEOUT,
+    MKA_MSG_DATA_IND
+} mka_msg_type;
+
+typedef struct
+{
+    mka_msg_type type;
+    eapol_msg_hdr *msg;
+    bcmolt_buf *buf;
+} mka_event;
+
+/* Calculate Key Encryption Key */
+static void _mka_calc_kek(uint8_t *kek, uint8_t *cak, uint8_t *ckn)
+{
+    char kek_label[MKA_KEK_LABEL_LEN + 1] = "IEEE8021 KEK";
+
+    /* Parameter checks. */
+    BUG_ON(kek == NULL);
+    BUG_ON(cak == NULL);
+    BUG_ON(ckn == NULL);
+
+    dpoe_sec_aes_cmac_kdf(cak, kek_label, ckn, MKA_KEK_CONTEXT_LEN, kek);
+}
+
+/* Calculate Integrity Check value Key */
+static void _mka_calc_ick(uint8_t *ick, uint8_t *cak, uint8_t *ckn)
+{
+    char ick_label[MKA_ICK_LABEL_LEN + 1] = "IEEE8021 ICK";
+
+    /* Parameter checks. */
+    BUG_ON(ick == NULL);
+    BUG_ON(cak == NULL);
+    BUG_ON(ckn == NULL);
+
+    dpoe_sec_aes_cmac_kdf(cak, ick_label, ckn, MKA_ICK_CONTEXT_LEN, ick);
+}
+
+/* calculate Integrity Check Value */
+static void _mka_calc_icv(uint8_t *icv, uint8_t *ick, uint8_t *msdu, uint16_t msdu_len)
+{
+    /* Parameter checks. */
+    BUG_ON(icv == NULL);
+    BUG_ON(ick == NULL);
+    BUG_ON(msdu == NULL);
+
+    dpoe_sec_aes_cmac(ick, msdu, msdu_len, icv);
+}
+
+/* Calculate Secure Connectivity Association Key Name */
+static void _mka_calc_ckn(dpoe_sec_link_rec *link_rec, uint8_t eap_sess_id_len)
+{
+    char ckn_label[MKA_CKN_LABEL_LEN + 1] = "IEEE8021 EAP CKN";
+    uint8_t ckn_context[MKA_CKN_CONTEXT_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info->ckn == NULL);
+    BUG_ON(link_rec->auth_ctrl.trans_data.master_session_key == NULL);
+    BUG_ON(link_rec->auth_ctrl.trans_data.session_id == NULL);
+
+    memcpy(&ckn_context[0], link_rec->auth_ctrl.trans_data.session_id, eap_sess_id_len);
+    memcpy(&ckn_context[eap_sess_id_len], link_rec->mka_ctrl.mka_info->lesser_mac.u8, BCMOS_ETH_ALEN);
+    memcpy(&ckn_context[eap_sess_id_len + BCMOS_ETH_ALEN], link_rec->mka_ctrl.mka_info->greater_mac.u8, BCMOS_ETH_ALEN);
+    dpoe_sec_aes_cmac_kdf(link_rec->auth_ctrl.trans_data.master_session_key, ckn_label, ckn_context, MKA_CKN_CONTEXT_LEN, link_rec->mka_ctrl.mka_info->ckn);
+}
+
+/* Calculate Secure Connectivity Association Key */
+static void _mka_calc_cak(dpoe_sec_link_rec *link_rec)
+{
+    char cak_label[MKA_CAK_LABEL_LEN + 1] = "IEEE8021 EAP CAK";
+    uint8_t cak_context[MKA_CAK_CONTEXT_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info->cak == NULL);
+    BUG_ON(link_rec->auth_ctrl.trans_data.master_session_key == NULL);
+
+    memcpy(&cak_context[0], link_rec->mka_ctrl.mka_info->lesser_mac.u8, BCMOS_ETH_ALEN);
+    memcpy(&cak_context[BCMOS_ETH_ALEN], link_rec->mka_ctrl.mka_info->greater_mac.u8, BCMOS_ETH_ALEN);
+    dpoe_sec_aes_cmac_kdf(link_rec->auth_ctrl.trans_data.master_session_key, cak_label, cak_context, MKA_CAK_CONTEXT_LEN, link_rec->mka_ctrl.mka_info->cak);
+}
+
+/* Get OLT port Mac and link Mac & compare. This is needed for calculating CAK */
+static void _mka_get_olt_and_link_mac(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    /* compare OLT and Link MAC addresses */
+    if (memcmp(link_rec->ni_mac.u8, link_rec->key.mac_address.u8, sizeof(BCMOS_ETH_ALEN)) >= 0)
+    {
+        link_rec->mka_ctrl.mka_info->lesser_mac = link_rec->key.mac_address;
+        link_rec->mka_ctrl.mka_info->greater_mac = link_rec->ni_mac;
+    }
+    else
+    {
+        link_rec->mka_ctrl.mka_info->lesser_mac = link_rec->ni_mac;
+        link_rec->mka_ctrl.mka_info->greater_mac = link_rec->key.mac_address;
+    }
+}
+
+/* Initialize link MKA info structure */
+static void _mka_init_link_info(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    /* initialize all local values */
+    memset(link_rec->mka_ctrl.mka_info, 0, sizeof(*link_rec->mka_ctrl.mka_info));
+    dpoe_sec_generate_n_byte_random_number(link_rec->mka_ctrl.mka_info->olt_member_id, MKA_MI_LEN);
+    link_rec->mka_ctrl.mka_info->curr_msg_num = 1; /* start from 1 */
+    link_rec->mka_ctrl.mka_info->key_number = 1; /* start from 1 */
+    _mka_get_olt_and_link_mac(link_rec);
+    _mka_calc_cak(link_rec);
+    _mka_calc_ckn(link_rec, SIZE_OF_EAP_SESSION_ID);
+    _mka_calc_ick(link_rec->mka_ctrl.mka_info->ick, link_rec->mka_ctrl.mka_info->cak, link_rec->mka_ctrl.mka_info->ckn);
+    _mka_calc_kek(link_rec->mka_ctrl.mka_info->kek, link_rec->mka_ctrl.mka_info->cak, link_rec->mka_ctrl.mka_info->ckn);
+}
+
+static void _mka_clear_link_info(dpoe_sec_link_rec *link_rec)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    memset(link_rec->mka_ctrl.mka_info, 0, sizeof(*link_rec->mka_ctrl.mka_info));
+}
+
+/* Build EAPOL header for MKPDU */
+static bcmos_bool _mka_build_eapol_header(dpoe_sec_link_rec *link_rec, bcmolt_buf *out_buf, uint16_t length)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(out_buf == NULL);
+
+    return
+        bcmolt_buf_write_mac_address(out_buf, mka_dst_mac_addr) &&
+        bcmolt_buf_write_mac_address(out_buf, link_rec->ni_mac) &&
+        bcmolt_buf_write_u16_be(out_buf, ETHERTYPE_EAPOL) &&
+        bcmolt_buf_write_u8(out_buf, EAPOL_PROTOCOL_VERSION_DPOE) &&
+        bcmolt_buf_write_u8(out_buf, EAPOL_TYPE_MKA) &&
+        bcmolt_buf_write_u16_be(out_buf, length);
+}
+
+/* Build Basic parameter set. This parameter set is required for every MKPDU */
+static bcmos_bool _mka_build_basic_parameter_set(dpoe_sec_link_rec *link_rec, bcmolt_buf *out_buf)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(out_buf == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    return
+        bcmolt_buf_write_u8(out_buf, MKA_VERSION) && /* Version Number, 802.1X Pg.98 */
+        bcmolt_buf_write_u8(out_buf, MKA_SERVER_PRIORITY) && /* Key Server Priority */
+        bcmolt_buf_write_u8(out_buf, MKA_BASIC_PARAM_SET_OPT) &&
+        bcmolt_buf_write_u8(out_buf, MKA_BASIC_PARAM_SET_LEN) &&
+        bcmolt_buf_write_mac_address(out_buf, link_rec->ni_mac) && /* 6 byte of SCI */
+        bcmolt_buf_write_u16_be(out_buf, link_rec->llid) && /* lower 2 byte SCI */
+        bcmolt_buf_write(out_buf, link_rec->mka_ctrl.mka_info->olt_member_id, MKA_MI_LEN) &&
+        bcmolt_buf_write_u32_be(out_buf, link_rec->mka_ctrl.mka_info->curr_msg_num++) && /* Important !! */
+        bcmolt_buf_write_u32_be(out_buf, MKA_ALGORITHM_AGILITY) &&
+        bcmolt_buf_write(out_buf, link_rec->mka_ctrl.mka_info->ckn, MKA_CKN_LEN / 8); /* CAK Name(CKN) */
+}
+
+/* Build an empty Live Peer or Potential Peer list parameter set */
+static bcmos_bool _mka_build_empty_peer_list(bcmolt_buf *out_buf, uint8_t type)
+{
+    /* Parameter checks. */
+    BUG_ON(out_buf == NULL);
+
+    return
+        bcmolt_buf_write_u8(out_buf, type) && /* Parameter set type */
+        bcmolt_buf_write_u8(out_buf, 0) &&
+        bcmolt_buf_write_u8(out_buf, 0) && /* options & 4 bit length */
+        bcmolt_buf_write_u8(out_buf, 0); /* Parameter set body length. */
+}
+
+/* Build Live Peer List or Potential Peer List parameter set */
+static bcmos_bool _mka_build_peer_list(
+    bcmolt_buf *out_buf,
+    uint8_t type,
+    const uint8_t *member_id,
+    uint32_t message_num)
+{
+    /* Parameter checks. */
+    BUG_ON(out_buf == NULL);
+    BUG_ON(member_id == NULL);
+
+    return
+        bcmolt_buf_write_u8(out_buf, type) && /* Parameter set type */
+        bcmolt_buf_write_u8(out_buf, 0) &&
+        bcmolt_buf_write_u8(out_buf, 0) && /* options & 4 bit length */
+        bcmolt_buf_write_u8(out_buf, MKA_MI_LEN + sizeof(uint32_t)) && /* Parameter set body length. */
+        bcmolt_buf_write(out_buf, member_id, MKA_MI_LEN) &&
+        bcmolt_buf_write_u32_be(out_buf, message_num);
+}
+
+/* Build ICV Indicator parameter set */
+static bcmos_bool _mka_build_icv_indicator(bcmolt_buf *out_buf)
+{
+    /* Parameter checks. */
+    BUG_ON(out_buf == NULL);
+
+    return
+        bcmolt_buf_write_u8(out_buf, MKPDU_PARAM_SET_ICV_INDICATOR) &&
+        bcmolt_buf_write_u8(out_buf, 0) &&
+        bcmolt_buf_write_u8(out_buf, 0) && /* options & 4 bit length */
+        bcmolt_buf_write_u8(out_buf, MKA_ICV_LEN); /* Parameter set body length. */
+}
+
+/* Build Distributed SAK parameter set (GCM-AES-128) */
+static bcmos_bool _mka_build_distributed_sak(
+    const dpoe_sec_link_rec *link_rec,
+    bcmolt_buf *out_buf,
+    const uint8_t *key_wrapped_sak)
+{
+    uint8_t option;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(out_buf == NULL);
+    BUG_ON(key_wrapped_sak == NULL);
+
+    /* need Distributed AN & Confidentiality Offset (bit 5,4. use 0) */
+    option = link_rec->mka_ctrl.mka_info->association_number;
+
+    option <<= MKA_SAK_USE_PARAM_AN_SHIFT; /* move to bit 7,6 */
+
+    /* key number & AN will be increased from the caller */
+    if (!bcmolt_buf_write_u8(out_buf, MKPDU_PARAM_SET_DIST_SAK) ||
+        !bcmolt_buf_write_u8(out_buf, option) ||
+        !bcmolt_buf_write_u8(out_buf, 0) || /* options & 4 bit length */
+        !bcmolt_buf_write_u8(out_buf, MKA_KEY_WRAPPED_SAK_LEN + MKA_KN_LEN) ||
+        !bcmolt_buf_write_u32_be(out_buf, link_rec->mka_ctrl.mka_info->key_number) ||
+        !bcmolt_buf_write(out_buf, key_wrapped_sak, MKA_KEY_WRAPPED_SAK_LEN))
+    {
+        return BCMOS_FALSE;
+    }
+    return BCMOS_TRUE;
+}
+
+/* Build MACsec SAK Use paramater set */
+static bcmos_bool _mka_build_sak_use_param(
+    const dpoe_sec_link_rec *link_rec,
+    bcmolt_buf *out_buf,
+    bcmos_bool set_tx,
+    bcmos_bool set_rx)
+{
+    uint8_t option1 = 0;
+    uint8_t option2 = 0;
+    uint8_t tmp = 0;
+    uint32_t old_kn;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+    BUG_ON(out_buf == NULL);
+
+    /* option 1: Latest Key AN(7:6), Latest Key Tx(5), Latest Key Rx(4) Old Key AN(3:2), Old Key Tx(1), Old Key Rx(0) */
+    option1 = link_rec->mka_ctrl.mka_info->association_number;
+    option1 <<= MKA_SAK_USE_PARAM_AN_SHIFT; /* move to bit 7,6 (AN) */
+
+    if (set_tx)
+    {
+        tmp = 1 << MKA_SAK_USE_PARAM_LK_TX_SHIFT; /* Latest Key TX */
+    }
+    if (set_rx)
+    {
+        tmp = tmp | (1 << MKA_SAK_USE_PARAM_LK_RX_SHIFT); /* Latest Key Rx */
+    }
+
+    /* for first frame, all 0 is used for old key info */
+    if (link_rec->mka_ctrl.mka_info->refresh_cnt != 0)
+    {
+        /* for OLD AN, tx, rx bits */
+        tmp = tmp | ((link_rec->mka_ctrl.mka_info->association_number & MKA_SAK_USE_PARAM_OLD_KEY_AN_MASK) << MKA_SAK_USE_PARAM_OLD_KEY_AN_SFT);
+        tmp = tmp | MKA_SAK_USE_PARAM_OLD_KEY_TX_RX; /* should have been tx & rx */
+    }
+    option1 |= tmp;
+
+    /* option 2: only set bit 7,6(plain tx & plain rx). all other bits including 4 bits length field should be 0 */
+    option2 = MKA_SAK_USE_PARAM_SECOND_OPT_BYTE;
+
+    /* need to decrement key number for old key (because key numer may be already incremented) */
+    if (link_rec->mka_ctrl.mka_info->key_number == 1) /* initial number */
+    {
+        old_kn = 1;
+    }
+    else
+    {
+        old_kn = link_rec->mka_ctrl.mka_info->key_number - 1;
+    }
+
+    if (!bcmolt_buf_write_u8(out_buf, MKPDU_PARAM_SET_MACSEC_SAK_USE) ||
+        !bcmolt_buf_write_u8(out_buf, option1) ||
+        !bcmolt_buf_write_u8(out_buf, option2) ||
+        !bcmolt_buf_write_u8(out_buf, MKA_SAK_USE_PARAM_BODY_LEN) ||
+        !bcmolt_buf_write(out_buf, link_rec->mka_ctrl.mka_info->olt_member_id, MKA_MI_LEN) ||
+        !bcmolt_buf_write_u32_be(out_buf, link_rec->mka_ctrl.mka_info->key_number) ||
+        !bcmolt_buf_write_u32_be(out_buf, 1) || /* Lowest acceptable PN, start from 1 */
+        !bcmolt_buf_write(out_buf, link_rec->mka_ctrl.mka_info->olt_member_id, MKA_MI_LEN) ||
+        !bcmolt_buf_write_u32_be(out_buf, old_kn) ||
+        !bcmolt_buf_write_u32_be(out_buf, 1))
+    {
+        return BCMOS_FALSE;
+    }
+
+    return BCMOS_TRUE;
+}
+
+/* Calculate ICV and attach it at the end of MKPDU */
+static bcmos_bool _mka_calc_and_attach_icv(mka_link_info *mka_info, bcmolt_buf *out_buf)
+{
+    uint8_t ick[MKA_KEY_LEN];
+    uint8_t icv[MKA_KEY_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(mka_info == NULL);
+    BUG_ON(out_buf == NULL);
+
+    _mka_calc_ick(ick, mka_info->cak, mka_info->ckn);
+
+    /* ICV will be calculated from DA to end of ICV indicator */
+    _mka_calc_icv(icv, ick, out_buf->start, (uint16_t)bcmolt_buf_get_used(out_buf));
+
+    return bcmolt_buf_write(out_buf, icv, MKA_KEY_LEN);
+}
+
+/* Send the first MKA frame to ONU */
+static bcmos_bool _mka_send_init_frame(dpoe_sec_link_rec *link_rec)
+{
+    bcmolt_buf out_buf;
+    uint8_t frame[MKA_PDU_MAX_FRAME_LEN];
+    bcmos_bool ret = BCMOS_FALSE;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    bcmolt_buf_init(&out_buf, MKA_PDU_MAX_FRAME_LEN, frame, BCMOLT_BUF_ENDIAN_FIXED);
+    /* construct frame */
+    if (!_mka_build_eapol_header(link_rec, &out_buf, MKA_OLT_INIT_FRAME_LEN) ||
+        !_mka_build_basic_parameter_set(link_rec, &out_buf) ||
+        !_mka_build_empty_peer_list(&out_buf, MKPDU_PARAM_SET_LIVE_PEER_LIST) ||
+        !_mka_build_empty_peer_list(&out_buf, MKPDU_PARAM_SET_POTENTIAL_PEER_LIST) ||
+        !_mka_build_icv_indicator(&out_buf) ||
+        !_mka_calc_and_attach_icv(link_rec->mka_ctrl.mka_info, &out_buf)) /* last important step. Calc & attach ICV */
+    {
+        return ret;
+    }
+
+    if (BCM_ERR_OK == dpoe_sec_send_eapol(link_rec->device, &link_rec->key, frame, bcmolt_buf_get_used(&out_buf)))
+    {
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->rx_timer, MKA_LIFE_TIME * 1000);
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->tx_timer, MKA_HELLO_TIME * 1000);
+        ret = BCMOS_TRUE;
+    }
+
+    return ret;
+}
+
+/* Generate a SAK using RNG */
+void mka_generate_sak(dpoe_sec_link_rec *link_rec, bcmos_bool initial)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    /* Generate SAK */
+    if (initial)
+    {
+        _mka_calc_kek(link_rec->mka_ctrl.mka_info->kek, link_rec->mka_ctrl.mka_info->cak, link_rec->mka_ctrl.mka_info->ckn);
+        dpoe_sec_generate_n_byte_random_number(link_rec->mka_ctrl.mka_info->sak, MKA_KEY_LEN);
+    }
+    else
+    {
+        dpoe_sec_generate_n_byte_random_number(link_rec->mka_ctrl.mka_info->new_sak, MKA_KEY_LEN);
+    }
+}
+
+/* Send the SAK to the ONU */
+bcmos_errno mka_send_sak(dpoe_sec_link_rec *link_rec, uint8_t *sak)
+{
+    bcmolt_buf out_buf;
+    uint8_t frame[MKA_PDU_MAX_FRAME_LEN];
+    uint8_t key_wrapped_sak[MKA_KEY_LEN * 2] = {};
+    bcmos_errno rc = BCM_ERR_COMM_FAIL;
+    dpoe_sec_aes_key aes_key;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    bcmolt_buf_init(&out_buf, MKA_PDU_MAX_FRAME_LEN, frame, BCMOLT_BUF_ENDIAN_FIXED);
+    /* key wrap sak using kek */
+    dpoe_sec_aes_set_encrypt_key(link_rec->mka_ctrl.mka_info->kek, MKA_KEK_LEN, &aes_key);
+    dpoe_sec_aes_wrap_key(&aes_key, key_wrapped_sak, sak, MKA_SAK_LEN);
+
+    /* construct frame */
+    if (!_mka_build_eapol_header(
+            link_rec,
+            &out_buf,
+            MKA_OLT_INIT_FRAME_LEN + MKA_ONE_PEER_LEN + MKA_DISTRIBUTED_SAK_LEN + MKA_SAK_USE_PARAM_LEM) ||
+        !_mka_build_basic_parameter_set(link_rec, &out_buf) ||
+        !_mka_build_peer_list(
+            &out_buf,
+            MKPDU_PARAM_SET_LIVE_PEER_LIST,
+            link_rec->mka_ctrl.mka_info->link_memeber_id,
+            link_rec->mka_ctrl.mka_info->link_msg_num) ||
+        !_mka_build_empty_peer_list(&out_buf, MKPDU_PARAM_SET_POTENTIAL_PEER_LIST) ||
+        !_mka_build_distributed_sak(link_rec, &out_buf, key_wrapped_sak) ||
+        !_mka_build_sak_use_param(link_rec, &out_buf, BCMOS_FALSE, BCMOS_TRUE) || /* set RX only */
+        !_mka_build_icv_indicator(&out_buf))
+    {
+        return BCM_ERR_OVERFLOW;
+    }
+
+    /* last important step. Calc & attach ICV */
+    _mka_calc_and_attach_icv(link_rec->mka_ctrl.mka_info, &out_buf);
+
+    if (BCM_ERR_OK == dpoe_sec_send_eapol(link_rec->device, &link_rec->key, frame, bcmolt_buf_get_used(&out_buf)))
+    {
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->tx_timer, MKA_HELLO_TIME * 1000);
+        rc = BCM_ERR_OK;
+    }
+
+    return rc;
+}
+
+/* Received SAK response. Send confirm frame to ONU */
+static bcmos_bool _mka_send_sak_confirm_frame(dpoe_sec_link_rec *link_rec)
+{
+    bcmolt_buf out_buf;
+    uint8_t frame[MKA_PDU_MAX_FRAME_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    bcmolt_buf_init(&out_buf, MKA_PDU_MAX_FRAME_LEN, frame, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!_mka_build_eapol_header(link_rec, &out_buf, MKA_OLT_INIT_FRAME_LEN + MKA_ONE_PEER_LEN + MKA_SAK_USE_PARAM_LEM) ||
+        !_mka_build_basic_parameter_set(link_rec, &out_buf) ||
+        !_mka_build_peer_list(
+            &out_buf, MKPDU_PARAM_SET_LIVE_PEER_LIST,
+            link_rec->mka_ctrl.mka_info->link_memeber_id,
+            link_rec->mka_ctrl.mka_info->link_msg_num) ||
+        !_mka_build_empty_peer_list(&out_buf, MKPDU_PARAM_SET_POTENTIAL_PEER_LIST) ||
+        !_mka_build_sak_use_param(link_rec, &out_buf, BCMOS_TRUE, BCMOS_TRUE) ||
+        !_mka_build_icv_indicator(&out_buf))
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* last important step. Calc & attach ICV */
+    _mka_calc_and_attach_icv(link_rec->mka_ctrl.mka_info, &out_buf);
+
+    return BCM_ERR_OK == dpoe_sec_send_eapol(link_rec->device, &link_rec->key, frame, bcmolt_buf_get_used(&out_buf));
+}
+
+/* send keep-alive frame every 2 seconds */
+static bcmos_bool _mka_send_keepalive_frame(dpoe_sec_link_rec *link_rec)
+{
+    bcmolt_buf out_buf;
+    uint8_t frame[MKA_PDU_MAX_FRAME_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    bcmolt_buf_init(&out_buf, MKA_PDU_MAX_FRAME_LEN, frame, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!_mka_build_eapol_header(link_rec, &out_buf, MKA_OLT_INIT_FRAME_LEN + MKA_ONE_PEER_LEN) ||
+        !_mka_build_basic_parameter_set(link_rec, &out_buf) ||
+        !_mka_build_peer_list(
+            &out_buf,
+            MKPDU_PARAM_SET_LIVE_PEER_LIST,
+            link_rec->mka_ctrl.mka_info->link_memeber_id,
+            link_rec->mka_ctrl.mka_info->link_msg_num) ||
+        !_mka_build_empty_peer_list(&out_buf, MKPDU_PARAM_SET_POTENTIAL_PEER_LIST) ||
+        !_mka_build_icv_indicator(&out_buf))
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* last important step. Calc & attach ICV */
+    _mka_calc_and_attach_icv(link_rec->mka_ctrl.mka_info, &out_buf);
+
+    return BCM_ERR_OK == dpoe_sec_send_eapol(link_rec->device, &link_rec->key, frame, bcmolt_buf_get_used(&out_buf));
+}
+
+/* Entry point for MKA process. It is called to start the MKA exchange with the ONU that owns the specified link. */
+bcmos_errno mka_start(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    /* initialize link MKA info */
+    _mka_init_link_info(link_rec);
+
+    /* send initial frame (basic param + empty live & potential peer list */
+    if (_mka_send_init_frame(link_rec))
+    {
+        rc = BCM_ERR_OK;
+    }
+    else
+    {
+        rc = BCM_ERR_COMM_FAIL;
+    }
+
+    return rc;
+}
+
+/* Calculate the ICV over the message and compare with the ICV in the message. */
+static bcmos_errno _mka_compare_icv(mka_link_info *info, const mka_event *msg, uint8_t *rx_icv)
+{
+    uint16_t len = 0;
+    uint8_t icv[MKA_ICV_LEN];
+
+    /* Parameter checks. */
+    BUG_ON(info == NULL);
+    BUG_ON(msg == NULL);
+
+    /* If the ICV pointer is NULL, then there was no ICV in the received MKA packet. Just return an error. */
+    if (rx_icv == NULL)
+    {
+        return BCM_ERR_PARM;
+    }
+
+    /* Calculate the length of the buffer over which to calculate the ICV. */
+    len = (EAPOL_MSG_HDR_SIZE + msg->msg->eapol_length) - MKA_ICV_LEN;
+
+    _mka_calc_icv(icv, info->ick, msg->buf->start, len);
+
+    if (memcmp(icv, rx_icv, sizeof(icv)) != 0)
+    {
+        return BCM_ERR_ONU_ERR_RESP;
+    }
+
+    return BCM_ERR_OK;
+}
+
+/* Parse the Basic Parameter Set in MKA PDUs */
+static bcmos_errno _mka_parse_basic_param_set(
+    dpoe_sec_link_rec *link_rec,
+    bcmolt_buf *buf,
+    uint16_t *bytes_read,
+    uint16_t body_len,
+    uint8_t *sci,
+    uint8_t *member_id,
+    uint32_t *msg_num)
+{
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(buf == NULL);
+    BUG_ON(bytes_read == NULL);
+
+    /* Read the ONU SCI */
+    if (!bcmolt_buf_read(buf, sci, MKA_SCI_LEN))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    /* Read the Member ID */
+    if (!bcmolt_buf_read(buf, member_id, MKA_MI_LEN))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    /* Read the ONU message number. */
+    if (!bcmolt_buf_read_u32_be(buf, msg_num))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    /* The SCI, MI, and MN were read from the packet. */
+    *bytes_read = MKA_SCI_LEN + MKA_MI_LEN + MKA_MN_LEN;
+
+    /* Skip the rest */
+    if (!bcmolt_buf_skip(buf, body_len - *bytes_read))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno _mka_waiting_initial_response_data_ind(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_PARSE;
+    uint8_t *icv_ptr;
+    uint8_t param_set_type;
+    uint16_t body_len;
+    bcmos_bool basic_param_set_exist = BCMOS_FALSE;
+    bcmos_bool live_peer_list_exist = BCMOS_FALSE;
+    bcmos_bool potential_peer_list_exist = BCMOS_FALSE;
+    uint16_t bytes_read;
+
+    /* Validate the msg pointer. */
+    BUG_ON(msg->buf == NULL);
+    BUG_ON(msg->msg == NULL);
+
+    /* mark ICV field offset */
+    icv_ptr = msg->buf->curr + (msg->msg->eapol_length - MKA_ICV_LEN);
+
+    /* Validate the Integrity Check Value before continuing to process the packet. */
+    rc = _mka_compare_icv(link_rec->mka_ctrl.mka_info, msg, icv_ptr);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+
+    while (bcmolt_buf_get_remaining_size(msg->buf) > 0)
+    {
+        /* Don't parse past the ICV Parameter Set */
+        if (bcmolt_buf_snap_get(msg->buf) >= icv_ptr)
+        {
+            break;
+        }
+
+        /* Read what should be the Basic Parameter Set header. */
+        if (!bcmolt_buf_read_u8(msg->buf, &param_set_type) ||
+            !bcmolt_buf_skip(msg->buf, 1) ||
+            !bcmolt_buf_read_u16_be(msg->buf, &body_len))
+        {
+            break;
+        }
+
+        /* The body length is really 12-bits. */
+        body_len &= 0x0fff;
+
+        if (param_set_type == MKPDU_PARAM_SET_LIVE_PEER_LIST)
+        {
+            /* need to find out whether it is Basic parameter set OR Live Peer List */
+            if (body_len >= MKA_BASIC_PARAM_SET_LEN)
+            {
+                rc = _mka_parse_basic_param_set(
+                    link_rec,
+                    msg->buf,
+                    &bytes_read,
+                    body_len,
+                    link_rec->mka_ctrl.mka_info->onu_sci,
+                    link_rec->mka_ctrl.mka_info->link_memeber_id,
+                    &link_rec->mka_ctrl.mka_info->link_msg_num);
+                if (rc != BCM_ERR_OK)
+                {
+                    DPOE_SEC_LINK_LOG(ERROR, link_rec, "parse basic param set failed!\n");
+                    break;
+                }
+
+                /* Mark that the Basic Parameter Set was found. */
+                basic_param_set_exist = BCMOS_TRUE;
+            }
+            else
+            {
+                /* Live Peer List. skip */
+                if (!bcmolt_buf_skip(msg->buf, body_len))
+                {
+                    DPOE_SEC_LINK_LOG(ERROR, link_rec, "parse live peer list failed!\n");
+                    rc = BCM_ERR_PARSE;
+                    break;
+                }
+
+                /* Mark that the Live Peer List Parameter Set was found. */
+                live_peer_list_exist = BCMOS_TRUE;
+            }
+        }
+        else if (param_set_type == MKPDU_PARAM_SET_POTENTIAL_PEER_LIST)
+        {
+            uint8_t mi[MKA_MI_LEN];
+
+            if (!bcmolt_buf_read(msg->buf, mi, sizeof(mi)))
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "parse potential peer list failed!\n");
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+
+            /* Verify that the OLT member ID in the Potential Peer List Parameter Set matches the expected OLT member
+               ID. */
+            if (memcmp(mi, link_rec->mka_ctrl.mka_info->olt_member_id, MKA_MI_LEN) != 0)
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "wrong member ID!\n");
+                rc = BCM_ERR_ONU_ERR_RESP;
+                break;
+            }
+
+            /* Skip the remaining bytes. */
+            if (!bcmolt_buf_skip(msg->buf, body_len - MKA_MI_LEN))
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "buffer overflow!\n");
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+
+            /* Mark that the Potential Peer List Parameter Set was found. */
+            potential_peer_list_exist = BCMOS_TRUE;
+        }
+        else
+        {
+            /* Skip other Parameter Set data. */
+            if (!bcmolt_buf_skip(msg->buf, body_len))
+            {
+                DPOE_SEC_LINK_LOG(ERROR, link_rec, "buffer overflow!\n");
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+        }
+    }
+
+    if ((rc == BCM_ERR_OK) &&
+        basic_param_set_exist &&
+        live_peer_list_exist &&
+        potential_peer_list_exist &&
+        (link_rec->mka_ctrl.mka_info->peer_state != MKA_PEER_STATE_LIVE))
+    {
+        /* proceed to next state */
+        link_rec->mka_ctrl.mka_info->peer_state = MKA_PEER_STATE_LIVE;
+    }
+    else
+    {
+        rc = BCM_ERR_ONU_ERR_RESP;
+    }
+
+    return rc;
+}
+
+/* Handler for initial response from the ONU */
+static bcmos_errno _mka_waiting_initial_response(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks. */
+    BUG_ON(msg == NULL);
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    switch (msg->type)
+    {
+        case MKA_MSG_TIMEOUT:
+            /* retry 3 times */
+            if (link_rec->mka_ctrl.mka_info->retry_cnt < MKA_INIT_RETRY_CNT)
+            {
+                link_rec->mka_ctrl.mka_info->retry_cnt++;
+                (void)_mka_send_init_frame(link_rec);
+            }
+            else
+            {
+                _mka_clear_link_info(link_rec);
+                rc = BCM_ERR_TIMEOUT;
+            }
+            break;
+
+        case MKA_MSG_DATA_IND:
+            rc = _mka_waiting_initial_response_data_ind(msg, link_rec);
+            break;
+
+        default:
+            rc = BCM_ERR_ONU_ERR_RESP;
+            break;
+    }
+
+    return rc;
+}
+
+static bcmos_errno _mka_initial_sak_sent_data_ind(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_PARSE;
+    uint8_t *icv_ptr;
+    uint8_t param_set_type;
+    uint8_t ks_priority;
+    uint16_t body_len;
+    bcmos_bool basic_param_ok = BCMOS_FALSE;
+    bcmos_bool live_peer_list_ok = BCMOS_FALSE;
+    bcmos_bool potential_peer_list_ok = BCMOS_FALSE;
+    bcmos_bool sak_use_param_ok = BCMOS_FALSE;
+    uint16_t bytes_read;
+
+    /* Validate the msg pointer. */
+    BUG_ON(msg->msg == NULL);
+    BUG_ON(msg->buf == NULL);
+
+    /* mark ICV field offset */
+    icv_ptr = msg->buf->curr + (msg->msg->eapol_length - MKA_ICV_LEN);
+
+    /* Validate the Integrity Check Value before continuing to process the packet. */
+    rc = _mka_compare_icv(link_rec->mka_ctrl.mka_info, msg, icv_ptr);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+
+    while (bcmolt_buf_get_remaining_size(msg->buf) > 0)
+    {
+        /* Don't parse past the ICV Parameter Set */
+        if (bcmolt_buf_snap_get(msg->buf) >= icv_ptr)
+        {
+            break;
+        }
+
+        /* Read what should be the Basic Parameter Set header. */
+        if (!bcmolt_buf_read_u8(msg->buf, &param_set_type) ||
+            !bcmolt_buf_read_u8(msg->buf, &ks_priority) ||
+            !bcmolt_buf_read_u16_be(msg->buf, &body_len))
+        {
+            rc = BCM_ERR_PARSE;
+            break;
+        }
+
+        /* The body length is really 12-bits. */
+        body_len &= 0x0fff;
+
+        if (param_set_type == MKPDU_PARAM_SET_LIVE_PEER_LIST)
+        {
+            /* need to find out whether it is Basic parameter set OR Live Peer List */
+            if (body_len >= MKA_BASIC_PARAM_SET_LEN)
+            {
+                /* in our environment, ONU can not have more than one live peer. So, just seeing param set body len
+                   should be good enough. */
+                uint32_t link_mn;
+                uint8_t sci[MKA_SCI_LEN];
+                uint8_t mi[MKA_MI_LEN];
+
+                rc = _mka_parse_basic_param_set(link_rec, msg->buf, &bytes_read, body_len, sci, mi, &link_mn);
+                if (rc != BCM_ERR_OK)
+                {
+                    break;
+                }
+
+                /* Verify that ONU SCI is what's expected. */
+                if (memcmp(link_rec->mka_ctrl.mka_info->onu_sci, sci, MKA_SCI_LEN) != 0)
+                {
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                    break;
+                }
+
+                /* Verify that ONU MI is what's expected. */
+                if (memcmp(link_rec->mka_ctrl.mka_info->link_memeber_id, mi, MKA_MI_LEN) != 0)
+                {
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                    break;
+                }
+
+                /* Verify that the MN is what's expected. */
+                if (link_rec->mka_ctrl.mka_info->link_msg_num < link_mn)
+                {
+                    /* correct MN! */
+                    link_rec->mka_ctrl.mka_info->link_msg_num = link_mn;
+                    basic_param_ok = BCMOS_TRUE;
+                }
+            }
+            else
+            {
+                /* Live Peer List. skip */
+                if (!bcmolt_buf_skip(msg->buf, body_len))
+                {
+                    rc = BCM_ERR_PARSE;
+                    break;
+                }
+
+                /* Mark that the Live Peer List Parameter Set was found. */
+                live_peer_list_ok = BCMOS_TRUE;
+            }
+        }
+        else if (param_set_type == MKPDU_PARAM_SET_POTENTIAL_PEER_LIST)
+        {
+            /* Skip Potential Peer List Parameter Set. */
+            if (!bcmolt_buf_skip(msg->buf, body_len))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+
+            /* Mark that the Potential Peer List Parameter Set was found. */
+            potential_peer_list_ok = BCMOS_TRUE;
+        }
+        else if (param_set_type == MKPDU_PARAM_SET_MACSEC_SAK_USE)
+        {
+            /* check only Latest key tx & rx bits (bit 4 & 5) */
+            if ((ks_priority & 0x30) == 0x30)
+            {
+                sak_use_param_ok = BCMOS_TRUE;
+            }
+
+            /* Skip the MKA SAK Use Parameter Set. */
+            if (!bcmolt_buf_skip(msg->buf, body_len))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+        }
+        else
+        {
+            /* Skip all other Parameter Set data. */
+            if (!bcmolt_buf_skip(msg->buf, body_len))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+        }
+    }
+
+    /* Everything looks good so far? */
+    if ((rc == BCM_ERR_OK) &&
+        basic_param_ok &&
+        live_peer_list_ok &&
+        potential_peer_list_ok &&
+        sak_use_param_ok)
+    {
+        DPOE_SEC_LINK_LOG(DEBUG, link_rec, "Got good MKA SAK response\n");
+    }
+
+    return rc;
+}
+
+/* Response frame handler for the first SAK from the OLT */
+static bcmos_errno _mka_initial_sak_sent(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks. */
+    BUG_ON(msg == NULL);
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    switch (msg->type)
+    {
+        case MKA_MSG_TIMEOUT:
+            _mka_clear_link_info(link_rec);
+            rc = BCM_ERR_TIMEOUT;
+            break;
+
+        case MKA_MSG_DATA_IND:
+            rc = _mka_initial_sak_sent_data_ind(msg, link_rec);
+            break;
+
+        default:
+            rc = BCM_ERR_ONU_ERR_RESP;
+            break;
+    }
+
+    return rc;
+}
+
+static bcmos_errno _mka_sak_done_data_ind(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_PARSE;
+    uint8_t *icv_ptr;
+    uint8_t param_set_type;
+    uint16_t body_len;
+    uint8_t ks_priority;
+    uint16_t bytes_read;
+
+    /* Validate the msg pointer. */
+    BUG_ON(msg->msg == NULL);
+    BUG_ON(msg->buf == NULL);
+
+    /* mark ICV field offset */
+    icv_ptr = msg->buf->curr + (msg->msg->eapol_length - MKA_ICV_LEN);
+
+    /* Validate the Integrity Check Value before continuing to process the packet. */
+    rc = _mka_compare_icv(link_rec->mka_ctrl.mka_info, msg, icv_ptr);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
+
+    while (bcmolt_buf_get_remaining_size(msg->buf) > 0)
+    {
+        /* Don't parse past the ICV Parameter Set */
+        if (bcmolt_buf_snap_get(msg->buf) >= icv_ptr)
+        {
+            break;
+        }
+
+        /* Read what should be the Basic Parameter Set header. */
+        if (!bcmolt_buf_read_u8(msg->buf, &param_set_type) ||
+            !bcmolt_buf_read_u8(msg->buf, &ks_priority) ||
+            !bcmolt_buf_read_u16_be(msg->buf, &body_len))
+        {
+            rc = BCM_ERR_PARSE;
+            break;
+        }
+
+        /* The body length is really 12-bits. */
+        body_len &= 0x0fff;
+
+        if (param_set_type == MKPDU_PARAM_SET_LIVE_PEER_LIST)
+        {
+            /* need to find out whether it is Basic parameter set OR Live Peer List */
+            if (body_len >= MKA_BASIC_PARAM_SET_LEN)
+            {
+                /* in our environment, ONU can not have more than one live peer. So, just seeing param set body len
+                   should be good enough. */
+                uint32_t link_mn;
+                uint8_t sci[MKA_SCI_LEN];
+                uint8_t mi[MKA_MI_LEN];
+
+                rc = _mka_parse_basic_param_set(link_rec, msg->buf, &bytes_read, body_len, sci, mi, &link_mn);
+                if (rc != BCM_ERR_OK)
+                {
+                    break;
+                }
+
+                /* Verify that ONU SCI is what's expected. */
+                if (memcmp(link_rec->mka_ctrl.mka_info->onu_sci, sci, MKA_SCI_LEN) != 0)
+                {
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                    break;
+                }
+
+                /* Verify that ONU MI is what's expected. */
+                if (memcmp(link_rec->mka_ctrl.mka_info->link_memeber_id, mi, MKA_MI_LEN) != 0)
+                {
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                    break;
+                }
+
+                if (link_rec->mka_ctrl.mka_info->link_msg_num < link_mn)
+                {
+                    /* correct MN! */
+                    link_rec->mka_ctrl.mka_info->link_msg_num = link_mn;
+                }
+            }
+            else
+            {
+                /* Live Peer List. skip */
+                if (!bcmolt_buf_skip(msg->buf, body_len))
+                {
+                    rc = BCM_ERR_PARSE;
+                    break;
+                }
+            }
+        }
+        else if (param_set_type == MKPDU_PARAM_SET_MACSEC_SAK_USE)
+        {
+            uint32_t llpn;
+
+            /* Skip to the LLPN. */
+            if (!bcmolt_buf_skip(msg->buf, MKA_MI_LEN + MKA_KN_LEN))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+
+            /* read Latest Lowest Acceptable PN */
+            if (!bcmolt_buf_read_u32_be(msg->buf, &llpn))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+
+            if (llpn > MKA_MAX_ACCEPTABLE_PN)
+            {
+                /* Set flag to indicate SAK refresh is needed. */
+                link_rec->mka_ctrl.mka_info->sak_refresh_needed = BCMOS_TRUE;
+            }
+
+            /* check only Latest key tx & rx bits (bit 4 & 5) */
+            if ((ks_priority & 0x30) != 0x30)
+            {
+                /* Wrong LK Flag */ /* TODO: why isn't this an error? */
+            }
+
+            /* The MI, KN, and LLPN were read. */
+            bytes_read = body_len - (MKA_MI_LEN + MKA_KN_LEN + sizeof(uint32_t));
+
+            /* Skip the rest */
+            if (!bcmolt_buf_skip(msg->buf, bytes_read))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+        }
+        else
+        {
+            /* Skip all other Parameter Set data. */
+            if (!bcmolt_buf_skip(msg->buf, body_len))
+            {
+                rc = BCM_ERR_PARSE;
+                break;
+            }
+        }
+    }
+
+    /* refresh retry count. This is a Critical counter */
+    link_rec->mka_ctrl.mka_info->retry_cnt = 0;
+
+    return rc;
+}
+
+/* MKA frame handler for stable state */
+static bcmos_errno _mka_sak_done(mka_event *msg, dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks. */
+    BUG_ON(msg == NULL);
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    switch (msg->type)
+    {
+        case MKA_MSG_TIMEOUT:
+            /* send keepalive frame at every 2 seconds */
+            (void)_mka_send_keepalive_frame(link_rec);
+            break;
+
+        case MKA_MSG_DATA_IND:
+            rc = _mka_sak_done_data_ind(msg, link_rec);
+            break;
+
+        default:
+            rc = BCM_ERR_ONU_ERR_RESP;
+            break;
+    }
+
+    return rc;
+}
+
+/* Validate the MKA packet. */
+static bcmos_bool _mka_validate_pkt(bcmolt_u8_list_u16 mka_msg, eapol_msg_hdr *eapol)
+{
+    /* Parameter checks. */
+    BUG_ON(eapol == NULL);
+
+    /* This had better be an MKA packet. */
+    if (eapol->eapol_packet_type != EAPOL_TYPE_MKA)
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* The EAPOL length plus the size of the EapolMsgHdr must equal the length of the received packet. */
+    if (mka_msg.len != (eapol->eapol_length + EAPOL_MSG_HDR_SIZE))
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* The EAPOL length must be greater than the minimum supported length, Ethernet + EAPOL header length. */
+    if (eapol->eapol_length < MKA_PDU_MIN_LENGTH)
+    {
+        return BCMOS_FALSE;
+    }
+
+    /* The EAPOL length must be a multiple of four. */
+    if ((eapol->eapol_length % 4) != 0)
+    {
+        return BCMOS_FALSE;
+    }
+
+    return BCMOS_TRUE;
+}
+
+/* Wrapper into the MKA code that sends an SAK Confirm response to the ONU. */
+bcmos_errno mka_send_sak_confirm(dpoe_sec_link_rec *link_rec)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(link_rec->mka_ctrl.mka_info == NULL);
+
+    /* proceed to next state */
+    if (_mka_send_sak_confirm_frame(link_rec))
+    {
+        /* Everything looks good so far. Update AN and Key Number */
+        link_rec->mka_ctrl.mka_info->key_number += 1;
+        link_rec->mka_ctrl.mka_info->association_number += 1;
+        if (link_rec->mka_ctrl.mka_info->association_number == 4)
+        {
+            /* The association number is concatenated with the OLT SCI to identify a secure association between OLT and
+               ONU. The AN value starts at zero and cycles through values 0 - 3 as a new SAK is distributed to the ONU.
+               */
+            link_rec->mka_ctrl.mka_info->association_number = 0; /* AN = 0 ~ 3 */
+        }
+
+        link_rec->mka_ctrl.mka_info->state = MKA_STATE_MKA_DONE;
+        link_rec->mka_ctrl.mka_info->refresh_cnt += 1;
+        link_rec->mka_ctrl.mka_info->retry_cnt = 0;
+
+        /* start keepalive timer (2 second) */
+        bcmos_timer_start(&link_rec->mka_ctrl.mka_fsm_info->tx_timer, MKA_HELLO_TIME * 1000);
+    }
+    else
+    {
+        rc = BCM_ERR_COMM_FAIL;
+    }
+
+    return rc;
+}
+
+/* Wrapper into the MKA code that handles MKA PDUs from the ONU. */
+bcmos_errno mka_process_packet(dpoe_sec_link_rec *link_rec, bcmolt_u8_list_u16 rx_frame, mka_op_type op_type)
+{
+    bcmos_errno rc = BCM_ERR_PARM;
+    mka_event mka_msg;
+    bcmolt_buf buf;
+    eapol_msg_hdr eapol;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON(rx_frame.val == NULL);
+    BUG_ON((op_type <= MKA_OP__INVALID) || (op_type > MKA_OP_KEEP_ALIVE));
+
+    bcmolt_buf_init(&buf, rx_frame.len, rx_frame.val, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!dpoe_sec_eapol_unpack(&buf, &eapol))
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "failed to unpack EAPOL header!\n");
+        return BCM_ERR_PARSE;
+    }
+
+    /* Validate the MKA packet length before processing. */
+    if (!_mka_validate_pkt(rx_frame, &eapol))
+    {
+        DPOE_SEC_LINK_LOG(ERROR, link_rec, "Invalid MKA packet!\n");
+        return BCM_ERR_PARSE;
+    }
+
+    /* Pass the packet to the handler. */
+    mka_msg.type = MKA_MSG_DATA_IND;
+    mka_msg.msg = &eapol;
+    mka_msg.buf = &buf;
+
+    /* Process the MKA packet. */
+    switch (op_type)
+    {
+        case MKA_OP_START_RSP:
+            rc = _mka_waiting_initial_response(&mka_msg, link_rec);
+            break;
+        case MKA_OP_SAK_RSP:
+            rc = _mka_initial_sak_sent(&mka_msg, link_rec);
+            break;
+        case MKA_OP_KEEP_ALIVE:
+            rc = _mka_sak_done(&mka_msg, link_rec);
+            break;
+        default:
+            break;
+    }
+
+    return rc;
+}
+
+/* Wrapper into the MKA code that handles MKA timeouts. */
+bcmos_errno mka_process_timeout(dpoe_sec_link_rec *link_rec, mka_op_type op_type)
+{
+    bcmos_errno rc = BCM_ERR_TIMEOUT;
+    mka_event mka_msg;
+
+    /* Parameter checks. */
+    BUG_ON(link_rec == NULL);
+    BUG_ON((op_type < MKA_OP_START_TIMEOUT) || (op_type >= MKA_OP__COUNT));
+
+    /* Pass the packet to the handler. */
+    mka_msg.type = MKA_MSG_TIMEOUT;
+
+    /* Process the MKA packet. */
+    switch (op_type)
+    {
+        case MKA_OP_START_TIMEOUT:
+            rc = _mka_waiting_initial_response(&mka_msg, link_rec);
+            break;
+        case MKA_OP_SAK_TIMEOUT:
+            rc  = _mka_initial_sak_sent(&mka_msg, link_rec);
+            break;
+        case MKA_OP_SEND_KEEP_ALIVE:
+            rc = _mka_sak_done(&mka_msg, link_rec);
+            break;
+        default:
+            break;
+    }
+
+    return rc;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.h b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.h
new file mode 100644
index 0000000..b195864
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/dpoe_sec/mka.h
@@ -0,0 +1,130 @@
+/*
+<: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.
+
+:>
+ */
+
+#if !defined(MKA_H)
+#define MKA_H
+
+#include "bcmos_system.h"
+#include "bcmolt_model_types.h"
+
+#define MKA_LIFE_TIME               6000 /* Ms, 6 seconds */
+
+#define MKA_SCI_LEN                 8 /* common SCI size */
+#define MKA_MI_LEN                  12 /* Member Identifier */
+
+/* KEK */
+#define MKA_KEK_LEN                 128 /* bit length. 16 bytes */
+
+/* SAK */
+#define MKA_SAK_LEN                 16
+
+/* ICK */
+#define MKA_ICK_LEN                 128 /* bit length. 16 bytes */
+
+/* CKN */
+#define MKA_CKN_LEN                 128 /* bit length. 16 bytes */
+
+/* CAK */
+#define MKA_CAK_LEN                 128 /* bit length. 16 bytes */
+
+/* for MACSec Key Agreement */
+typedef enum
+{
+    MKA_STATE_INITIAL                   = 0,
+    MKA_STATE_WAITING_INITIAL_PEER_RESP = 1,
+    MKA_STATE_SAK_SENT                  = 2,
+    MKA_STATE_MKA_DONE                  = 3,
+
+    MKA_STATE__COUNT
+} mka_state;
+
+typedef enum
+{
+    MKA_PEER_STATE_NONE = 0,
+    MKA_PEER_STATE_LIVE = 1,
+    MKA_PEER_STATE_POTENTIAL = 2,
+} mka_peer_state;
+
+typedef struct
+{
+    uint8_t  onu_sci[MKA_SCI_LEN]; /**< ONU's SCI */
+    uint8_t  olt_member_id[MKA_MI_LEN]; /**< OLT MI */
+    uint32_t curr_msg_num; /**< Current OLT MN */
+    uint8_t  link_memeber_id[MKA_MI_LEN]; /**< Link MI */
+    uint32_t link_msg_num; /**< Current Link MN */
+    uint8_t  cak[MKA_CAK_LEN/8]; /**< Derived CAK */
+    uint8_t  ckn[MKA_CKN_LEN/8]; /**< Derived CKN */
+    uint8_t  sak[MKA_SAK_LEN]; /**< Derived SAK (a.k.a TEK) */
+    uint8_t  new_sak[MKA_SAK_LEN]; /**< New derived SAK for key refresh */
+    uint8_t  kek[MKA_KEK_LEN/8]; /**< Derived KEK */
+    uint8_t  ick[MKA_ICK_LEN/8]; /**< Derived ICK */
+    uint32_t key_number; /**< Current KN */
+    uint8_t  association_number; /**< AN */
+    bcmos_mac_address lesser_mac; /**< Lowest MAC address of MKA peers */
+    bcmos_mac_address greater_mac; /**< Greatest MAC address of MKA peers */
+    mka_state state; /**< State of MKA proper SM */
+    mka_peer_state peer_state; /**< State of MKA peer */
+    uint8_t  retry_cnt; /**< MKA message retry count */
+    uint8_t  refresh_cnt; /**< Important for building SAK Use Param set */
+    bcmos_bool sak_refresh_needed; /**< SAK refresh needed flag */
+} mka_link_info;
+
+/* The type of MKA operation to process. */
+typedef enum mka_op_type
+{
+    MKA_OP__INVALID = -1,
+
+    /* Packet operations. */
+    MKA_OP_START_RSP,
+    MKA_OP_SAK_RSP,
+    MKA_OP_KEEP_ALIVE,
+
+    /* Timeout operations. */
+    MKA_OP_START_TIMEOUT,
+    MKA_OP_SAK_TIMEOUT,
+    MKA_OP_SEND_KEEP_ALIVE,
+
+    MKA_OP__COUNT
+} mka_op_type;
+
+struct dpoe_sec_link_rec;
+
+bcmos_errno mka_start(struct dpoe_sec_link_rec *link);
+
+void mka_generate_sak(struct dpoe_sec_link_rec *link, bcmos_bool initial);
+
+bcmos_errno mka_send_sak(struct dpoe_sec_link_rec *link, uint8_t *sak);
+
+bcmos_errno mka_send_sak_confirm(struct dpoe_sec_link_rec *link);
+
+bcmos_errno mka_process_packet(struct dpoe_sec_link_rec *link, bcmolt_u8_list_u16 rx_frame, mka_op_type op_type);
+
+bcmos_errno mka_process_timeout(struct dpoe_sec_link_rec *link, mka_op_type op_type);
+
+#endif /* MKA_H */
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/Makefile b/bcm68620_release/release/host_reference/user_appl/eon/Makefile
new file mode 100644
index 0000000..ac4c3a5
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/Makefile
@@ -0,0 +1,33 @@
+# EPON OAM Negotiation user application
+
+ifeq ("$(ENABLE_CLI)", "y")
+
+	MOD_NAME = bcm_user_appl_eon
+	MOD_TYPE = lib
+	MOD_DEPS = host_api bcm_user_appl_epon_oam common_epon_oam
+
+	ifeq ("$(OS_KERNEL)", "linux")
+		MOD_DEPS += dev_log_linux
+	endif
+
+	srcs = bcmolt_eon.c oam_sets/oam_common.c
+
+	ENABLE_DPOE_OAM ?= y
+	ifeq ("$(ENABLE_DPOE_OAM)", "y")
+		EXTRA_DEFINES += -DEON_OAM_SET_DPOE_SUPPORTED
+		srcs += oam_sets/dpoe/dpoe.c
+	endif # ENABLE_DPOE_OAM
+
+	ENABLE_BRCM_OAM ?= y
+	ifeq ("$(ENABLE_BRCM_OAM)", "y")
+		EXTRA_DEFINES += -DEON_OAM_SET_BRCM_SUPPORTED
+		srcs += oam_sets/brcm/brcm.c
+	endif # ENABLE_BRCM_OAM
+
+	ENABLE_CTC_OAM ?= y
+	ifeq ("$(ENABLE_CTC_OAM)", "y")
+		EXTRA_DEFINES += -DEON_OAM_SET_CTC_SUPPORTED
+		srcs += oam_sets/ctc/ctc.c
+	endif # ENABLE_CTC_OAM
+
+endif
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.c b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.c
new file mode 100644
index 0000000..e193148
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.c
@@ -0,0 +1,783 @@
+/*
+<: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 is the master event handler for the EPON OAM negotiation task -
+    It accepts events from the embedded software and the host interface, supported events are
+    1.  Start OAM negotiaton (from host interface)
+    2.  Stop OAM negotiation (from host interface)
+    3.  RX Frame (from ONU via embedded firmware)
+    4.  Timeout
+
+    It dispatches these events to the OAM specific state machine. Currently we support
+    OAM negotiation for BRCM, DPoE, and CTC OAM standards.
+*/
+
+#include "bcmolt_eon_private.h"
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcmolt_msg_pack.h>
+#include "oam_sets/oam_common.h"
+
+#define EON_TASK_MSG_Q_SIZE 64
+
+typedef enum
+{
+    EON_EVENT_TYPE_FRAME_RX,
+    EON_EVENT_TYPE_OAM_NEG_START,
+    EON_EVENT_TYPE_OAM_NEG_STOP,
+    EON_EVENT_TYPE_OAM_TIMER_EXPIRE,
+
+    EON_EVENT_TYPE_COUNT
+} eon_event_type;
+
+typedef union
+{
+    eon_frame_data frame_rx;
+} eon_event_data;
+
+typedef struct
+{
+    eon_event_type type;
+    eon_link_state *link;
+    eon_event_data data;
+} eon_event;
+
+typedef bcmos_errno (*eon_event_handler)(const eon_event* in_event);
+
+static const char* _evname[EON_EVENT_TYPE_COUNT] =
+{
+    [EON_EVENT_TYPE_FRAME_RX] = "eon_event_type_frame_rx",
+    [EON_EVENT_TYPE_OAM_NEG_START] = "eon_event_type_oam_neg_start",
+    [EON_EVENT_TYPE_OAM_NEG_STOP] = "eon_event_type_oam_neg_stop",
+    [EON_EVENT_TYPE_OAM_TIMER_EXPIRE] = "eon_event_type_oam_timer_expire"
+};
+
+static bcmcli_enum_val eon_oam_set_id_table[EON_OAM_SET_ID_COUNT+1] =
+{
+    { .name = "dpoe", .val = EON_OAM_SET_ID_DPOE },
+    { .name = "brcm", .val = EON_OAM_SET_ID_BRCM },
+    { .name = "ctc", .val = EON_OAM_SET_ID_CTC },
+    BCMCLI_ENUM_LAST
+};
+
+eon_global_state eon_state[BCMTR_MAX_OLTS] = {};
+
+#define HEARTBEAT_SEND_PERIOD 1
+#define HEARTBEAT_SEND_OFFSET 15
+#define HEARTBEAT_RECEIVE_TIMEOUT 5
+#define OAM_TIMEOUT_US (HEARTBEAT_RECEIVE_TIMEOUT * 1000 * 1000)
+
+static bcmos_bool  epon_oam_neg_running = BCMOS_FALSE;
+
+static inline int _link_key_cmp(const eon_link_state* a, const eon_link_state* b)
+{
+    return memcmp(&a->link_key.link, &b->link_key.link, sizeof(a->link_key.link));
+}
+
+RB_GENERATE_INLINE(link_state_map, eon_link_state, rb_entry, _link_key_cmp);
+
+
+static bcmos_errno _eon_init_task(bcmolt_devid device)
+{
+    bcmos_task_parm task_params =
+    {
+        .name         = eon_state[device].task_name,
+        .priority     = TASK_PRIORITY_USER_APPL_EON,
+        .core         = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+    snprintf(eon_state[device].task_name, sizeof(eon_state[device].task_name), "user_appl_eon%u", device);
+    bcmos_errno rc = bcmos_task_create(&eon_state[device].task, &task_params);
+
+    return rc;
+}
+
+
+static bcmos_errno _eon_init_module(bcmolt_devid device)
+{
+    bcmos_module_parm module_params =
+    {
+        .qparm = { .name = eon_state[device].msg_queue_name, .size = EON_TASK_MSG_Q_SIZE },
+        .init = NULL
+    };
+    snprintf(eon_state[device].msg_queue_name, sizeof(eon_state[device].msg_queue_name), "eon_msg_q%u", device);
+    return bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_EON + device, &eon_state[device].task, &module_params);
+}
+
+static bcmos_errno _eon_send_os_msg(bcmolt_devid device, bcmos_msg_id type, eon_task_msg_data *data)
+{
+    bcmos_errno rc;
+    eon_task_msg *msg = bcmos_calloc(sizeof(*msg));
+    if (msg == NULL)
+    {
+        EON_LOG(ERROR, device, "OS Message calloc failed\n");
+        return BCM_ERR_NOMEM;
+    }
+
+    msg->os_msg.instance = device;
+    msg->os_msg.type = type;
+    msg->data = *data;
+    rc = bcmos_msg_dispatch(&msg->os_msg, BCMOS_MSG_SEND_AUTO_FREE);
+    if (BCM_ERR_OK != rc)
+    {
+        EON_LOG(ERROR, device, "OS Message send failed (%s)\n", bcmos_strerror(rc));
+    }
+
+    return rc;
+}
+
+static void _eon_destroy_link_state(eon_link_state* link_state)
+{
+    EON_LINK_LOG(DEBUG, &link_state->link_key, "Destroying link\n");
+    bcmos_timer_destroy(&link_state->oam_timer);
+    RB_REMOVE(link_state_map, &eon_state[link_state->link_key.device_id].link_state_map, link_state);
+    if (NULL != link_state->tx.payload)
+    {
+        bcmos_free(link_state->tx.payload);
+    }
+    if (NULL != link_state->rx.payload)
+    {
+        bcmos_free(link_state->rx.payload);
+    }
+    if (NULL != link_state->org_spec_state)
+    {
+        bcmos_free(link_state->org_spec_state);
+    }
+    bcmos_free(link_state);
+}
+
+static eon_link_state* _eon_find_link_state(const eon_link_key* link_key)
+{
+    eon_link_state query_key = { .link_key.link = link_key->link };
+
+    return RB_FIND(link_state_map, &eon_state[link_key->device_id].link_state_map, &query_key);
+}
+
+static void _eon_default_callback_fn(void *context, const eon_link_key* link_key, bcmos_errno result)
+{
+    eon_link_state *link_state = _eon_find_link_state(link_key);
+
+    EON_LINK_LOG(
+        INFO, link_key, "OAM negotiation completed (%s), flags %04x\n", bcmos_strerror(result), link_state->oam_state);
+}
+
+static bcmos_errno _eon_inject_frame(eon_link_key* link_key, uint16_t payload_length, uint8_t *payload_addr)
+{
+    bcmos_errno rc;
+    bcmolt_epon_link_inject_frame inject_frame;
+    bcmolt_epon_link_key epon_link_key = link_key->link;
+    bcmolt_ethernet_frame_unmasked frame =
+        {
+            .frame_octets =
+            {
+                .len = payload_length,
+                .val = payload_addr
+            }
+        };
+
+    BCMOLT_PROXY_INIT(&inject_frame, epon_link, inject_frame, epon_link_key);
+    BCMOLT_PROXY_PROP_SET(&inject_frame, epon_link, inject_frame, frame, frame);
+    rc = bcmolt_proxy_send(link_key->device_id, &inject_frame.hdr);
+    if (BCM_ERR_OK != rc)
+    {
+        EON_LINK_LOG(ERROR, link_key, "inject operation failed (%s)\n", bcmos_strerror(rc));
+    }
+
+    return rc;
+}
+
+static bcmos_errno _eon_clear_prov(eon_link_key *link_key)
+{
+    bcmos_errno err;
+    bcmolt_epon_link_key key = link_key->link;
+    bcmolt_epon_link_oam_keepalive_timer_stop stop_oper;
+    bcmolt_oam_heartbeat_config hb_cfg = {};
+    bcmolt_epon_link_cfg link_cfg;
+
+    BCMOLT_OPER_INIT(&stop_oper, epon_link, oam_keepalive_timer_stop, key);
+    err = bcmolt_oper_submit(link_key->device_id, &stop_oper.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        return err;
+    }
+
+    hb_cfg.transmit_frame.len = 0;
+    hb_cfg.transmit_frame.val = NULL;
+    hb_cfg.ignored_receive_frame_template.frame_octets.len = 0;
+    hb_cfg.ignored_receive_frame_template.frame_octets.val = NULL;
+    hb_cfg.ignored_receive_frame_template.mask_octets.len = 0;
+    hb_cfg.ignored_receive_frame_template.mask_octets.val = NULL;
+    hb_cfg.receive_timeout = 5;
+    hb_cfg.maximum_receive_size = 0;
+    BCMOLT_CFG_INIT(&link_cfg, epon_link, key);
+    BCMOLT_CFG_PROP_SET(&link_cfg, epon_link, oam_heartbeat_config, hb_cfg);
+    err = bcmolt_cfg_set(link_key->device_id, &link_cfg.hdr);
+
+    return err;
+}
+
+static bcmos_errno _eon_set_oam_heartbeat_config(const eon_link_state *link_state)
+{
+    bcmos_errno rc;
+    bcmolt_oam_heartbeat_config oam_heartbeat_config = {};
+
+    oam_heartbeat_config.receive_timeout = HEARTBEAT_RECEIVE_TIMEOUT;
+    oam_heartbeat_config.maximum_receive_size = link_state->max_pdu_size;
+
+    /* The BCM68620 TX heartbeat provisioning does not include the first 15 bytes - SA, DA, EtherType or Subtype */
+    oam_heartbeat_config.transmit_frame.val = &link_state->tx.payload[HEARTBEAT_SEND_OFFSET];
+    oam_heartbeat_config.transmit_frame.len = link_state->tx.length - HEARTBEAT_SEND_OFFSET;
+
+    oam_heartbeat_config.ignored_receive_frame_template.frame_octets.val = link_state->rx.payload;
+    oam_heartbeat_config.ignored_receive_frame_template.frame_octets.len = link_state->rx.length;
+
+    /* forward OAM frames with any differences to the host */
+    oam_heartbeat_config.ignored_receive_frame_template.mask_octets.val = bcmos_calloc(link_state->rx.length);
+    if (NULL != oam_heartbeat_config.ignored_receive_frame_template.mask_octets.val)
+    {
+        memset(oam_heartbeat_config.ignored_receive_frame_template.mask_octets.val, 0xFF, link_state->rx.length);
+        oam_heartbeat_config.ignored_receive_frame_template.mask_octets.len = link_state->rx.length;
+    }
+    else
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    bcmolt_epon_link_cfg link_cfg;
+    BCMOLT_CFG_INIT(&link_cfg, epon_link, link_state->link_key.link);
+    BCMOLT_CFG_PROP_SET(&link_cfg, epon_link, oam_heartbeat_config, oam_heartbeat_config);
+    rc = bcmolt_cfg_set(link_state->link_key.device_id, &link_cfg.hdr); /* call API */
+
+    bcmos_free(oam_heartbeat_config.ignored_receive_frame_template.mask_octets.val);
+
+    return rc;
+}
+
+static bcmos_errno _eon_start_oam_heartbeat(eon_link_state *link_state)
+{
+    bcmos_errno rc;
+
+    rc = _eon_set_oam_heartbeat_config(link_state);
+    if (BCM_ERR_OK == rc)
+    {
+        bcmolt_epon_link_key emb_link_key = link_state->link_key.link;
+        bcmolt_epon_link_oam_keepalive_timer_start start_heartbeat_op;
+        BCMOLT_OPER_INIT(&start_heartbeat_op, epon_link, oam_keepalive_timer_start, emb_link_key);
+        BCMOLT_OPER_PROP_SET(
+            &start_heartbeat_op,
+            epon_link,
+            oam_keepalive_timer_start,
+            send_period,
+            HEARTBEAT_SEND_PERIOD);
+        rc = bcmolt_oper_submit(link_state->link_key.device_id, &start_heartbeat_op.hdr);
+        if (BCM_ERR_OK != rc)
+        {
+            EON_LINK_LOG(ERROR, &link_state->link_key, "keepalive start operation submit failed (%s)\n",
+                         bcmos_strerror(rc));
+        }
+    }
+    else
+    {
+        EON_LINK_LOG(ERROR, &link_state->link_key, "failed to set heartbeat configuration(%s)\n", bcmos_strerror(rc));
+    }
+
+    return rc;
+}
+
+static bcmos_errno _eon_send_info_frame(eon_link_state *link_state)
+{
+    eon_frame_data frame_tx_req;
+    bcmos_errno rc;
+
+    rc = build_tx_info_frame(link_state, &frame_tx_req);
+    if (BCM_ERR_OK == rc)
+    {
+        rc = _eon_inject_frame(&link_state->link_key, frame_tx_req.length, frame_tx_req.payload);
+        if (BCM_ERR_OK == rc)
+        {
+            bcmos_timer_start(&link_state->oam_timer, OAM_TIMEOUT_US); /* start timer for rx frame */
+        }
+        else
+        {
+            EON_LINK_LOG(WARNING, &link_state->link_key, "failed to inject frame (%s)\n", bcmos_strerror(rc));
+        }
+        bcmos_free(frame_tx_req.payload);
+    }
+
+    return rc;
+}
+
+static bcmos_errno _eon_complete_negotiation(eon_link_state *link_state, uint8_t *rx_payload, uint16_t rx_length)
+{
+    bcmos_errno rc;
+    eon_frame_data frame_tx_req;
+
+    rc = build_tx_info_frame(link_state, &frame_tx_req);
+    if (BCM_ERR_OK != rc)
+    {
+        return rc;
+    }
+    link_state->tx.payload = frame_tx_req.payload;
+    link_state->tx.length = frame_tx_req.length;
+    EON_LINK_LOG(DEBUG, &link_state->link_key, "Setting TX payload\n");
+
+    link_state->rx.payload = bcmos_calloc(rx_length);
+    if (NULL == link_state->rx.payload)
+    {
+        return BCM_ERR_NOMEM;
+    }
+    memcpy(link_state->rx.payload, rx_payload, rx_length);
+    link_state->rx.length = rx_length;
+    EON_LINK_LOG(DEBUG, &link_state->link_key, "Setting RX mask\n");
+
+    if (link_state->is_heartbeat_autostart)
+    {
+        rc = _eon_start_oam_heartbeat(link_state);
+        EON_LINK_LOG(INFO, &link_state->link_key, "start OAM heartbeat: %s\n", bcmos_strerror(rc));
+    }
+    link_state->callback(link_state->context, &link_state->link_key, rc);
+    _eon_destroy_link_state(link_state);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno _eon_event_handler_frame_rx(const eon_event* event)
+{
+    bcmos_errno rc;
+
+    EON_LINK_LOG(DEBUG, &event->link->link_key, "received %u-byte frame beginning at %p\n",
+                 event->data.frame_rx.length, event->data.frame_rx.payload);
+
+    /* validates frame, updates flags */
+    rc = handle_rx_info_frame(event->link, event->data.frame_rx.length, event->data.frame_rx.payload);
+    switch (rc)
+    {
+        case BCM_ERR_OK: /* Completed Negotiation */
+            rc = _eon_complete_negotiation(event->link, event->data.frame_rx.payload, event->data.frame_rx.length);
+            break;
+        case BCM_ERR_IN_PROGRESS:
+            rc = _eon_send_info_frame(event->link);
+            break;
+        case BCM_ERR_PARSE:
+            EON_LINK_LOG(INFO, &event->link->link_key, "received non info frame; ignoring\n");
+            rc = BCM_ERR_OK; /* ...and keep running */
+            break;
+        default:
+            break;
+    }
+
+    return rc;
+}
+
+static bcmos_errno _eon_event_handler_start(const eon_event* event)
+{
+    bcmos_errno rc;
+
+    rc = _eon_clear_prov(&event->link->link_key);
+    if (BCM_ERR_OK == rc)
+    {
+        event->link->oam_state = BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING;
+        rc = _eon_send_info_frame(event->link);
+    }
+    return rc;
+}
+
+static bcmos_errno _eon_event_handler_stop(const eon_event* event)
+{
+    EON_LINK_LOG(INFO, &event->link->link_key, "OAM stop command received\n");
+    _eon_destroy_link_state(event->link);
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno _eon_event_handler_oam_timer_expire(const eon_event* event)
+{
+    EON_LINK_LOG(WARNING, &event->link->link_key, "OAM timer expired\n");
+    return BCM_ERR_TIMEOUT;
+}
+
+static const eon_event_handler event_sm[EON_EVENT_TYPE_COUNT] =
+{
+    [EON_EVENT_TYPE_FRAME_RX] = _eon_event_handler_frame_rx,
+    [EON_EVENT_TYPE_OAM_NEG_START] = _eon_event_handler_start,
+    [EON_EVENT_TYPE_OAM_NEG_STOP] = _eon_event_handler_stop,
+    [EON_EVENT_TYPE_OAM_TIMER_EXPIRE] = _eon_event_handler_oam_timer_expire
+};
+
+static bcmos_errno _eon_event_handler(const eon_event* event)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    if (event->type < EON_EVENT_TYPE_COUNT)
+    {
+        EON_LINK_LOG(DEBUG, &event->link->link_key, "event %s\n", _evname[event->type]);
+        rc = event_sm[event->type](event);
+    }
+    else
+    {
+        EON_LINK_LOG(ERROR, &event->link->link_key, "unknown event %d\n", event->type);
+    }
+
+    return rc;
+}
+
+static void _eon_event_dispatch(const eon_event* event_in)
+{
+    bcmos_errno rc;
+
+    rc = _eon_event_handler(event_in);
+    if (rc != BCM_ERR_OK)
+    {
+        event_in->link->callback(event_in->link->context, &event_in->link->link_key, rc);
+        _eon_destroy_link_state(event_in->link);
+    }
+}
+
+static bcmos_timer_rc _eon_oam_timer_expire(bcmos_timer* timer, long pUser)
+{
+    eon_event event = { .type = EON_EVENT_TYPE_OAM_TIMER_EXPIRE, .link = (eon_link_state *)pUser };
+
+    _eon_event_dispatch(&event);
+
+    return BCMOS_TIMER_OK;
+}
+
+static bcmos_errno _eon_create_link_state(
+    eon_link_state **state,
+    eon_link_key* link_key,
+    eon_oam_set_id oam_set,
+    bcmolt_eon_result_cb cb,
+    void *context,
+    bcmos_bool is_heartbeat_autostart)
+{
+    bcmos_errno rc;
+    eon_link_state *link_state;
+
+    link_state = _eon_find_link_state(link_key);
+    if (link_state != NULL)
+    {
+        EON_LINK_LOG(ERROR, link_key, "OAM negotiation is already in progress\n");
+        return BCM_ERR_ALREADY;
+    }
+
+    link_state = bcmos_calloc(sizeof(*link_state));
+    if (link_state == NULL)
+    {
+        EON_LINK_LOG(ERROR, link_key, "Failed to allocate state record\n");
+        return BCM_ERR_NOMEM;
+    }
+
+    bcmolt_epon_ni_key ni_key = { .epon_ni = link_key->link.epon_ni };
+    bcmolt_epon_ni_cfg ni_cfg;
+
+    BCMOLT_CFG_INIT(&ni_cfg, epon_ni, ni_key);
+    BCMOLT_CFG_PROP_GET(&ni_cfg, epon_ni, mac_address);
+    rc = bcmolt_cfg_get(link_key->device_id, &ni_cfg.hdr);
+    if (BCM_ERR_OK == rc)
+    {
+        link_state->epon_ni_mac_addr = ni_cfg.data.mac_address;
+    }
+    else
+    {
+        EON_LINK_LOG(ERROR, link_key, "Failed to retrieve EPON NI MAC address: %s\n", bcmos_strerror(rc));
+        bcmos_free(link_state);
+        return rc;
+    }
+
+    bcmos_timer_parm timer_spec =
+    {
+        .owner   = BCMOS_MODULE_ID_USER_APPL_EON + link_key->device_id,
+        .handler = _eon_oam_timer_expire,
+        .data    = (long)link_state
+    };
+    rc = bcmos_timer_create(&link_state->oam_timer, &timer_spec);
+    if (BCM_ERR_OK != rc)
+    {
+        EON_LINK_LOG(ERROR, link_key, "Failed to create timer: %s\n", bcmos_strerror(rc));
+        bcmos_free(link_state);
+        return rc;
+    }
+
+    link_state->link_key = *link_key;
+    link_state->callback = cb ? cb : _eon_default_callback_fn;
+    link_state->context = context;
+    link_state->is_heartbeat_autostart = is_heartbeat_autostart;
+    link_state->oam_set = oam_set;
+    link_state->max_pdu_size = 1536;
+
+    RB_INSERT(link_state_map, &eon_state[link_key->device_id].link_state_map, link_state);
+
+    *state = link_state;
+
+    return BCM_ERR_OK;
+}
+
+static void _eon_task_process_rx(bcmolt_devid device_id, const bcmolt_proxy_rx *rx)
+{
+    eon_event event  = { .type = EON_EVENT_TYPE_FRAME_RX };
+
+    const bcmolt_epon_link_key* epon_link_key = (const bcmolt_epon_link_key*)(rx + 1);
+    eon_link_key link_key = { .device_id = device_id, .link = *epon_link_key };
+    eon_link_state *link_state = _eon_find_link_state(&link_key);
+
+    if (link_state != NULL)
+    {
+        EON_LINK_LOG(DEBUG, &link_key, "found existing link\n");
+        const bcmolt_u8_list_u32 frame = ((const bcmolt_epon_link_frame_captured*)rx)->data.frame;
+        event.link = link_state;
+        event.data.frame_rx.payload = frame.val;
+        event.data.frame_rx.length  = frame.len;
+        _eon_event_dispatch(&event);
+    }
+    else
+    {
+        EON_LINK_LOG(DEBUG, &link_key, "rx on unknown link\n");
+    }
+}
+
+static void _eon_handle_proxy_rx(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    eon_task_msg *msg = (eon_task_msg*)os_msg;
+
+    _eon_task_process_rx(os_msg->instance, msg->data.proxy_rx.rx);
+
+    bcmolt_msg_free(&msg->data.proxy_rx.rx->hdr); /* free cloned indication */
+    bcmos_free(os_msg);
+}
+
+static void _eon_handle_start(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    eon_task_msg *msg = (eon_task_msg*)os_msg;
+    eon_link_key link_key = { .device_id = os_msg->instance, .link = msg->data.start.link_key };
+    eon_event event = { .type = EON_EVENT_TYPE_OAM_NEG_START };
+
+    if (BCM_ERR_OK == _eon_create_link_state(
+        &event.link,
+        &link_key,
+        msg->data.start.oam_set,
+        msg->data.start.cb,
+        msg->data.start.context,
+        msg->data.start.is_heartbeat_autostart))
+    {
+        _eon_event_dispatch(&event);
+    }
+
+    bcmos_free(os_msg);
+}
+
+static void _eon_handle_stop(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    eon_task_msg *msg = (eon_task_msg*)os_msg;
+    eon_link_key link_key = { .device_id = os_msg->instance, .link = msg->data.stop.link_key };
+    eon_event event = { .type = EON_EVENT_TYPE_OAM_NEG_STOP, .link = _eon_find_link_state(&link_key) };
+
+    if (NULL != event.link)
+    {
+        _eon_event_dispatch(&event);
+    }
+
+    bcmos_free(os_msg);
+}
+
+static bcmos_errno _eon_cmd_start(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno rc;
+    eon_task_msg_data msg_data;
+
+    msg_data.start.link_key.epon_ni = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    msg_data.start.link_key.mac_address = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    msg_data.start.oam_set = (eon_oam_set_id)bcmcli_find_named_parm(session, "oam_set")->value.enum_val;
+    msg_data.start.cb = NULL;
+    msg_data.start.context = NULL;
+    msg_data.start.is_heartbeat_autostart = BCMOS_TRUE;
+
+    rc = _eon_send_os_msg(current_device, BCMOS_MSG_ID_EON_START, &msg_data);
+
+    return rc;
+}
+
+static bcmos_errno _eon_cmd_stop(bcmcli_session *session,
+                                 const bcmcli_cmd_parm parm[],
+                                 uint16_t n_parms)
+{
+    bcmos_errno rc;
+    eon_task_msg_data msg_data;
+
+    msg_data.stop.link_key.epon_ni = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    msg_data.stop.link_key.mac_address = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    rc = _eon_send_os_msg(current_device, BCMOS_MSG_ID_EON_STOP, &msg_data);
+
+    return rc;
+}
+
+
+static bcmos_errno _eon_cmd_show(
+    bcmcli_session        *session,
+    const bcmcli_cmd_parm  parm[],
+    uint16_t               n_parms)
+{
+    eon_link_state*   link_state;
+    bcmos_bool          no_links = BCMOS_TRUE;
+
+    RB_FOREACH(link_state, link_state_map, &eon_state[current_device].link_state_map)
+    {
+        no_links = BCMOS_FALSE;
+        bcmcli_session_print(session,
+                             "-->  "LINK_KEY_FMT_STR"  OAM set %u   state %04x\n",
+                             LINK_KEY_DATA(&link_state->link_key),
+                             link_state->oam_set,
+                             link_state->oam_state);
+    }
+    if (no_links)
+    {
+        bcmcli_session_print(session, "no links\n");
+    }
+
+    return BCM_ERR_OK;
+}
+
+
+void bcmolt_user_appl_eon_cli_init(bcmcli_entry *top_dir)
+{
+    static const char *dir_name = "eon";
+
+    if (bcmcli_dir_find(top_dir, dir_name))
+    {
+        return;
+    }
+
+    bcmcli_entry *dir = bcmcli_dir_add(top_dir,
+                                       dir_name,
+                                       "EPON OAM negotiation commands",
+                                       BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(dir == NULL);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir,
+                           "show",
+                           "Show current EON configuration and state",
+                           _eon_cmd_show);
+
+    BCMCLI_MAKE_CMD(dir,
+                    "start_oam_negotiation",
+                    "Start OAM negotiation for an EPON link",
+                    _eon_cmd_start,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, BCMCLI_PARM_FLAG_NONE),
+                    BCMCLI_MAKE_PARM("mac_address", "MAC address", BCMCLI_PARM_MAC, BCMCLI_PARM_FLAG_NONE),
+                    BCMCLI_MAKE_PARM_ENUM("oam_set", "OAM set", eon_oam_set_id_table, BCMCLI_PARM_FLAG_NONE));
+
+
+    BCMCLI_MAKE_CMD(dir,
+                    "stop_oam_negotiation",
+                    "Stop in progress OAM negotiation for an EPON link",
+                    _eon_cmd_stop,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+                    BCMCLI_MAKE_PARM("mac_address", "MAC address", BCMCLI_PARM_MAC, BCMCLI_PARM_FLAG_NONE) );
+}
+
+
+void bcmolt_user_appl_eon_init(void)
+{
+    bcmos_errno rc;
+
+    if (epon_oam_neg_running)
+    {
+        return;
+    }
+
+    for (bcmolt_devid device = 0; device < BCMTR_MAX_OLTS; device++)
+    {
+        rc = _eon_init_task(device);
+        BUG_ON(rc != BCM_ERR_OK);
+
+        rc = _eon_init_module(device);
+        BUG_ON(rc != BCM_ERR_OK);
+
+        char log_name[MAX_DEV_LOG_ID_NAME];
+        snprintf(log_name, sizeof(log_name), "user_appl_eon%u", device);
+        eon_state[device].log_id = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+
+        bcmos_msg_register(
+            BCMOS_MSG_ID_EON_START,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_EON + device,
+            _eon_handle_start);
+        bcmos_msg_register(
+            BCMOS_MSG_ID_EON_STOP,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_EON + device,
+            _eon_handle_stop);
+        bcmos_msg_register(
+            BCMOS_MSG_ID_EON_PROXY_RX,
+            device,
+            BCMOS_MODULE_ID_USER_APPL_EON + device,
+            _eon_handle_proxy_rx);
+
+        // initialize the state map
+        RB_INIT(&eon_state[device].link_state_map);
+    }
+
+    epon_oam_neg_running = BCMOS_TRUE;
+}
+
+// public indication handler interface -- called in transport layer context
+bcmos_errno bcmolt_user_appl_eon_process_rx(bcmolt_devid device_id, bcmolt_proxy_rx *rx)
+{
+     bcmos_errno rc;
+     eon_task_msg_data msg_data = {};
+
+     /* Not running --> silently ignore  */
+     if (!epon_oam_neg_running)
+     {
+         return BCM_ERR_OK;
+     }
+
+     /* Not our message --> silenty ignore */
+     if ( (BCMOLT_OBJ_ID_EPON_LINK != rx->hdr.obj_type) ||
+          (BCMOLT_EPON_LINK_PROXY_RX_ID_FRAME_CAPTURED != rx->hdr.subgroup) )
+     {
+         return BCM_ERR_OK;
+     }
+
+     /* This is something this application does care about - so clone the message into
+        newly-allocated memory so we can handle it after the original message has been freed */
+     rc = bcmolt_msg_clone((bcmolt_msg**)&msg_data.proxy_rx.rx, &rx->hdr);
+     if (rc != BCM_ERR_OK)
+     {
+         EON_LOG(ERROR, device_id, "Message clone failed: %s\n", bcmos_strerror(rc));
+         return rc;
+     }
+
+     rc = _eon_send_os_msg(device_id, BCMOS_MSG_ID_EON_PROXY_RX, &msg_data);
+     if (rc != BCM_ERR_OK)
+     {
+         bcmolt_msg_free(&msg_data.proxy_rx.rx->hdr);
+     }
+     return rc;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.h b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.h
new file mode 100644
index 0000000..c93fcd1
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon.h
@@ -0,0 +1,98 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_EON_H_
+#define _BCMOLT_EON_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcmcli.h>
+
+/* uniquely identifies specific EPON links under management by this application */
+typedef struct
+{
+     bcmolt_devid          device_id;
+     bcmolt_epon_link_key  link;
+} eon_link_key;
+
+typedef enum
+{
+     EON_OAM_SET_ID_DPOE,
+     EON_OAM_SET_ID_BRCM,
+     EON_OAM_SET_ID_CTC,
+
+     EON_OAM_SET_ID_COUNT
+} eon_oam_set_id;
+
+/* signature of optional callback function used to report outcomes of OAM negotiations */
+typedef void (*bcmolt_eon_result_cb)(void *context, const eon_link_key *link_key, bcmos_errno result);
+
+typedef struct
+{
+    bcmolt_epon_link_key link_key;
+    eon_oam_set_id oam_set;
+    bcmolt_eon_result_cb cb;
+    void *context;
+    bcmos_bool is_heartbeat_autostart;
+} eon_task_start_data;
+
+typedef struct
+{
+    bcmolt_epon_link_key link_key;
+} eon_task_stop_data;
+
+typedef struct
+{
+    bcmolt_proxy_rx *rx;
+} eon_task_proxy_rx_data;
+
+typedef union
+{
+    eon_task_start_data start;
+    eon_task_stop_data stop;
+    eon_task_proxy_rx_data proxy_rx;
+} eon_task_msg_data;
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    eon_task_msg_data data;
+} eon_task_msg;
+
+// initialize the EPON OAM negotiation application (should be called as part of user application startup)
+extern void bcmolt_user_appl_eon_init(void);
+
+// process a proxy rx
+bcmos_errno bcmolt_user_appl_eon_process_rx(bcmolt_devid device_id, bcmolt_proxy_rx *proxy_rx);
+
+// initialize EON CLI
+void bcmolt_user_appl_eon_cli_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon_private.h b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon_private.h
new file mode 100644
index 0000000..f40b453
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/bcmolt_eon_private.h
@@ -0,0 +1,92 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_EON_PRIVATE_H_
+#define _BCMOLT_EON_PRIVATE_H_
+
+/* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+   x This file is for internal use within the EPON OAM negotiation application. x
+   x                                                                            x
+   x Outside code should not refer to anything defined or declared herein!      x
+   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx */
+
+#include "bcmolt_eon.h" /* the private interface extends the public interface */
+#include "bcmolt_epon_oam_types.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_utils.h"
+
+typedef struct
+{
+    uint8_t* payload; /* payload bytes in [DA,FCS) */
+    uint16_t length; /* frame length from DA to just before FCS */
+} eon_frame_data;
+
+/* map containing state of all EPON links with OAM negotiation underway or completed */
+typedef struct eon_link_state eon_link_state;
+typedef struct link_state_map link_state_map;
+RB_HEAD(link_state_map, eon_link_state);
+
+struct eon_link_state
+{
+    eon_link_key                 link_key;                  /* globally unique opaque entity id */
+    bcmos_timer                  oam_timer;                 /* timer used when awaiting oam responses */
+    eon_oam_set_id               oam_set;                   /* OAM set applicable to the link */
+    bcmolt_epon_oam_oam_flags    oam_state;                 /* OAM link negotiation state */
+    uint16_t                     revision;                  /* Local revision */
+    uint16_t                     max_pdu_size;              /* max PDU size */
+    bcmolt_eon_result_cb         callback;                  /* callback when negotiation is complete */
+    void                         *context;                  /* context for the above callback function */
+    bcmolt_epon_oam_local_remote_info remote_info;          /* previous TLV received from the remote */
+    bcmos_mac_address            epon_ni_mac_addr;          /* MAC address to use for SA of egress OAM frames */
+    eon_frame_data               rx;                        /* Final received OAM */
+    eon_frame_data               tx;                        /* Final sent OAM */
+    bcmos_bool                   is_heartbeat_autostart;    /* start heartbeat upon successful negotiation completion */
+    void                        *org_spec_state;            /* State specific to the selected OAM set */
+    RB_ENTRY(eon_link_state)     rb_entry;
+};
+
+typedef struct
+{
+    bcmos_task task;
+    char task_name[MAX_TASK_NAME_SIZE];
+    char msg_queue_name[MAX_MSG_QUEUE_NAME_SIZE];
+    dev_log_id log_id;
+    link_state_map link_state_map;
+} eon_global_state;
+
+#define LINK_KEY_FMT_STR "PON %u, "BCMOS_MACADDR_FMT_STR
+#define LINK_KEY_DATA(lkey) (lkey)->link.epon_ni, BCMOS_MACADDR_PARAMS(&(lkey)->link.mac_address)
+
+#define EON_LOG(level, device, format, args...) BCM_LOG(level, eon_state[device].log_id, format, ##args)
+#define EON_LINK_LOG(level, link_key, fmt, args...) \
+    BCM_LOG(level, eon_state[(link_key)->device_id].log_id, LINK_KEY_FMT_STR": "fmt, LINK_KEY_DATA(link_key), ##args)
+
+extern eon_global_state eon_state[BCMTR_MAX_OLTS];
+
+#endif // _BCMOLT_EON_PRIVATE_H_
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.c b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.c
new file mode 100644
index 0000000..c6170f6
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.c
@@ -0,0 +1,108 @@
+/*
+<: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 specific to BRCM OAM. */
+
+#include "bcmolt_eon_private.h"
+#include "../oam_common.h"
+#include "brcm.h"
+
+/* Org specific TLV to send to the ONU */
+static const bcmolt_epon_oam_organization_specific_info brcm_tx_tlv =
+{
+    .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_TEK,
+    .u =
+    {
+        .tek =
+        {
+            .tlvs =
+            {
+                .type = BCMOLT_EPON_OAM_TEK_INFO_TLV_TYPE_EXTENSION_SUPPORT,
+                .u =
+                {
+                    .extension_support =
+                    {
+                        .version = 0,
+                        .report_mode = BCMOLT_EPON_OAM_TEK_REPORT_MODES_TEKNOVUS,
+                        .preferred_report_mode = BCMOLT_EPON_OAM_TEK_REPORT_MODES_TEKNOVUS
+                    }
+                }
+            }
+        }
+    }
+};
+
+void brcm_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam)
+{
+    if (LOCAL_STABLE_REMOTE_STABLE == link_state->oam_state)
+    {
+        oam->u.info.tlvs[oam->u.info.tlvs_count].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC;
+        oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value = brcm_tx_tlv;
+        oam->u.info.tlvs_count++;
+    }
+}
+
+void brcm_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc)
+{
+    if (link_state->oam_state == LOCAL_STABLE_REMOTE_STABLE)
+    {
+        if ((NULL == org_spec) ||
+            (BCMOLT_EPON_OAM_WELL_KNOWN_OUI_TEK != org_spec->oui) ||
+            (BCMOLT_EPON_OAM_TEK_INFO_TLV_TYPE_EXTENSION_SUPPORT != org_spec->u.tek.tlvs.type))
+        {
+            EON_LINK_LOG(INFO, &link_state->link_key, "Didn't get BRCM TLV\n");
+            *rc = BCM_ERR_ONU_ERR_RESP;
+        }
+        else
+        {
+            if ((0 != org_spec->u.tek.tlvs.u.extension_support.version) ||
+                (0 ==
+                 (BCMOLT_EPON_OAM_TEK_REPORT_MODES_TEKNOVUS & org_spec->u.tek.tlvs.u.extension_support.report_mode)))
+            {
+                EON_LINK_LOG(
+                    INFO, &link_state->link_key, "BRCM TLV contained bad info: version %u rpt modes %02x\n",
+                    org_spec->u.tek.tlvs.u.extension_support.version,
+                    org_spec->u.tek.tlvs.u.extension_support.report_mode);
+                *rc = BCM_ERR_NOT_SUPPORTED;
+            }
+        }
+    }
+    else
+    {
+        if (NULL != org_spec)
+        {
+            EON_LINK_LOG(INFO, &link_state->link_key, "Got unexpected organization specific TLV\n");
+            *rc = BCM_ERR_ONU_ERR_RESP;
+        }
+    }
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.h b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.h
new file mode 100644
index 0000000..34c8878
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/brcm/brcm.h
@@ -0,0 +1,41 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_EON_BRCM_H_
+#define _BCMOLT_EON_BRCM_H_
+
+void brcm_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam);
+
+void brcm_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc);
+
+#endif // _BCMOLT_EON_BRCM_H_
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.c b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.c
new file mode 100644
index 0000000..768c8b9
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.c
@@ -0,0 +1,212 @@
+/*
+<: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 specific to CTC OAM. */
+
+#include "bcmolt_eon_private.h"
+#include "../oam_common.h"
+#include "ctc.h"
+
+#define CTC_EXTENSION_SUPPORT 0x1
+#define CTC_PREFERRED_VERSION 0x30
+
+typedef enum
+{
+    CTC_NEG_ADVERTISING,
+    CTC_NEG_CONFIRMING,
+    CTC_NEG_DONE
+} ctc_neg_state;
+
+typedef struct
+{
+    ctc_neg_state state;
+    uint8_t selected_version;
+} ctc_state;
+
+static bcmolt_epon_oam_ctc_oui_version_pair ctc_advertised_versions[] =
+{
+    { .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC, .version = 0x30 },
+    { .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC, .version = 0x21 },
+    { .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC, .version = 0x20 },
+    { .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC, .version = 0x13 },
+    { .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC, .version = 0x01 }
+};
+
+/* Org specific TLV to send to the ONU */
+static const bcmolt_epon_oam_organization_specific_info ctc_tx_advertise_tlv =
+{
+    .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC,
+    .u =
+    {
+        .ctc =
+        {
+            .extension_support = CTC_EXTENSION_SUPPORT,
+            .version = CTC_PREFERRED_VERSION,
+            .supp_version_count = NUM_ELEM(ctc_advertised_versions),
+            .supp_version = ctc_advertised_versions
+        }
+    }
+};
+
+void ctc_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam)
+{
+    ctc_state *ctc = link_state->org_spec_state;
+
+    if (ctc != NULL)
+    {
+        switch (ctc->state)
+        {
+            case CTC_NEG_ADVERTISING:
+                oam->u.info.tlvs[oam->u.info.tlvs_count].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value = ctc_tx_advertise_tlv;
+                oam->u.info.tlvs_count++;
+                break;
+            case CTC_NEG_CONFIRMING:
+                oam->u.info.tlvs[oam->u.info.tlvs_count].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value.oui =
+                    BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value.u.ctc.extension_support =
+                    CTC_EXTENSION_SUPPORT;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value.u.ctc.version =
+                    ctc->selected_version;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value.u.ctc.supp_version_count = 0;
+                oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value.u.ctc.supp_version = NULL;
+                oam->u.info.tlvs_count++;
+                break;
+            case CTC_NEG_DONE:
+                break; /* don't add a TLV */
+            default:
+                EON_LINK_LOG(ERROR, &link_state->link_key, "Unknown CTC state %d\n", ctc->state);
+                break;
+        }
+    }
+}
+
+void ctc_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc)
+{
+    *rc = BCM_ERR_IN_PROGRESS;
+
+    if (link_state->org_spec_state != NULL)
+    {
+        ctc_state *ctc = link_state->org_spec_state;
+
+        switch (ctc->state)
+        {
+            case CTC_NEG_ADVERTISING:
+                if ((NULL == org_spec) || (BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC != org_spec->oui))
+                {
+                    EON_LINK_LOG(INFO, &link_state->link_key, "Didn't get CTC advertise TLV\n");
+                    *rc = BCM_ERR_ONU_ERR_RESP;
+                }
+                else
+                {
+                    /* find highest common version */
+                    ctc->selected_version = 0;
+                    for (uint8_t i = 0; i < org_spec->u.ctc.supp_version_count; i++)
+                    {
+                        for (uint8_t j = 0; j < NUM_ELEM(ctc_advertised_versions); j++)
+                        {
+                            if ((org_spec->u.ctc.supp_version[i].oui == ctc_advertised_versions[j].oui) &&
+                                (org_spec->u.ctc.supp_version[i].version == ctc_advertised_versions[j].version))
+                            {
+                                if (ctc_advertised_versions[j].version > ctc->selected_version)
+                                {
+                                    ctc->selected_version = ctc_advertised_versions[j].version;
+                                }
+                            }
+                        }
+                    }
+                    if (ctc->selected_version == 0)
+                    {
+                        *rc = BCM_ERR_NOT_SUPPORTED; /* no common version found */
+                    }
+                    else
+                    {
+                        ctc->state = CTC_NEG_CONFIRMING;
+                    }
+                }
+                break;
+            case CTC_NEG_CONFIRMING:
+                if ((NULL == org_spec) || (BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC != org_spec->oui))
+                {
+                    EON_LINK_LOG(INFO, &link_state->link_key, "Didn't get CTC confirm TLV\n");
+                    *rc = BCM_ERR_ONU_ERR_RESP;
+                }
+                else
+                {
+                    if (org_spec->u.ctc.supp_version_count == 0)
+                    {
+                        if (org_spec->u.ctc.version != ctc->selected_version)
+                        {
+                            *rc = BCM_ERR_NOT_SUPPORTED;
+                        }
+                        else
+                        {
+                            ctc->state = CTC_NEG_DONE;
+                        }
+                    }
+                }
+                break;
+            case CTC_NEG_DONE:
+                if ((NULL != org_spec) && (org_spec->oui == BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC))
+                {
+                    EON_LINK_LOG(
+                        INFO, &link_state->link_key, "Got unexpected CTC TLV after negotiation\n");
+                    *rc = BCM_ERR_ONU_ERR_RESP;
+                }
+                else
+                {
+                    *rc = BCM_ERR_OK;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    else
+    {
+        if ((NULL != org_spec) && (org_spec->oui == BCMOLT_EPON_OAM_WELL_KNOWN_OUI_CTC))
+        {
+            EON_LINK_LOG(
+                INFO, &link_state->link_key, "Got unexpected CTC TLV before negotiation\n");
+            *rc = BCM_ERR_ONU_ERR_RESP;
+        }
+
+        if (LOCAL_STABLE_REMOTE_STABLE == link_state->oam_state)
+        {
+            ctc_state *ctc = bcmos_calloc(sizeof(ctc_state));
+            ctc->state = CTC_NEG_ADVERTISING;
+            link_state->org_spec_state = ctc;
+        }
+    }
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.h b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.h
new file mode 100644
index 0000000..a2107b4
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/ctc/ctc.h
@@ -0,0 +1,42 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_EON_CTC_H_
+#define _BCMOLT_EON_CTC_H_
+
+void ctc_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam);
+
+void ctc_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc);
+
+#endif // _BCMOLT_EON_CTC_H_
+
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.c b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.c
new file mode 100644
index 0000000..6b0acf1
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.c
@@ -0,0 +1,122 @@
+/*
+<: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 specific to DPoE OAM. */
+
+#include "bcmolt_eon_private.h"
+#include "../oam_common.h"
+#include "dpoe.h"
+
+/* Org specific TLV to send to the ONU */
+static const bcmolt_epon_oam_organization_specific_info dpoe_tx_tlv =
+{
+    .oui = BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE,
+    .u =
+    {
+        .dpoe =
+        {
+            .dpoe_info_tlv =
+            {
+                .type = BCMOLT_EPON_OAM_DPOE_INFO_TLV_TYPE_DPOE_OAM_SUPPORT,
+                .u =
+                {
+                    .dpoe_oam_support =
+                    {
+                        .dpoe_oam_version = 0x11
+                    }
+                }
+            }
+        }
+    }
+};
+
+void dpoe_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam)
+{
+    if ((LOCAL_EVAL_REMOTE_EVAL == link_state->oam_state) ||
+        (LOCAL_STABLE_REMOTE_EVAL == link_state->oam_state))
+    {
+        oam->u.info.tlvs[oam->u.info.tlvs_count].type = BCMOLT_EPON_OAM_INFO_TLV_TYPE_ORGANIZATION_SPECIFIC;
+        oam->u.info.tlvs[oam->u.info.tlvs_count].u.organization_specific.value = dpoe_tx_tlv;
+        oam->u.info.tlvs_count++;
+    }
+}
+
+void dpoe_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc)
+{
+    if (link_state->oam_state == LOCAL_STABLE_REMOTE_STABLE)
+    {
+        if (NULL != org_spec)
+        {
+            EON_LINK_LOG(INFO, &link_state->link_key, "Got unexpected organization specific TLV\n");
+            *rc = BCM_ERR_ONU_ERR_RESP;
+        }
+    }
+    else
+    {
+        if ((NULL == org_spec) ||
+            (BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE != org_spec->oui) ||
+            (BCMOLT_EPON_OAM_DPOE_INFO_TLV_TYPE_DPOE_OAM_SUPPORT != org_spec->u.dpoe.dpoe_info_tlv.type))
+        {
+            EON_LINK_LOG(INFO, &link_state->link_key, "Didn't get DPoE TLV\n");
+            *rc = BCM_ERR_ONU_ERR_RESP;
+        }
+        else
+        {
+            switch (org_spec->u.dpoe.dpoe_info_tlv.u.dpoe_oam_support.dpoe_oam_version)
+            {
+                case 0x22:
+                case 0x21:
+                case 0x20:
+                case 0x11: /* DPoE v1.0 I05 and higher */
+                    break;
+                case 0x01: /* backwards compatibility, same as 0x10 */
+                case 0x02: /* pre-DPoE OAM, no Certificate Authority support */
+                case 0x03: /* pre-DPoE OAM, with Certificate Authority support */
+                case 0x10: /* DPoE v1.0 I04 and lower */
+                    EON_LINK_LOG(
+                        WARNING, &link_state->link_key,
+                        "DPoE TLV contained old version: %u (some features may not work)\n",
+                        org_spec->u.dpoe.dpoe_info_tlv.u.dpoe_oam_support.dpoe_oam_version);
+                    break;
+                case 0x00: /* workaround for EASW-17839 */
+                    break;
+                default:
+                    EON_LINK_LOG(
+                        ERROR, &link_state->link_key, "DPoE TLV contained bad version: %u\n",
+                        org_spec->u.dpoe.dpoe_info_tlv.u.dpoe_oam_support.dpoe_oam_version);
+                    *rc = BCM_ERR_NOT_SUPPORTED;
+                    break;
+            }
+        }
+    }
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.h b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.h
new file mode 100644
index 0000000..2b6d6f2
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/dpoe/dpoe.h
@@ -0,0 +1,41 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_EON_DPOE_H_
+#define _BCMOLT_EON_DPOE_H_
+
+void dpoe_tx_add_tlv(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam);
+
+void dpoe_rx_tlv(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc);
+
+#endif // _BCMOLT_EON_DPOE_H_
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.c b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.c
new file mode 100644
index 0000000..cd92c92
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.c
@@ -0,0 +1,361 @@
+/*
+<: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;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.h b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.h
new file mode 100644
index 0000000..50f08b2
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/eon/oam_sets/oam_common.h
@@ -0,0 +1,56 @@
+/*
+<: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. */
+
+#ifndef _OAM_COMMON_H_
+#define _OAM_COMMON_H_
+
+#include "bcmolt_eon_private.h"
+
+#define LOCAL_EVAL_REMOTE_EVAL\
+    (BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_EVALUATING | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING)
+#define LOCAL_STABLE_REMOTE_EVAL\
+    (BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_EVALUATING)
+#define LOCAL_STABLE_REMOTE_STABLE\
+    (BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE)
+
+typedef void (*oam_info_add_tlv)(eon_link_state *link_state, bcmolt_epon_oam_oam_pdu_content *oam);
+
+typedef void (*oam_rx_org_spec_tlv)(
+    eon_link_state *link_state,
+    bcmolt_epon_oam_organization_specific_info *org_spec,
+    bcmos_errno *rc);
+
+bcmos_errno build_tx_info_frame(eon_link_state *link_state, eon_frame_data *frame_data);
+
+bcmos_errno handle_rx_info_frame(eon_link_state *link_state, uint16_t rx_length, uint8_t *rx_payload);
+
+#endif // _OAM_COMMON_H
+
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/Makefile b/bcm68620_release/release/host_reference/user_appl/epon_hde/Makefile
new file mode 100644
index 0000000..36f10f1
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/Makefile
@@ -0,0 +1,11 @@
+# EPON Host Driven Encryption

+

+ifeq ("$(ENABLE_CLI)", "y")

+

+	MOD_NAME = bcm_user_appl_epon_hde

+	MOD_TYPE = lib

+	MOD_DEPS = host_api

+

+	srcs = bcmolt_epon_hde.c encrypt_oam.c

+

+endif

diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.c b/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.c
new file mode 100644
index 0000000..60cb073
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.c
@@ -0,0 +1,693 @@
+/*
+ <: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.
+
+ :>
+ */
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcmcli_session.h>
+#include <bcmolt_msg_pack.h>
+#include <bcm_dev_log.h>
+#include <encrypt_oam.h>
+#include <oam_defs.h>
+#include "bcmolt_epon_hde.h"
+
+#define HDE_TASK_MSG_Q_SIZE 64
+#define ENC_KEY_LENGTH_KEY 16
+#define HDE_MAX_NUM_LINKS 512
+#define KEY_EXCHANGE_PERIOD 15
+
+typedef enum
+{
+    HDE_EVENT_ENABLE_ENCRYPTION,
+    HDE_EVENT_DISABLE_ENCRYPTION,
+    HDE_EVENT_FRAME_RX,
+    HDE_EVENT_OAM_TIMEOUT,
+    HDE_EVENT__NUM_OF
+} hde_event;
+
+typedef enum
+{
+    HDE_STATE_NOT_ENCRYPTED,
+    HDE_STATE_ENCRYPTED,
+    HDE_STATE_WAITING_FOR_OAM_ACK,
+    HDE_STATE__NUM_OF
+} hde_state;
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    bcmolt_msg *rx;
+    hde_key key;
+    hde_event event;
+} hde_task_msg;
+
+typedef struct
+{
+    hde_key hde_link;
+    hde_state state;
+    bcmos_timer oam_timeout_timer;
+    bcmolt_epon_link_rate link_rate;
+    bcmolt_epon_llid llid;
+    bcmos_mac_address corresponding_pon_port_mac;
+} hde_link_data;
+
+static struct
+{
+    bcmos_task task;
+    dev_log_id log_id[BCMTR_MAX_OLTS];
+    hde_link_data *links;
+    uint16_t num_links;
+} hde_data;
+
+static bcmos_bool is_running = BCMOS_FALSE;
+
+typedef void (*link_state_handler)(hde_link_data *hde_link, const bcmolt_msg *ind);
+
+static const char *get_state_string(hde_state state)
+{
+    switch (state)
+    {
+    case HDE_STATE_NOT_ENCRYPTED:
+        return "not encrypted";
+    case HDE_STATE_ENCRYPTED:
+        return "encrypted";
+    case HDE_STATE_WAITING_FOR_OAM_ACK:
+        return "waiting for oam ack";
+    default:
+        return "UNKNOWN STATE!!!";
+    }
+}
+
+static bcmos_errno set_new_key(const hde_key *hde_link, bcmolt_encryption_information_container *new_key, bcmolt_epon_key_choice key_choice, bcmolt_epon_encryption_mode mode)
+{
+    bcmolt_epon_link_cfg link_cfg;
+    bcmolt_epon_encryption_config encryption_config = { };
+
+    // Up/down format must match even when doing DS only
+    encryption_config.upstream_encryption_information.format = new_key->format;
+
+    encryption_config.downstream_encryption_information = *new_key;
+    encryption_config.downstream_key_choice = key_choice;
+    encryption_config.downstream_mode = mode;
+    bcmolt_epon_link_key link_key =
+    {
+        .epon_ni = hde_link->epon_ni,
+        .mac_address = hde_link->mac_addr
+    };
+    BCMOLT_CFG_INIT(&link_cfg, epon_link, link_key);
+    BCMOLT_CFG_PROP_SET(&link_cfg, epon_link, epon_encryption, encryption_config);
+    return bcmolt_cfg_set(hde_link->device_id, &link_cfg.hdr);
+}
+
+static bcmos_errno set_pon_encryption_if_needed(const hde_key *hde_link)
+{
+    // First check the current state of the PON's encryption mode.
+    bcmos_errno rc;
+    bcmolt_epon_ni_cfg pon_cfg;
+    bcmolt_epon_ni_key pon_key = { .epon_ni = hde_link->epon_ni };
+    BCMOLT_CFG_INIT(&pon_cfg, epon_ni, pon_key);
+    BCMOLT_CFG_PROP_GET(&pon_cfg, epon_ni, encryption_cfg);
+    rc = bcmolt_cfg_get(hde_link->device_id, &pon_cfg.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->device_id], "Failure - Cannot query PON encryption mode!.\n");
+        return rc;
+    }
+
+    if (pon_cfg.data.encryption_cfg.downstream_encryption_mode != BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES)
+    {
+        // Encryption mode is not correctly set on the PON, so set it now.
+        pon_cfg.data.encryption_cfg.downstream_encryption_mode = BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES;
+        pon_cfg.data.encryption_cfg.upstream_encryption_mode = BCMOLT_EPON_ENCRYPTION_MODE_NO_ENCRYPTION;
+        rc = bcmolt_cfg_set(hde_link->device_id, &pon_cfg.hdr);
+        if (rc != BCM_ERR_OK)
+        {
+            BCM_LOG(ERROR, hde_data.log_id[hde_link->device_id], "Failure - Cannot set PON encryption mode!.\n");
+            return rc;
+        }
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmolt_epon_link_rate get_link_rate(const hde_key *hde_link)
+{
+    bcmos_errno rc;
+    bcmolt_epon_link_cfg link_cfg;
+    bcmolt_epon_link_key link_key =
+    {
+        .epon_ni = hde_link->epon_ni,
+        .mac_address = hde_link->mac_addr
+    };
+    BCMOLT_CFG_INIT(&link_cfg, epon_link, link_key);
+    BCMOLT_CFG_PROP_GET(&link_cfg, epon_link, link_rate);
+    rc = bcmolt_cfg_get(hde_link->device_id, &link_cfg.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(
+            ERROR,
+            hde_data.log_id[hde_link->device_id],
+            "Failure - cannot query link rate for link %02x:%02x:%02x:%02x:%02x:%02x!.\n",
+            hde_link->mac_addr.u8[0],
+            hde_link->mac_addr.u8[1],
+            hde_link->mac_addr.u8[2],
+            hde_link->mac_addr.u8[3],
+            hde_link->mac_addr.u8[4],
+            hde_link->mac_addr.u8[5]);
+    }
+    return link_cfg.data.link_rate;
+}
+
+static bcmos_mac_address get_pon_mac(const hde_key *hde_link)
+{
+    bcmos_errno rc;
+    bcmolt_epon_ni_cfg pon_cfg;
+    bcmolt_epon_ni_key pon_key = { .epon_ni = hde_link->epon_ni };
+    BCMOLT_CFG_INIT(&pon_cfg, epon_ni, pon_key);
+    BCMOLT_CFG_PROP_GET(&pon_cfg, epon_ni, mac_address);
+    rc = bcmolt_cfg_get(hde_link->device_id, &pon_cfg.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->device_id], "Failure - Cannot query PON mac address!.\n");
+    }
+    return pon_cfg.data.mac_address;
+}
+
+static bcmolt_epon_llid get_llid(const hde_key *hde_link)
+{
+    bcmos_errno rc;
+    bcmolt_epon_link_cfg link_cfg;
+    bcmolt_epon_link_key link_key =
+    {
+        .epon_ni = hde_link->epon_ni,
+        .mac_address = hde_link->mac_addr
+    };
+    BCMOLT_CFG_INIT(&link_cfg, epon_link, link_key);
+    BCMOLT_CFG_PROP_GET(&link_cfg, epon_link, llid);
+    rc = bcmolt_cfg_get(hde_link->device_id, &link_cfg.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(
+            ERROR,
+            hde_data.log_id[hde_link->device_id],
+            "Failure - cannot query LLID for link %02x:%02x:%02x:%02x:%02x:%02x!.\n",
+            hde_link->mac_addr.u8[0],
+            hde_link->mac_addr.u8[1],
+            hde_link->mac_addr.u8[2],
+            hde_link->mac_addr.u8[3],
+            hde_link->mac_addr.u8[4],
+            hde_link->mac_addr.u8[5]);
+    }
+    return link_cfg.data.llid;
+}
+
+/* By spec the SCI is the LLID of the link appended to the corresponding epon port mac address */
+static void create_sci(const bcmos_mac_address *epon_port_mac, bcmolt_epon_llid llid, uint8_t *sci)
+{
+    sci[7] = (uint8_t) llid;
+    sci[6] = (uint8_t) (llid >> 8);
+    sci[5] = epon_port_mac->u8[5];
+    sci[4] = epon_port_mac->u8[4];
+    sci[3] = epon_port_mac->u8[3];
+    sci[2] = epon_port_mac->u8[2];
+    sci[1] = epon_port_mac->u8[1];
+    sci[0] = epon_port_mac->u8[0];
+}
+
+static void handle_not_encrypted_enable_encryption(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    dpoe_encrypt_mode mode = DPOE_ENCRYPT_MODE_NONE;
+    // Retreive useful link info and store it to save messages later.
+    hde_link->link_rate = get_link_rate(&hde_link->hde_link);
+    hde_link->corresponding_pon_port_mac = get_pon_mac(&hde_link->hde_link);
+    hde_link->llid = get_llid(&hde_link->hde_link);
+
+    if (hde_link->link_rate == BCMOLT_EPON_LINK_RATE_TEN_TEN || hde_link->link_rate == BCMOLT_EPON_LINK_RATE_TEN_ONE)
+    {
+        mode = DPOE_ENCRYPT_MODE_10DOWN;
+    }
+    else if (hde_link->link_rate == BCMOLT_EPON_LINK_RATE_ONE_ONE)
+    {
+        mode = DPOE_ENCRYPT_MODE_1DOWN;
+    }
+    else
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id], "Failure - UNKNOWN LINK RATE!!!.\n");
+    }
+
+    dpoe_encrypt_oam_set_request_send(
+        &hde_link->hde_link,
+        KEY_EXCHANGE_PERIOD,
+        mode,
+        hde_link->corresponding_pon_port_mac);
+
+    bcmos_timer_start(&hde_link->oam_timeout_timer, HDE_ENABLE_OAM_WAIT_US + OAM_TIMEOUT_GRACE_PERIOD_US);
+
+    hde_link->state = HDE_STATE_WAITING_FOR_OAM_ACK;
+}
+
+static void handle_encrypted_disable_encryption(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    bcmolt_encryption_information_container empty_key = { };
+    bcmos_errno rc;
+
+    // Turn off encryption on the embedded side.
+    rc = set_new_key(&hde_link->hde_link, &empty_key, BCMOLT_EPON_KEY_CHOICE_KEY_0, BCMOLT_EPON_ENCRYPTION_MODE_NO_ENCRYPTION);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id],
+            "Failure - Cannot communicate with the embedded!.\n");
+    }
+
+    // Send the disable OAM to the link. Don't bother waiting for a response.
+    dpoe_encrypt_oam_set_request_send(&hde_link->hde_link, 0, DPOE_ENCRYPT_MODE_NONE, get_pon_mac(&hde_link->hde_link));
+
+    hde_link->state = HDE_STATE_NOT_ENCRYPTED;
+}
+
+static void handle_encrypted_frame_rx(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    const bcmolt_epon_link_frame_captured *frame_captured = (const bcmolt_epon_link_frame_captured*) ind;
+    uint8_t new_key[16];
+    bcmolt_epon_key_choice key_choice;
+    bcmos_errno rc;
+    bcmolt_encryption_information_container key_for_embedded;
+    rc = dpoe_encrypt_oam_parse_new_key(&frame_captured->data.frame, new_key, &key_choice);
+    if (rc != BCM_ERR_OK)
+    {
+        // Not a DPoE new key, ignore it
+        return;
+    }
+
+    if (hde_link->link_rate == BCMOLT_EPON_LINK_RATE_TEN_TEN || hde_link->link_rate == BCMOLT_EPON_LINK_RATE_TEN_ONE)
+    {
+        uint8_t sci[8];
+        create_sci(&hde_link->corresponding_pon_port_mac, hde_link->llid, sci);
+        key_for_embedded.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CTR;
+        memcpy(key_for_embedded.u.ctr.key, new_key, sizeof(new_key));
+        memcpy(key_for_embedded.u.ctr.sci, sci, sizeof(sci));
+    }
+    else if (hde_link->link_rate == BCMOLT_EPON_LINK_RATE_ONE_ONE)
+    {
+        key_for_embedded.format = BCMOLT_EPON_ENCRYPTION_INFORMATION_FORMAT_CFB;
+        memcpy(key_for_embedded.u.cfb.key, new_key, sizeof(new_key));
+    }
+    else
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id], "Failure - UNKNOWN LINK RATE!!!.\n");
+    }
+
+    rc = set_new_key(&hde_link->hde_link, &key_for_embedded, key_choice, BCMOLT_EPON_ENCRYPTION_MODE_EPON_ZERO_OVERHEAD_AES);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id],
+            "Failure - Cannot communicate with the embedded!.\n");
+    }
+}
+
+static void handle_waiting_for_oam_ack_oam_timeout(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id], "Failure - No response to initial OAM.\n");
+    hde_link->state = HDE_STATE_NOT_ENCRYPTED;
+}
+
+static void handle_waiting_for_oam_ack_frame_rx(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    const bcmolt_epon_link_frame_captured *frame_captured = (const bcmolt_epon_link_frame_captured*) ind;
+    uint8_t encrypt_mode_response;
+    uint8_t key_expiry_response;
+    bcmos_errno rc;
+
+    rc = dpoe_encrypt_oam_parse_set_response(&frame_captured->data.frame, &encrypt_mode_response, &key_expiry_response);
+    if (rc != BCM_ERR_OK)
+    {
+        // Not a encryption set reponse, ignore it
+        return;
+    }
+
+    /* 0 represents 'no error'. */
+    if ((encrypt_mode_response != 0) || (key_expiry_response != 0))
+    {
+        BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id],
+            "Failure - The link refused one of the requested TLVs.\n");
+        hde_link->state = HDE_STATE_NOT_ENCRYPTED;
+        return;
+    }
+
+    // We have an appropriate response, so move on normally.
+    bcmos_timer_stop(&hde_link->oam_timeout_timer);
+    hde_link->state = HDE_STATE_ENCRYPTED;
+}
+
+static void handle_waiting_for_oam_ack_disable_encryption(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    hde_link->state = HDE_STATE_NOT_ENCRYPTED;
+}
+
+static void handle_ignore(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    // Literally do nothing
+}
+
+static void handle_error(hde_link_data *hde_link, const bcmolt_msg *ind)
+{
+    BCM_LOG(ERROR, hde_data.log_id[hde_link->hde_link.device_id], "Unexpected event in state %s\n",
+        get_state_string(hde_link->state));
+}
+
+static link_state_handler link_state_machine[HDE_STATE__NUM_OF][HDE_EVENT__NUM_OF] =
+{
+    [HDE_STATE_NOT_ENCRYPTED] =
+    {
+        [HDE_EVENT_ENABLE_ENCRYPTION] = handle_not_encrypted_enable_encryption,
+        [HDE_EVENT_DISABLE_ENCRYPTION] = handle_error,
+        [HDE_EVENT_FRAME_RX] = handle_ignore,
+        [HDE_EVENT_OAM_TIMEOUT] = handle_ignore,
+    },
+    [HDE_STATE_ENCRYPTED] =
+    {
+        [HDE_EVENT_ENABLE_ENCRYPTION] = handle_error,
+        [HDE_EVENT_DISABLE_ENCRYPTION] = handle_encrypted_disable_encryption,
+        [HDE_EVENT_FRAME_RX] = handle_encrypted_frame_rx,
+        [HDE_EVENT_OAM_TIMEOUT] = handle_ignore,
+    },
+    [HDE_STATE_WAITING_FOR_OAM_ACK] =
+    {
+        [HDE_EVENT_ENABLE_ENCRYPTION] = handle_error,
+        [HDE_EVENT_DISABLE_ENCRYPTION] = handle_waiting_for_oam_ack_disable_encryption,
+        [HDE_EVENT_FRAME_RX] = handle_waiting_for_oam_ack_frame_rx,
+        [HDE_EVENT_OAM_TIMEOUT] = handle_waiting_for_oam_ack_oam_timeout,
+    }
+};
+
+static int32_t find_table_index(const hde_key *hde_link)
+{
+    uint16_t i;
+    for (i = 0; i < hde_data.num_links; ++i)
+    {
+        if (memcmp(hde_link, &hde_data.links[i], sizeof(*hde_link)) == 0)
+        {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static void remove_link_from_table(int32_t link_index)
+{
+    int32_t i;
+    for (i = link_index; i < hde_data.num_links - 1; ++i)
+    {
+        /* Backwards copy all data expect for the timer handle */
+        hde_data.links[i].hde_link = hde_data.links[i + 1].hde_link;
+        hde_data.links[i].link_rate = hde_data.links[i + 1].link_rate;
+        hde_data.links[i].llid = hde_data.links[i + 1].llid;
+        hde_data.links[i].state = hde_data.links[i + 1].state;
+    }
+    /* also clear the state of the now empty link */
+    hde_data.links[hde_data.num_links - 1].state = HDE_STATE_NOT_ENCRYPTED;
+    hde_data.num_links--;
+}
+
+static void run_state_machine(int32_t link_index, const hde_task_msg *task_msg)
+{
+    if (link_index < 0) // We don't know of this link yet.
+    {
+        if (hde_data.num_links == HDE_MAX_NUM_LINKS)
+        {
+            BCM_LOG(ERROR, hde_data.log_id[task_msg->key.device_id], "Failure - Cannot add any more links!.\n");
+            return;
+        }
+
+        link_index = hde_data.num_links;
+        hde_data.links[link_index].hde_link = task_msg->key;
+        link_state_machine[hde_data.links[link_index].state][task_msg->event](&hde_data.links[link_index], task_msg->rx);
+
+        if (hde_data.links[link_index].state != HDE_STATE_NOT_ENCRYPTED)
+        {
+            // This link did not end in the state 'not encrypted', so we need to start tracking him.
+            ++hde_data.num_links;
+        }
+    }
+    else // We already know of this link.
+    {
+        link_state_machine[hde_data.links[link_index].state][task_msg->event](&hde_data.links[link_index], task_msg->rx);
+
+        if (hde_data.links[link_index].state == HDE_STATE_NOT_ENCRYPTED)
+        {
+            // The link has is no longer encrypted so lets forget about him.
+            remove_link_from_table(link_index);
+        }
+    }
+}
+
+static void hde_handle_event(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    hde_task_msg *task_msg = (hde_task_msg *) os_msg;
+
+    int32_t link_index;
+
+    link_index = find_table_index(&task_msg->key);
+    run_state_machine(link_index, task_msg);
+
+    if (task_msg->rx != NULL)
+    {
+        bcmolt_msg_free(task_msg->rx);
+    }
+    bcmos_free(task_msg);
+}
+
+static bcmos_timer_rc oam_response_timeout(bcmos_timer *timer, long data)
+{
+    hde_task_msg *task_msg = bcmos_calloc(sizeof(*task_msg));
+
+    BUG_UNLESS(task_msg);
+
+    task_msg->key = hde_data.links[data].hde_link;
+    task_msg->event = HDE_EVENT_OAM_TIMEOUT;
+    task_msg->os_msg.handler = hde_handle_event;
+
+    bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_EPON_HDE, &task_msg->os_msg, BCMOS_MSG_SEND_AUTO_FREE);
+    return BCMOS_TIMER_OK;
+}
+
+static bcmos_errno hde_set_encryption(hde_key* key, bcmos_bool enabled)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    hde_task_msg *task_msg = bcmos_calloc(sizeof(*task_msg));
+
+    BUG_UNLESS(task_msg);
+
+    task_msg->key = *key;
+    task_msg->event = enabled ? HDE_EVENT_ENABLE_ENCRYPTION : HDE_EVENT_DISABLE_ENCRYPTION;
+    task_msg->os_msg.handler = hde_handle_event;
+
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_EPON_HDE, &task_msg->os_msg, BCMOS_MSG_SEND_AUTO_FREE);
+    BUG_ON(rc != BCM_ERR_OK);
+    return rc;
+}
+
+static bcmos_errno hde_cli_enable(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    hde_key key =
+    {
+        .device_id = current_device,
+        .epon_ni = (bcmolt_epon_ni)(bcmcli_find_named_parm(session, "epon_ni")->value.number),
+        .mac_addr = (bcmos_mac_address)(bcmcli_find_named_parm(session, "mac_address")->value.mac)
+    };
+
+    rc = set_pon_encryption_if_needed(&key);
+    if (rc != BCM_ERR_OK)
+    {
+        return rc;
+    }
+    return hde_set_encryption(&key, BCMOS_TRUE);
+}
+
+static bcmos_errno hde_cli_disable(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    hde_key key =
+    {
+        .device_id = current_device,
+        .epon_ni = (bcmolt_epon_ni)(bcmcli_find_named_parm(session, "epon_ni")->value.number),
+        .mac_addr = (bcmos_mac_address)(bcmcli_find_named_parm(session, "mac_address")->value.mac)
+    };
+
+    return hde_set_encryption(&key, BCMOS_FALSE);
+}
+
+// public indication handler interface -- called in transport layer context
+bcmos_errno bcmolt_epon_hde_process_rx(bcmolt_devid device_id, uint8_t instance, bcmolt_proxy_rx *rx)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    bcmolt_epon_link_frame_captured* actual_rx;
+    hde_task_msg *task_msg;
+    hde_key key;
+
+    if (!is_running)
+    {
+        return BCM_ERR_OK;
+    }
+
+    // We only look at message targetting epon links.
+    if (rx->hdr.obj_type != BCMOLT_OBJ_ID_EPON_LINK)
+    {
+        // Not an error, we just don't care about this object.
+        return BCM_ERR_OK;
+    }
+
+    // This is the only kind of proxy RX currently supported.
+    if (rx->hdr.subgroup != BCMOLT_EPON_LINK_PROXY_RX_ID_FRAME_CAPTURED)
+    {
+        return BCM_ERR_OK;
+    }
+
+    // This is safe due to the above subgroup check.
+    actual_rx = (bcmolt_epon_link_frame_captured*) rx;
+    key.device_id = device_id;
+    key.epon_ni = instance;
+    key.mac_addr = actual_rx->key.mac_address;
+
+    task_msg = bcmos_calloc(sizeof(*task_msg));
+    if (task_msg == NULL)
+    {
+        rc = BCM_ERR_NOMEM;
+    }
+    else
+    {
+        bcmolt_msg *rx_clone = NULL;
+        rc = bcmolt_msg_clone(&rx_clone, &rx->hdr);
+        if (rc != BCM_ERR_OK)
+        {
+            bcmos_free(task_msg);
+            return rc;
+        }
+        actual_rx = (bcmolt_epon_link_frame_captured*) rx_clone;
+
+        task_msg->rx = rx_clone;
+        task_msg->key = key;
+        task_msg->event = HDE_EVENT_FRAME_RX;
+        task_msg->os_msg.handler = hde_handle_event;
+        rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_EPON_HDE, &task_msg->os_msg, BCMOS_MSG_SEND_AUTO_FREE);
+        if (rc != BCM_ERR_OK)
+        {
+            BCM_LOG(ERROR, hde_data.log_id[device_id], "Message send failed!\n");
+            bcmolt_msg_free(rx_clone);
+        }
+    }
+
+    return rc;
+}
+
+void bcmolt_epon_hde_appl_cli_init(bcmcli_entry *top_dir)
+{
+    static const char *dir_name = "hde";
+
+    if (bcmcli_dir_find(top_dir, dir_name))
+    {
+        return;
+    }
+
+    bcmcli_entry *dir = bcmcli_dir_add(top_dir, dir_name, "EPON OAM negotiation commands", BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(dir == NULL);
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "enable",
+        "Enable encryption for an EPON link",
+        hde_cli_enable,
+        BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("mac_address", "MAC address", BCMCLI_PARM_MAC, 0));
+
+    BCMCLI_MAKE_CMD(
+        dir,
+        "disable",
+        "Disable encryption for an EPON link",
+        hde_cli_disable,
+        BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("mac_address", "MAC address", BCMCLI_PARM_MAC, 0));
+}
+
+void bcmolt_epon_hde_appl_init(void)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+    uint32_t i;
+
+    bcmos_task_parm task_params =
+    {
+        .name = "user_appl_hde",
+        .priority = TASK_PRIORITY_USER_APPL_EPON_HDE,
+        .core = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+
+    bcmos_timer_parm timer_parm =
+    {
+        .name = "oam_timeout_timer",
+        .owner = BCMOS_MODULE_ID_NONE,
+        .periodic = BCMOS_FALSE,
+        .handler = oam_response_timeout
+    };
+
+    if (is_running)
+    {
+        return;
+    }
+
+    is_running = BCMOS_TRUE;
+
+    for (i=0; i<BCMTR_MAX_OLTS; i++)
+    {
+        char log_name[MAX_DEV_LOG_ID_NAME];
+        snprintf(log_name, sizeof(log_name)-1, "user_appl_hde_%d", i);
+        hde_data.log_id[i] = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_ERROR, DEV_LOG_ID_TYPE_BOTH);
+    }
+
+    hde_data.links = bcmos_calloc(HDE_MAX_NUM_LINKS * sizeof(hde_data.links[0]));
+    BUG_ON(hde_data.links == NULL);
+
+    rc = bcmos_task_create(&hde_data.task, &task_params);
+    BUG_ON(rc != BCM_ERR_OK);
+
+    bcmos_module_parm module_params =
+    {
+        .qparm = { .name = "user_appl_hde", .size = HDE_TASK_MSG_Q_SIZE }
+    };
+    rc = bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_EPON_HDE, &hde_data.task, &module_params);
+    BUG_ON(rc != BCM_ERR_OK);
+
+    for (i = 0; i < HDE_MAX_NUM_LINKS; ++i)
+    {
+        timer_parm.data = i;
+        rc = bcmos_timer_create(&hde_data.links[i].oam_timeout_timer, &timer_parm);
+        BUG_ON(rc != BCM_ERR_OK);
+    }
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.h b/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.h
new file mode 100644
index 0000000..ce7f95b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/bcmolt_epon_hde.h
@@ -0,0 +1,55 @@
+/*
+ <: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.
+
+ :>
+ */
+
+#ifndef BCMOLT_EPON_HDE_H_
+#define BCMOLT_EPON_HDE_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcmolt_utils.h>
+#include <bcmcli.h>
+
+typedef struct
+{
+    bcmolt_devid device_id;
+    bcmolt_epon_ni epon_ni;
+    bcmos_mac_address mac_addr;
+} hde_key;
+
+// initialize the EPON OAM negotiation application (should be called as part of user application startup)
+void bcmolt_epon_hde_appl_init(void);
+
+// process an indication.
+bcmos_errno bcmolt_epon_hde_process_rx(bcmolt_devid device_id, uint8_t instance, bcmolt_proxy_rx *rx);
+
+// initialize HDE CLI
+void bcmolt_epon_hde_appl_cli_init(bcmcli_entry *top_dir);
+
+#endif /* BCMOLT_EPON_HDE_H_ */
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.c b/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.c
new file mode 100644
index 0000000..13ef632
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.c
@@ -0,0 +1,291 @@
+/*
+ <: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.
+
+ :>
+ */
+
+#include "bcmos_system.h"
+#include "bcmolt_model_types.h"
+#include "bcmolt_api.h"
+#include "oam_defs.h"
+#include "bcmolt_math.h"
+#include <encrypt_oam.h>
+
+static const bcmos_mac_address slow_prot_mcast_mac =
+{
+    .u8 = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x02 }
+};
+
+static uint8_t oam_buffer[OAM_MAX_PACKET_SIZE];
+
+static bcmos_bool oam_var_add_tlv_header(bcmolt_buf *buf, oam_var_branch branch, uint16_t leaf, uint8_t len)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+    uint8_t *snap = bcmolt_buf_snap_get(buf);
+
+    ok = ok && (len <= OAM_MAX_TLV_LENGTH);
+    ok = ok && bcmolt_buf_write_u8(buf, (uint8_t) branch);
+    ok = ok && bcmolt_buf_write_u16_be(buf, leaf);
+    ok = ok && bcmolt_buf_write_u8(buf, oam_var_tlv_length_encode(len));
+    ok = ok && (bcmolt_buf_get_remaining_size(buf) >= len);
+    if (!ok)
+    {
+        bcmolt_buf_snap_restore(buf, snap);
+    }
+    return ok;
+}
+
+static bcmos_bool dpoe_encrypt_tlv_add(bcmolt_buf *oambuf, uint16_t period, dpoe_encrypt_mode mode)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+
+    // encryption mode TLV
+    ok = ok && oam_var_add_tlv_header(oambuf, OAM_VAR_BRANCH_DPOE_ATTRIBUTE, (uint16_t) OAM_DPOE_ATTR_ENCRYPT_MODE, 1);
+    ok = ok && bcmolt_buf_write_u8(oambuf, (uint8_t) mode);
+
+    /* Key Expiry Time TLV */
+    ok = ok && oam_var_add_tlv_header(oambuf, OAM_VAR_BRANCH_DPOE_ATTRIBUTE, (uint16_t) OAM_DPOE_ATTR_KEY_EXPIRY_TIME, 2);
+    ok = ok && bcmolt_buf_write_u16_be(oambuf, period);
+
+    return ok;
+}
+
+static bcmos_bool oam_pdu_build_common(bcmos_mac_address pon_mac, bcmolt_buf *outbuf)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+
+    ok = ok && bcmolt_buf_write_mac_address(outbuf, slow_prot_mcast_mac);        // DA
+    ok = ok && bcmolt_buf_write_mac_address(outbuf, pon_mac);                    // SA
+    ok = ok && bcmolt_buf_write_u16_be(outbuf, ETHERTYPE_SLOWPROTOCOLS);         // Type
+    ok = ok && bcmolt_buf_write_u8(outbuf, (uint8_t) SLOW_PROTOCOL_SUBTYPE_OAM); // Subtype
+
+    return ok;
+}
+
+/* Sends OAM frame 'frame' to a link via the inject frame operation */
+static bcmos_bool oam_pdu_transmit(const hde_key* hde_link, uint8_t *frame, uint16_t len)
+{
+    bcmolt_ethernet_frame_unmasked oam_frame;
+    bcmolt_epon_link_inject_frame  inject_frame;
+
+    oam_frame.frame_octets.len = len;
+    oam_frame.frame_octets.val = frame;
+    bcmolt_epon_link_key link_key =
+    {
+        .epon_ni = hde_link->epon_ni,
+        .mac_address = hde_link->mac_addr
+    };
+
+    BCMOLT_PROXY_INIT(&inject_frame, epon_link, inject_frame, link_key);
+    BCMOLT_PROXY_PROP_SET(&inject_frame, epon_link, inject_frame, frame, oam_frame);
+    if (bcmolt_proxy_send(hde_link->device_id, &inject_frame.hdr) != BCM_ERR_OK)
+    {
+        return BCMOS_FALSE;
+    }
+
+    return BCMOS_TRUE;
+}
+
+static bcmos_bool oam_var_get_next(bcmolt_buf *buf, oam_var_generic *oamvar)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+    uint8_t branch = OAM_VAR_BRANCH_TERMINATION;
+
+    memset(oamvar, 0, sizeof(*oamvar));
+    ok = ok && (bcmolt_buf_get_remaining_size(buf) != 0);
+    ok = ok && bcmolt_buf_read_u8(buf, &branch); // Read the branch.
+    if (ok)
+    {
+        switch (branch)
+        {
+        case OAM_VAR_BRANCH_ATTRIBUTE:
+        case OAM_VAR_BRANCH_ACTION:
+        case OAM_VAR_BRANCH_DPOE_ACT:
+        case OAM_VAR_BRANCH_DPOE_ATTRIBUTE:
+        case OAM_VAR_BRANCH_DPOE_OBJECT:
+            oamvar->branch = (oam_var_branch) branch;
+            ok = ok && bcmolt_buf_read_u16_be(buf, &oamvar->leaf); // Read leaf
+            ok = ok && bcmolt_buf_read_u8(buf, &oamvar->width); // Read Width
+            oamvar->value = bcmolt_buf_snap_get(buf); // Keep the position of value
+            oamvar->width = oam_var_tlv_length_encode(oamvar->width); // Re-encode width via OAM spec
+            ok = ok && bcmolt_buf_skip(buf, oamvar->width); // Skip a 'width' amount
+            break;
+
+        case OAM_VAR_BRANCH_TERMINATION:
+        default:
+            ok = BCMOS_FALSE;
+            break;
+        }
+    }
+
+    ok = ok && bcmolt_buf_skip(buf, oamvar->width);
+    return ok;
+}
+
+/* Loops through 'oam_resp' looking for the provided branch/leaf. If found it will return it. */
+static bcmos_bool oam_var_get_branch_leaf(uint8_t *oam_resp, uint32_t oam_resp_len, oam_var_branch branch, uint16_t leaf, oam_var_generic *oam_var)
+{
+    bcmolt_buf buf;
+    oam_var_generic var;
+
+    bcmolt_buf_init(&buf, oam_resp_len, oam_resp, (bcmos_endian) BCMOLT_BUF_ENDIAN_FIXED);
+
+    while (oam_var_get_next(&buf, &var)) // Keep going through all OAM vars.
+    {
+        if ((var.branch == branch) && (var.leaf == leaf)) // Check to see it is the one we are looking for.
+        {
+            *oam_var = var;
+            return BCMOS_TRUE;
+        }
+    }
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool oam_parse_to_dpoe_opcode(bcmolt_buf *buf, oam_dpoe_opcode opcode)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+    uint24_t dpoe_oui = { .low_hi = { .hi = 0x00, .mid = 0x10, .low = 0x00 } };
+    uint16_t u16;
+    uint8_t u8;
+    uint24_t u24;
+    ok = ok && bcmolt_buf_skip(buf, 12); // Skip both mac addresses
+    ok = ok && bcmolt_buf_read_u16_be(buf, &u16); // Read ethertype
+    ok = ok && u16 == ETHERTYPE_SLOWPROTOCOLS; // Make sure it is slow protocol
+    ok = ok && bcmolt_buf_read_u8(buf, &u8); // Read the subtype
+    ok = ok && u8 == SLOW_PROTOCOL_SUBTYPE_OAM; // Make sure it is OAM
+    ok = ok && bcmolt_buf_skip(buf, 2); // Skip flags
+    ok = ok && bcmolt_buf_read_u8(buf, &u8); // Read the code
+    ok = ok && u8 == OAM_PDU_CODE_ORG_EXT; // Make sure it is organization specific
+    ok = ok && bcmolt_buf_read_u24(buf, &u24); // Read the OUI
+    ok = ok && memcmp(&dpoe_oui, &u24, sizeof(uint24_t)) == 0; // Make sure it is the DPoE OUI
+    ok = ok && bcmolt_buf_read_u8(buf, &u8); // Read the opcode
+    ok = ok && opcode == u8; // Make sure it matches the provided opcode
+    return ok;
+}
+
+bcmos_errno dpoe_encrypt_oam_set_request_send(const hde_key *hde_link, uint16_t period, dpoe_encrypt_mode encrypt_mode, bcmos_mac_address pon_mac)
+{
+    // send the encrypt mode and key expiry time TLVs to ONU.
+    bcmolt_buf buf;
+    ieee_oui dpoe_oui = { { 0x00, 0x10, 0x00 } };
+    bcmos_bool ok = BCMOS_TRUE;
+
+    memset(oam_buffer, 0, sizeof(oam_buffer));
+
+    bcmolt_buf_init(&buf, OAM_MAX_PACKET_SIZE, oam_buffer, BCMOLT_BUF_ENDIAN_FIXED);
+    ok = ok && oam_pdu_build_common(pon_mac, &buf);
+    ok = ok && bcmolt_buf_write_u16_be(&buf, (uint16_t) OAM_PDU_FLAG_SEND_ANY); /* write flags */
+    ok = ok && bcmolt_buf_write_u8(&buf, (uint8_t) OAM_PDU_CODE_ORG_EXT); /* write  code */
+    ok = ok && bcmolt_buf_write(&buf, dpoe_oui.byte, 3); /* write oui */
+    ok = ok && bcmolt_buf_write_u8(&buf, OAM_DPOE_OPCODE_SET_REQUEST); /* write opcode */
+    ok = ok && dpoe_encrypt_tlv_add(&buf, period, encrypt_mode); /* write encryption TLVs */
+    ok = ok && bcmolt_buf_write_u16_be(&buf, 0); /* write END */
+    ok = ok && oam_pdu_transmit(hde_link, oam_buffer, MAX(bcmolt_buf_get_used(&buf), 60)); /* Send it */
+
+    if (!ok)
+    {
+        return BCM_ERR_NOMEM;
+    }
+    else
+    {
+        return BCM_ERR_OK;
+    }
+    return ok;
+}
+
+bcmos_errno dpoe_encrypt_oam_parse_set_response(const bcmolt_u8_list_u32 *frame, uint8_t *encrypt_mode_response, uint8_t *key_expiry_response)
+{
+    oam_var_generic encrypt_mode;
+    oam_var_generic key_expiry_time;
+    bcmolt_buf buf;
+    bcmolt_buf_init(&buf, frame->len, frame->val, BCMOLT_BUF_ENDIAN_FIXED);
+
+    // Move the OAM buffer forward to the opcode
+    if (!oam_parse_to_dpoe_opcode(&buf, OAM_DPOE_OPCODE_SET_RESPONSE))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    // Check for encrypt mode TLV
+    if (!oam_var_get_branch_leaf(
+        bcmolt_buf_snap_get(&buf),
+        bcmolt_buf_get_remaining_size(&buf),
+        OAM_VAR_BRANCH_DPOE_ATTRIBUTE,
+        OAM_DPOE_ATTR_ENCRYPT_MODE,
+        &encrypt_mode))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    // Check for key expiry time TLV
+    if (!oam_var_get_branch_leaf(
+        bcmolt_buf_snap_get(&buf),
+        bcmolt_buf_get_remaining_size(&buf),
+        OAM_VAR_BRANCH_DPOE_ATTRIBUTE,
+        OAM_DPOE_ATTR_KEY_EXPIRY_TIME,
+        &key_expiry_time))
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    *encrypt_mode_response = encrypt_mode.width;
+    *key_expiry_response = key_expiry_time.width;
+    return BCM_ERR_OK;
+}
+
+bcmos_errno dpoe_encrypt_oam_parse_new_key(const bcmolt_u8_list_u32 *frame, uint8_t *new_key, bcmolt_epon_key_choice *key_choice)
+{
+    bcmos_bool ok = BCMOS_TRUE;
+    uint8_t key_number;
+    uint8_t key_length;
+    bcmolt_buf buf;
+    bcmolt_buf_init(&buf, frame->len, frame->val, BCMOLT_BUF_ENDIAN_FIXED);
+    ok = oam_parse_to_dpoe_opcode(&buf, OAM_DPOE_OPCODE_KEY_EXCHANGE);
+    ok = ok && bcmolt_buf_read_u8(&buf, &key_number); // Read key number
+    ok = ok && bcmolt_buf_read_u8(&buf, &key_length); // Read key length
+    ok = ok && key_length == 16; // Make sure the key is 16 byte length
+    ok = ok && bcmolt_buf_read(&buf, new_key, 16); // Read the key
+
+    if (!ok)
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    if (key_number == 0)
+    {
+        *key_choice = BCMOLT_EPON_KEY_CHOICE_KEY_0;
+    }
+    else if (key_number == 1)
+    {
+        *key_choice = BCMOLT_EPON_KEY_CHOICE_KEY_1;
+    }
+    else
+    {
+        return BCM_ERR_PARSE;
+    }
+
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.h b/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.h
new file mode 100644
index 0000000..18cd2eb
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/encrypt_oam.h
@@ -0,0 +1,59 @@
+/*

+ <: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.

+

+ :>

+ */

+

+#ifndef ENCRYPT_OAM_H_

+#define ENCRYPT_OAM_H_

+

+#include "oam_defs.h"

+#include "bcmolt_epon_hde.h"

+

+/* Send a Encryption Request OAM to ONU. */

+bcmos_errno dpoe_encrypt_oam_set_request_send(

+    const hde_key *hde_link,

+    uint16_t period,

+    dpoe_encrypt_mode encrypt_mode,

+    bcmos_mac_address pon_mac);

+

+/* Attempt to parse a DPoE set response and fill in the values of the two TLVs we initially sent down.

+ * This will return BCM_ERR_PARSE if it fails to fill in either of the TLVs.

+ */

+bcmos_errno dpoe_encrypt_oam_parse_set_response(

+    const bcmolt_u8_list_u32 *frame,

+    uint8_t *encrypt_mode_response,

+    uint8_t *key_expiry_response);

+

+/* Attempts to parse an OAM frame and fill in the values of new_key and key_choice from that frame.

+ * This will return BCM_ERR_PARSE if it fails to fill in either new_key or key_choice.

+ */

+bcmos_errno dpoe_encrypt_oam_parse_new_key(

+    const bcmolt_u8_list_u32 *frame,

+    uint8_t *new_key,

+    bcmolt_epon_key_choice *key_choice);

+

+#endif /* ENCRYPT_OAM_H_ */

diff --git a/bcm68620_release/release/host_reference/user_appl/epon_hde/oam_defs.h b/bcm68620_release/release/host_reference/user_appl/epon_hde/oam_defs.h
new file mode 100644
index 0000000..276d5eb
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_hde/oam_defs.h
@@ -0,0 +1,153 @@
+/*

+ <: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.

+

+ :>

+ */

+

+#ifndef OAM_DEFS_H_

+#define OAM_DEFS_H_

+

+#include "bcmolt_model_types.h"

+

+#define ETHERTYPE_SLOWPROTOCOLS 0x8809

+#define OAM_TIMEOUT_GRACE_PERIOD_US 100000

+#define HDE_ENABLE_OAM_WAIT_US 3e6 // 3 seconds
#define OAM_MAX_PACKET_SIZE 1518

+#define OAM_MAX_TLV_LENGTH 128

+

+/* Common OAMPDU Structure */

+typedef enum

+{

+    SLOW_PROTOCOL_SUBTYPE_LACP     = 0x01, /* Link Agregation Control Protocol */

+    SLOW_PROTOCOL_SUBTYPE_LAMP     = 0x02, /* Link Agregation Marker Protocol */

+    SLOW_PROTOCOL_SUBTYPE_OAM      = 0x03, /* OAM */

+    SLOW_PROTOCOL_SUBTYPE_ORG_SPEC = 0x0A  /* Organization Specific Subtype */

+} slow_protocol_subtype;

+

+/* Standard OAM Flags field.  IEEE802.3-2008_section5, ch.57.4.2.1 */

+typedef enum

+{

+    OAM_PDU_FLAG_LINK_FAULT     = (1 << 0), /* 0x0001 */

+    OAM_PDU_FLAG_DYING_GASP     = (1 << 1), /* 0x0002 */

+    OAM_PDU_FLAG_CRITICAL_EVENT = (1 << 2), /* 0x0004 */

+    OAM_PDU_FLAG_LOCAL_EVAL     = (1 << 3), /* 0x0008 */

+    OAM_PDU_FLAG_LOCAL_STABLE   = (1 << 4), /* 0x0010 */

+    OAM_PDU_FLAG_REMOTE_EVAL    = (1 << 5), /* 0x0020 */

+    OAM_PDU_FLAG_REMOTE_STABLE  = (1 << 6), /* 0x0040 */

+    OAM_PDU_FLAG_SEND_ANY       = (OAM_PDU_FLAG_LOCAL_STABLE | OAM_PDU_FLAG_REMOTE_STABLE)

+} oam_pdu_flag;

+

+/* Standard OAM Code field.  IEEE802.3-2008_section5, ch.57.4.2.2 */

+typedef enum

+{

+    OAM_PDU_CODE_INFO              = 0x0,

+    OAM_PDU_CODE_EVENTNOTIFICATION = 0x01,

+    OAM_PDU_CODE_VARREQUEST        = 0x02,

+    OAM_PDU_CODE_VARRESPONSE       = 0x03,

+    OAM_PDU_CODE_LOOPBACK          = 0x04,

+    OAM_PDU_CODE_ORG_EXT           = 0xfe

+} oam_pdu_code;

+

+/* IEEE Organization Unique Identifier */

+typedef struct

+{

+    uint8_t byte[3];

+} ieee_oui;

+

+/* Branch types */

+typedef enum

+{

+    OAM_VAR_BRANCH_TERMINATION    = 0x00,

+    OAM_VAR_BRANCH_ATTRIBUTE      = 0x07,

+    OAM_VAR_BRANCH_ACTION         = 0x09,

+    OAM_VAR_BRANCH_DPOE_OBJECT    = 0xD6,

+    OAM_VAR_BRANCH_DPOE_ATTRIBUTE = 0xD7,

+    OAM_VAR_BRANCH_DPOE_PROG_CTR  = 0xD8,

+    OAM_VAR_BRANCH_DPOE_ACT       = 0xD9

+} oam_var_branch;

+

+typedef enum

+{

+    OAM_DPOE_OPCODE_RESERVED       = 0,

+    OAM_DPOE_OPCODE_GET_REQUEST    = 1,

+    OAM_DPOE_OPCODE_GET_RESPONSE   = 2,

+    OAM_DPOE_OPCODE_SET_REQUEST    = 3,

+    OAM_DPOE_OPCODE_SET_RESPONSE   = 4,

+    OAM_DPOE_OPCODE_IPMC_CTRL      = 5,

+    OAM_DPOE_OPCODE_MCAST_REG      = 6,

+    OAM_DPOE_OPCODE_MCAST_REG_RESP = 7,

+    OAM_DPOE_OPCODE_KEY_EXCHANGE   = 8,

+    OAM_DPOE_OPCODE_FILE_TRANSFER  = 9,

+    OAM_DPOE_OPCODE_IPMC_CTRL_RESP = 0x0a /* DPoE 2.0 */

+} oam_dpoe_opcode;

+

+typedef enum

+{

+    OAM_DPOE_ATTR_KEY_EXPIRY_TIME = 0x0401,

+    OAM_DPOE_ATTR_ENCRYPT_MODE = 0x0402

+} oam_dpoe_attr;

+

+/* DPoE Key Exchange OAM TLV encryption mode */

+typedef enum

+{

+    DPOE_ENCRYPT_MODE_NONE,

+    DPOE_ENCRYPT_MODE_1DOWN,

+    DPOE_ENCRYPT_MODE_10DOWN,

+    DPOE_ENCRYPT_MODE_10BI

+} dpoe_encrypt_mode;

+

+/* generic OAM Variable format */

+typedef struct

+{

+    oam_var_branch branch;

+    uint16_t leaf;

+    uint8_t width;

+    uint8_t *value;

+} oam_var_generic;

+

+/** In the OAM protocol length 0 tlv is encoded as 0x80 (no error).

+ *  Length (1-127) are encoded with no change and 128 is encoded as 0.

+ *

+ * \param   width   TLV width field value

+ * \return  encoded value for the width field

+ * \ref     IEEE802.3-2008_section5, Ch. 57.6.2.1

+ */

+static inline uint8_t oam_var_tlv_length_encode(uint8_t width)

+{

+    if (width == 0)

+    {

+        return OAM_MAX_TLV_LENGTH; /* 0x00 equals 128 octets. */

+    }

+    else if (width == OAM_MAX_TLV_LENGTH)

+    {

+        return 0; /* 0x80 represents 'no error'. No 'Value' field. */

+    }

+    else

+    {

+        return width; /* represents the length of 'Value'. */

+    }

+}

+

+#endif /* OAM_DEFS_H_ */

diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam/Makefile b/bcm68620_release/release/host_reference/user_appl/epon_oam/Makefile
new file mode 100644
index 0000000..ff56b8f
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam/Makefile
@@ -0,0 +1,10 @@
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_user_appl_epon_oam
+    MOD_TYPE = lib
+	MOD_DEPS = utils dev_log common_epon_oam common_api
+    srcs = bcmolt_user_appl_epon_oam.c
+    ifeq ("$(OS_KERNEL)", "linux")
+	MOD_DEPS += dev_log_linux
+    endif
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.c b/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.c
new file mode 100644
index 0000000..558594c
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.c
@@ -0,0 +1,1436 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "bcmos_system.h"
+#include "bcmolt_math.h"
+#include "bcmolt_utils.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_msg.h"
+#include "bcmolt_msg_pack.h"
+#include "bcmolt_api.h"
+#include "bcmolt_model_types.h"
+#include "bcmolt_user_appl_epon_oam.h"
+#include "bcmos_hash_table.h"
+
+#define MAX_CONCURRENT_LINKS    2048
+
+typedef struct
+{
+    bcmolt_devid device_id;
+    bcmolt_epon_link_key key;
+    FILE *file;
+    void *buf;
+    uint16_t block_size;
+    uint16_t last_block;
+    uint16_t last_block_size;
+    uint8_t retries;
+    bcmos_bool done;
+    bcmos_timer timer;
+    uint32_t timeout_us;
+    uint32_t last_time;
+} epon_oam_dpoe_fw_upgrade_state;
+
+typedef struct
+{
+    bcmolt_devid device_id;
+    bcmolt_proxy_rx *rx;
+    bcmolt_user_appl_epon_oam_handle_rx_cb cb;
+} epon_oam_proxy_rx_data;
+
+typedef struct
+{
+    epon_oam_dpoe_fw_upgrade_state *state;
+} epon_oam_timeout_data;
+
+typedef union
+{
+    epon_oam_proxy_rx_data proxy_rx;
+    epon_oam_timeout_data timeout;
+} epon_oam_msg_data;
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    epon_oam_msg_data data;
+} epon_oam_msg;
+
+static bcmos_bool is_running = BCMOS_FALSE;
+static dev_log_id epon_oam_log[BCMTR_MAX_OLTS];
+static char mac_buf[MAC_STR_LEN];
+static bcmos_task epon_oam_task;
+static hash_table *dpoe_fw_upgrade_state;
+
+static void epon_oam_handle_os_msg(bcmos_module_id module_id, bcmos_msg *os_msg);
+
+#define EPON_OAM_LOG(level, device, link, fmt, args...)     \
+    BCM_LOG_CALLER_FMT(level, epon_oam_log[device], "%u-%s: "fmt, (link)->epon_ni, bcmos_mac_2_str(&(link)->mac_address, mac_buf), ##args)
+
+static bcmos_errno epon_oam_get_mac_da(bcmolt_devid device_id, bcmolt_epon_ni epon, bcmos_mac_address *mac)
+{
+    bcmos_errno err;
+    bcmolt_epon_ni_cfg pon_cfg;
+    bcmolt_epon_ni_key pon_key = { .epon_ni = epon };
+
+    /* retrieve the EPON NI MAC address */
+    BCMOLT_CFG_INIT(&pon_cfg, epon_ni, pon_key);
+    BCMOLT_CFG_PROP_GET(&pon_cfg, epon_ni, mac_address);
+    err = bcmolt_cfg_get(device_id, &pon_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        BCM_LOG(ERROR, epon_oam_log[device_id], "Failed to retrieve EPON NI MAC address!\n");
+    }
+    else
+    {
+        *mac = pon_cfg.data.mac_address;
+    }
+    return err;
+}
+
+bcmos_errno epon_oam_pack_frame(bcmolt_epon_oam_ethernet_frame *oam_frame, uint8_t **buffer, uint16_t *length)
+{
+    bcmolt_buf buf;
+
+    *length = MAX(bcmolt_epon_oam_ethernet_frame_get_packed_length(oam_frame), 60);
+    *buffer = bcmos_calloc(*length);
+    if (NULL == *buffer)
+    {
+        return BCM_ERR_NOMEM;
+    }
+    bcmolt_buf_init(&buf, *length, *buffer, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!bcmolt_epon_oam_ethernet_frame_pack(oam_frame, &buf))
+    {
+        bcmos_free(*buffer);
+        return BCM_ERR_INTERNAL;
+    }
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno epon_oam_pack_and_send(bcmolt_devid device_id,
+                                   bcmolt_epon_ni epon,
+                                   const bcmos_mac_address *mac,
+                                   bcmolt_epon_oam_ethernet_frame *oam_frame)
+{
+    bcmos_errno err;
+    bcmolt_epon_link_inject_frame inject_frame;
+    bcmolt_epon_link_key link_key = { .epon_ni = epon, .mac_address = *mac };
+    bcmolt_ethernet_frame_unmasked frame;
+
+    err = epon_oam_pack_frame(oam_frame, &frame.frame_octets.val, &frame.frame_octets.len);
+    if (BCM_ERR_OK != err)
+    {
+        EPON_OAM_LOG(ERROR, device_id, &link_key, "Failed to pack OAM frame: %s!\n", bcmos_strerror(err));
+        return err;
+    }
+
+    BCMOLT_PROXY_INIT(&inject_frame, epon_link, inject_frame, link_key);
+    BCMOLT_PROXY_PROP_SET(&inject_frame, epon_link, inject_frame, frame, frame);
+    err = bcmolt_proxy_send(device_id, &inject_frame.hdr);
+
+    bcmos_free(frame.frame_octets.val);
+
+    return err;
+}
+
+bcmolt_epon_oam_ethernet_frame *epon_oam_unpack(bcmolt_devid device_id, uint32_t len, uint8_t *bytes)
+{
+    bcmolt_buf buf;
+    uint32_t unpack_bytes = sizeof(bcmolt_epon_oam_ethernet_frame);
+    void *unpack_mem;
+    bcmolt_epon_oam_ethernet_frame *frame;
+    void *extra_mem;
+
+    /* We don't know how much space we'll need to unpack the OAM frame. Starting with the known fixed size, we scan the
+       frame to determine the exact space needed */
+    bcmolt_buf_init(&buf, len, bytes, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!bcmolt_epon_oam_ethernet_frame_scan(&buf, &unpack_bytes))
+    {
+        BCM_LOG(ERROR, epon_oam_log[device_id], "Failed to scan OAM frame\n");
+        return NULL;
+    }
+    unpack_mem = bcmos_calloc(unpack_bytes);
+    if (NULL == unpack_mem)
+    {
+        BCM_LOG(ERROR, epon_oam_log[device_id], "Failed to allocate %u bytes required to unpack\n", unpack_bytes);
+        return NULL;
+    }
+    else
+    {
+        BCM_LOG(DEBUG, epon_oam_log[device_id], "Successfully allocated %u bytes required to unpack\n", unpack_bytes);
+    }
+    /* Now that we have an appropriately sized buffer to unpack into, point the fixed structure to the start of the
+       buffer and direct the unpacker to store any additional data immediately after that */
+    frame = (bcmolt_epon_oam_ethernet_frame*)unpack_mem;
+    extra_mem = (void*)(frame + 1);
+
+    bcmolt_buf_init(&buf, len, bytes, BCMOLT_BUF_ENDIAN_FIXED);
+    if (!bcmolt_epon_oam_ethernet_frame_unpack(frame, &buf, &extra_mem))
+    {
+        BCM_LOG(ERROR, epon_oam_log[device_id], "Failed to unpack OAM frame\n");
+        bcmos_free(unpack_mem);
+        return NULL;
+    }
+
+    return frame;
+}
+
+static bcmos_errno epon_oam_make_dpoe_frame(bcmolt_devid device_id,
+                                            bcmolt_epon_ni epon,
+                                            bcmolt_epon_oam_ethernet_frame *oam_frame,
+                                            bcmolt_epon_oam_ethernet_protocol *protocol)
+{
+    bcmos_errno err;
+
+    oam_frame->da = slow_protocol_multicast_mac;
+    err = epon_oam_get_mac_da(device_id, epon, &oam_frame->sa);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    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 =
+        BCMOLT_EPON_OAM_OAM_FLAGS_LOCAL_STABLE | BCMOLT_EPON_OAM_OAM_FLAGS_REMOTE_STABLE;
+    protocol->u.slow_protocol.value.u.oam.content.code = BCMOLT_EPON_OAM_OAM_OPCODE_ORGANIZATION_SPECIFIC;
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.oui =
+        BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE;
+
+    return err;
+}
+
+static bcmos_errno epon_oam_send_os_msg(bcmos_msg_id type, epon_oam_msg_data *msg_data)
+{
+    bcmos_errno rc;
+    epon_oam_msg *msg = bcmos_calloc(sizeof(*msg));
+
+    if (msg == NULL)
+    {
+        BCM_LOG(ERROR, epon_oam_log[msg_data->proxy_rx.device_id], "OS msg calloc failed\n");
+        return BCM_ERR_NOMEM;
+    }
+
+    msg->os_msg.type = type;
+    msg->os_msg.handler = epon_oam_handle_os_msg;
+    msg->data = *msg_data;
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_EPON_OAM, &msg->os_msg, 0);
+    if (BCM_ERR_OK != rc)
+    {
+        BCM_LOG(ERROR, epon_oam_log[msg_data->proxy_rx.device_id], "OS msg send failed\n");
+    }
+    return rc;
+}
+
+bcmos_errno epon_oam_dpoe_get_critical_info(bcmolt_devid device_id,
+                                            bcmolt_epon_ni epon,
+                                            bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_descriptor vars[3] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_GET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.get_request.vars_count =
+        3;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.get_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[0].u.extended_attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_ONU_ID;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[1].u.extended_attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_MAX_LOGICAL_LINKS;
+    vars[2].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[2].u.end.unknown_count = 0;
+    vars[2].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_set_oam_rate(bcmolt_devid device_id,
+                                       bcmolt_epon_ni epon,
+                                       bcmos_mac_address *mac,
+                                       uint8_t min,
+                                       uint8_t max)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[2] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        2;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[0].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_OAM_RATE;
+    vars[0].u.extended_attribute.attribute.u.oam_rate.minimum_oam_rate = min;
+    vars[0].u.extended_attribute.attribute.u.oam_rate.maximum_oam_rate = max;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[1].u.end.unknown_count = 0;
+    vars[1].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_set_report_thresholds(bcmolt_devid device_id,
+                                                bcmolt_epon_ni epon,
+                                                bcmos_mac_address *mac,
+                                                uint8_t report_values_per_queue_set,
+                                                bcmolt_epon_oam_queue_sets queue_sets,
+                                                uint8_t num_queue_sets)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[2] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        2;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[0].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_REPORT_THRESHOLDS;
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.number_of_queue_sets = num_queue_sets;
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.report_values_per_queue_set =
+        report_values_per_queue_set;
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.queue_set0 = queue_sets[0];
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.queue_set1 = queue_sets[1];
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.queue_set2 = queue_sets[2];
+    vars[0].u.extended_attribute.attribute.u.report_thresholds.queue_set3 = queue_sets[3];
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[1].u.end.unknown_count = 0;
+    vars[1].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_set_encryption(
+    bcmolt_devid device_id,
+    bcmolt_epon_ni epon,
+    bcmos_mac_address *mac,
+    bcmolt_epon_oam_dpoe_encryption_mode enc_mode,
+    uint16_t period)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[3] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        3;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[0].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_ENCRYPT_MODE;
+    vars[0].u.extended_attribute.attribute.u.encrypt_mode.encryption_method = enc_mode;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[1].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_KEY_EXPIRY_TIME;
+    vars[1].u.extended_attribute.attribute.u.key_expiry_time.time = period;
+    vars[2].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[2].u.end.unknown_count = 0;
+    vars[2].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_bool epon_oam_is_dpoe(bcmolt_epon_oam_ethernet_frame *oam)
+{
+    return ((oam->protocols_count > 0) &&
+        (BCMOLT_EPON_OAM_PROTOCOL_TYPE_SLOW_PROTOCOL == oam->protocols[0].ethertype) &&
+        (BCMOLT_EPON_OAM_SLOW_PROTOCOL_SUBTYPE_OAM == oam->protocols[0].u.slow_protocol.value.subtype) &&
+        (BCMOLT_EPON_OAM_OAM_OPCODE_ORGANIZATION_SPECIFIC ==
+         oam->protocols[0].u.slow_protocol.value.u.oam.content.code) &&
+        (BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE ==
+         oam->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.oui));
+}
+
+bcmos_bool epon_oam_is_dpoe_encrypt_response(bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe)
+{
+    return (BCMOLT_EPON_OAM_DPOE_OPCODE_SET_RESPONSE == dpoe->op) &&
+        (dpoe->u.set_response.vars_count > 2) &&
+        (BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE == dpoe->u.set_response.vars[0].branch) &&
+        (BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_ENCRYPT_MODE ==
+         dpoe->u.set_response.vars[0].u.extended_attribute.attribute.leaf) &&
+        (BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE == dpoe->u.set_response.vars[1].branch) &&
+        (BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_KEY_EXPIRY_TIME ==
+         dpoe->u.set_response.vars[1].u.extended_attribute.attribute.leaf);
+}
+
+static bcmos_errno epon_oam_dpoe_clear_ingress_rules_make_frame(bcmolt_devid device_id,
+                                                                bcmolt_epon_ni epon,
+                                                                bcmos_mac_address *mac,
+                                                                bcmolt_epon_oam_ethernet_frame *oam_frame,
+                                                                bcmolt_epon_oam_ethernet_protocol *protocol,
+                                                                bcmolt_epon_oam_dpoe_var_container_base *vars)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    err = epon_oam_make_dpoe_frame(device_id, epon, oam_frame, protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        3;
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_OBJECT;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ACTION;
+    vars[1].u.extended_action.action.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ACTION_CLEAR_INGRESS_RULES;
+    vars[2].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[2].u.end.unknown_count = 0;
+    vars[2].u.end.unknown = NULL;
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno epon_oam_dpoe_clear_ingress_rules_network_pon(bcmolt_devid device_id,
+                                                          bcmolt_epon_ni epon,
+                                                          bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[3] = {};
+    uint8_t port = 0;
+
+    epon_oam_dpoe_clear_ingress_rules_make_frame(device_id, epon, mac, &oam_frame, &protocol, vars);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    vars[0].u.object.object_context.object_type = BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_NETWORK_PON;
+    vars[0].u.object.object_context.u.network_pon.pon_count = 1;
+    vars[0].u.object.object_context.u.network_pon.pon = &port;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_clear_ingress_rules_user_port(bcmolt_devid device_id,
+                                                        bcmolt_epon_ni epon,
+                                                        bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[3] = {};
+    uint8_t port = 0;
+
+    epon_oam_dpoe_clear_ingress_rules_make_frame(device_id, epon, mac, &oam_frame, &protocol, vars);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    vars[0].u.object.object_context.object_type = BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_USER_PORT;
+    vars[0].u.object.object_context.u.user_port.port_count = 1;
+    vars[0].u.object.object_context.u.user_port.port = &port;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_clear_ingress_rules(bcmolt_devid device_id,
+                                              bcmolt_epon_ni epon,
+                                              bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    err = epon_oam_dpoe_clear_ingress_rules_network_pon(device_id, epon, mac);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    return epon_oam_dpoe_clear_ingress_rules_user_port(device_id, epon, mac);
+}
+
+bcmos_errno epon_oam_dpoe_set_basic_queue_config(bcmolt_devid device_id,
+                                                 bcmolt_epon_ni epon,
+                                                 bcmos_mac_address *mac,
+                                                 uint8_t up_queue_size,
+                                                 uint8_t dn_queue_size)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[2] = {};
+    bcmolt_epon_oam_dpoe_queue_set up_queue_set;
+    bcmolt_epon_oam_dpoe_queue_set dn_queue_set;
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        2;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[0].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_QUEUE_CONFIG;
+    vars[0].u.extended_attribute.attribute.u.queue_config.number_of_links = 1;
+    vars[0].u.extended_attribute.attribute.u.queue_config.link_configuration = &up_queue_set;
+    up_queue_set.queue_count = 1;
+    up_queue_set.queue_sizes = &up_queue_size;
+    vars[0].u.extended_attribute.attribute.u.queue_config.number_of_ports = 1;
+    vars[0].u.extended_attribute.attribute.u.queue_config.port_configuration = &dn_queue_set;
+    dn_queue_set.queue_count = 1;
+    dn_queue_set.queue_sizes = &dn_queue_size;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[1].u.end.unknown_count = 0;
+    vars[1].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+static bcmos_errno epon_oam_dpoe_add_ingress_rules_make_frame(bcmolt_devid device_id,
+                                                              bcmolt_epon_ni epon,
+                                                              bcmos_mac_address *mac,
+                                                              bcmolt_epon_oam_ethernet_frame *oam_frame,
+                                                              bcmolt_epon_oam_ethernet_protocol *protocol,
+                                                              bcmolt_epon_oam_dpoe_var_container_base *vars,
+                                                              dpoe_rule_vlan_mode vlan_mode,
+                                                              uint8_t vlan_tag[4])
+{
+    static const uint32_t var_count[DPOE_RULE_VLAN_MODE_COUNT] =
+    {
+        [DPOE_RULE_VLAN_MODE_NONE] = 8,
+        [DPOE_RULE_VLAN_MODE_ADD] = 10,
+        [DPOE_RULE_VLAN_MODE_REMOVE] = 9
+    };
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    err = epon_oam_make_dpoe_frame(device_id, epon, oam_frame, protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        var_count[vlan_mode];
+    protocol->u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_OBJECT;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[1].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+    vars[1].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_HEADER;
+    vars[1].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.header.precedence = 10;
+    vars[2].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[2].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_CLAUSE;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.field_code.code =
+        BCMOLT_EPON_OAM_DPOE_FIELD_CODE_L2DA;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.field_code.instance = 0;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.msb_mask = 0;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.lsb_mask = 0;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.operator =
+        BCMOLT_EPON_OAM_RULE_OPERATOR_ALWAYS_MATCH;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.match_value_length = 0;
+    vars[2].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.clause.match_value = NULL;
+    vars[3].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[3].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_RESULT;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.result =
+        BCMOLT_EPON_OAM_DPOE_RESULT_QUEUE;
+    vars[4].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[4].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+    vars[4].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_RESULT;
+    vars[4].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.result =
+        BCMOLT_EPON_OAM_DPOE_RESULT_FORWARD;
+
+    switch (vlan_mode)
+    {
+        case DPOE_RULE_VLAN_MODE_NONE:
+            break; /* nothing to do */
+        case DPOE_RULE_VLAN_MODE_ADD:
+            vars[5].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+            vars[5].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_RESULT;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.result =
+                BCMOLT_EPON_OAM_DPOE_RESULT_INSERT;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.insert.field.field.code =
+                BCMOLT_EPON_OAM_DPOE_FIELD_CODE_CVLAN;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.insert.field.field.instance = 0;
+            vars[6].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+            vars[6].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_RESULT;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.result =
+                BCMOLT_EPON_OAM_DPOE_RESULT_SET;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.set.field.field.code =
+                BCMOLT_EPON_OAM_DPOE_FIELD_CODE_CVLAN;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.set.field.field.instance = 0;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.set.value_count = 4;
+            vars[6].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.set.value = vlan_tag;
+            break;
+        case DPOE_RULE_VLAN_MODE_REMOVE:
+            vars[5].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+            vars[5].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_RESULT;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.result =
+                BCMOLT_EPON_OAM_DPOE_RESULT_DELETE;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.delete.field.field.code =
+                BCMOLT_EPON_OAM_DPOE_FIELD_CODE_CVLAN;
+            vars[5].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.delete.field.field.instance = 0;
+            break;
+        default:
+            return BCM_ERR_PARM;
+    }
+
+    vars[var_count[vlan_mode]-3].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE;
+    vars[var_count[vlan_mode]-3].u.extended_attribute.attribute.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_PORT_INGRESS_RULE;
+    vars[var_count[vlan_mode]-3].u.extended_attribute.attribute.u.port_ingress_rule.rule.subtype = BCMOLT_EPON_OAM_RULE_TYPE_TERMINATOR;
+    vars[var_count[vlan_mode]-2].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ACTION;
+    vars[var_count[vlan_mode]-2].u.extended_action.action.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ACTION_ADD_INGRESS_RULES;
+    vars[var_count[vlan_mode]-1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[var_count[vlan_mode]-1].u.end.unknown_count = 0;
+    vars[var_count[vlan_mode]-1].u.end.unknown = NULL;
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules_network_pon(bcmolt_devid device_id,
+                                                        bcmolt_epon_ni epon,
+                                                        bcmos_mac_address *mac,
+                                                        dpoe_rule_vlan_mode vlan_mode,
+                                                        uint8_t vlan_tag[4])
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[10] = {};
+    uint8_t port = 0;
+
+    err = epon_oam_dpoe_add_ingress_rules_make_frame(device_id,
+                                                     epon,
+                                                     mac,
+                                                     &oam_frame,
+                                                     &protocol,
+                                                     vars,
+                                                     vlan_mode,
+                                                     vlan_tag);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    vars[0].u.object.object_context.object_type = BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_NETWORK_PON;
+    vars[0].u.object.object_context.u.network_pon.pon_count = 1;
+    vars[0].u.object.object_context.u.network_pon.pon = &port;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.type =
+        BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_USER_PORT;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.instance = 0;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.queue = 0;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules_user_port(bcmolt_devid device_id,
+                                                      bcmolt_epon_ni epon,
+                                                      bcmos_mac_address *mac,
+                                                      dpoe_rule_vlan_mode vlan_mode,
+                                                      uint8_t vlan_tag[4])
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[10] = {};
+    uint8_t port = 0;
+
+    err = epon_oam_dpoe_add_ingress_rules_make_frame(device_id,
+                                                     epon,
+                                                     mac,
+                                                     &oam_frame,
+                                                     &protocol,
+                                                     vars,
+                                                     vlan_mode,
+                                                     vlan_tag);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    vars[0].u.object.object_context.object_type = BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_USER_PORT;
+    vars[0].u.object.object_context.u.user_port.port_count = 1;
+    vars[0].u.object.object_context.u.user_port.port = &port;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.type =
+        BCMOLT_EPON_OAM_DPOE_OBJECT_TYPE_LINK;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.instance = 0;
+    vars[3].u.extended_attribute.attribute.u.port_ingress_rule.rule.u.result.result.u.queue.queue.queue = 0;
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules(bcmolt_devid device_id,
+                                            bcmolt_epon_ni epon,
+                                            bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    err = epon_oam_dpoe_add_ingress_rules_network_pon(device_id, epon, mac, DPOE_RULE_VLAN_MODE_NONE, NULL);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    return epon_oam_dpoe_add_ingress_rules_user_port(device_id, epon, mac, DPOE_RULE_VLAN_MODE_NONE, NULL);
+}
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules_with_vlan(bcmolt_devid device_id,
+                                                      bcmolt_epon_ni epon,
+                                                      bcmos_mac_address *mac,
+                                                      uint8_t vlan_tag[4])
+{
+    static uint8_t example_vlan[4] = { 0x81, 0x00, 0x12, 0x34 };
+    bcmos_errno err = BCM_ERR_OK;
+
+    err = epon_oam_dpoe_add_ingress_rules_network_pon(device_id, epon, mac, DPOE_RULE_VLAN_MODE_REMOVE, NULL);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+
+    if (NULL == vlan_tag)
+    {
+        vlan_tag = example_vlan;
+    }
+    return epon_oam_dpoe_add_ingress_rules_user_port(device_id, epon, mac, DPOE_RULE_VLAN_MODE_ADD, vlan_tag);
+}
+
+static bcmos_errno _epon_oam_dpoe_user_traffic_set_action(bcmolt_devid device_id,
+                                                          bcmolt_epon_ni epon,
+                                                          bcmos_mac_address *mac,
+                                                          bcmolt_epon_oam_dpoe_leaf_action action)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[2] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        2;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ACTION;
+    vars[0].u.extended_action.action.leaf = action;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[1].u.end.unknown_count = 0;
+    vars[1].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+bcmos_errno epon_oam_dpoe_disable_user_traffic(bcmolt_devid device_id,
+                                               bcmolt_epon_ni epon,
+                                               bcmos_mac_address *mac)
+{
+    return _epon_oam_dpoe_user_traffic_set_action(device_id,
+                                                  epon,
+                                                  mac,
+                                                  BCMOLT_EPON_OAM_DPOE_LEAF_ACTION_DISABLE_USER_TRAFFIC);
+}
+
+bcmos_errno epon_oam_dpoe_enable_user_traffic(bcmolt_devid device_id,
+                                              bcmolt_epon_ni epon,
+                                              bcmos_mac_address *mac)
+{
+    return _epon_oam_dpoe_user_traffic_set_action(device_id,
+                                                  epon,
+                                                  mac,
+                                                  BCMOLT_EPON_OAM_DPOE_LEAF_ACTION_ENABLE_USER_TRAFFIC);
+}
+
+static epon_oam_dpoe_fw_upgrade_state *epon_oam_dpoe_fw_upgrade_state_add(const bcmolt_epon_link_key *key)
+{
+    epon_oam_dpoe_fw_upgrade_state state = {};
+    return hash_table_put(dpoe_fw_upgrade_state, (const uint8_t*)key, &state);
+}
+
+static epon_oam_dpoe_fw_upgrade_state* epon_oam_dpoe_fw_upgrade_state_get(const bcmolt_epon_link_key *key)
+{
+    return hash_table_get(dpoe_fw_upgrade_state, (const uint8_t*)key);
+}
+
+static void epon_oam_dpoe_fw_upgrade_state_release(epon_oam_dpoe_fw_upgrade_state *state)
+{
+    if (NULL == state)
+    {
+        BCM_LOG(ERROR, epon_oam_log[0], "Tried to release NULL state\n");
+        return;
+    }
+
+    if (NULL != state->file)
+    {
+        fclose(state->file);
+    }
+    bcmos_free(state->buf);
+    bcmos_timer_destroy(&state->timer);
+    if (!hash_table_remove(dpoe_fw_upgrade_state, (const uint8_t*)&state->key))
+    {
+        BCM_LOG(ERROR, epon_oam_log[state->device_id], "Unable to remove from hash table\n");
+    }
+}
+
+static void epon_oam_dpoe_send_block(bcmolt_devid device_id,
+                                     const bcmolt_epon_link_key *key,
+                                     uint16_t block,
+                                     uint8_t *data,
+                                     uint16_t length)
+{
+    bcmos_errno err;
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+
+    err = epon_oam_make_dpoe_frame(device_id, key->epon_ni, &oam_frame, &protocol);
+    BCMOS_RETURN_ON_ERROR(err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_FILE_TRANSFER;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.opcode = BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_DATA;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_data.block = block;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_data.length = length;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_data.data = data;
+
+    if (BCM_ERR_OK != epon_oam_pack_and_send(device_id, key->epon_ni, &key->mac_address, &oam_frame))
+    {
+        EPON_OAM_LOG(ERROR, device_id, key, "Failed to send fw upgrade block %u\n", block);
+    }
+    else
+    {
+        EPON_OAM_LOG(DEBUG, device_id, key, "Sent block %u\n", block);
+    }
+}
+
+static void epon_oam_dpoe_send_ack(bcmolt_devid device_id,
+                                   const bcmolt_epon_link_key *key,
+                                   bcmolt_epon_oam_dpoe_file_transfer_error error)
+{
+    bcmos_errno err;
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+
+    err = epon_oam_make_dpoe_frame(device_id, key->epon_ni, &oam_frame, &protocol);
+    BCMOS_RETURN_ON_ERROR(err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_FILE_TRANSFER;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.opcode = BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_ACK;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_ack.block = 0;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_ack.error = error;
+
+    if (BCM_ERR_OK != epon_oam_pack_and_send(device_id, key->epon_ni, &key->mac_address, &oam_frame))
+    {
+        EPON_OAM_LOG(ERROR, device_id, key, "Failed to send fw upgrade ack\n");
+    }
+    else
+    {
+        EPON_OAM_LOG(DEBUG, device_id, key, "Sent ack: error %u\n", error);
+    }
+}
+
+static void epon_oam_dpoe_reset_timeout(epon_oam_dpoe_fw_upgrade_state *state)
+{
+    EPON_OAM_LOG(DEBUG, state->device_id, &state->key, "ONU took %u us to respond\n", bcmos_timestamp() - state->last_time);
+    state->last_time = bcmos_timestamp();
+    bcmos_timer_start(&state->timer, state->timeout_us);
+}
+
+static void epon_oam_dpoe_handle_ack(bcmolt_devid device_id,
+                                     const bcmolt_epon_link_key *key,
+                                     const bcmolt_epon_oam_dpoe_file_transfer_base *ft,
+                                     bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    epon_oam_dpoe_fw_upgrade_state *state;
+    int block_size;
+
+    state = epon_oam_dpoe_fw_upgrade_state_get(key);
+
+    if (NULL == state)
+    {
+        EPON_OAM_LOG(DEBUG, device_id, key, "Rx unexpected ack\n");
+        return;
+    }
+
+    if (state->done)
+    {
+        if (ft->u.file_ack.block != 0)
+        {
+            EPON_OAM_LOG(ERROR, device_id, key, "Final ack contained non-zero block: %u\n", ft->u.file_ack.block);
+        }
+        switch (ft->u.file_ack.error)
+        {
+            case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_OK:
+                EPON_OAM_LOG(INFO, device_id, key, "Upgrade successful\n");
+                break;
+            default:
+                EPON_OAM_LOG(ERROR, device_id, key, "Upgrade failed with error %u\n", ft->u.file_ack.error);
+                break;
+        }
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+        return;
+    }
+
+    state->last_block++;
+    if ((state->last_block - 1) == ft->u.file_ack.block)
+    { /* retry last block */
+        state->last_block--;
+        state->retries++;
+        if (state->retries > 3)
+        {
+            EPON_OAM_LOG(ERROR, device_id, key, "Exceeded retry limit on block %u\n", ft->u.file_ack.block);
+            epon_oam_dpoe_send_ack(device_id, key, BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_TIMEOUT);
+            epon_oam_dpoe_fw_upgrade_state_release(state);
+        }
+        else
+        {
+            epon_oam_dpoe_send_block(device_id, key, ft->u.file_ack.block, state->buf, state->last_block_size);
+            epon_oam_dpoe_reset_timeout(state);
+        }
+    }
+    else if (state->last_block == ft->u.file_ack.block)
+    { /* send next block */
+        state->retries = 0;
+        block_size = fread(state->buf, 1, state->block_size, state->file);
+        if (block_size > 0)
+        {
+            epon_oam_dpoe_send_block(device_id, key, ft->u.file_ack.block, state->buf, block_size);
+        }
+        else
+        {
+            state->done = BCMOS_TRUE;
+            epon_oam_dpoe_send_ack(device_id, key, BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_OK);
+        }
+        state->last_block_size = block_size;
+        epon_oam_dpoe_reset_timeout(state);
+    }
+    else
+    { /* unexpected block */
+        EPON_OAM_LOG(ERROR, device_id, key, "Unexpected block requested: %u (expecting %u)\n",
+                     ft->u.file_ack.block, state->last_block + 1);
+        epon_oam_dpoe_send_ack(device_id, key, BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_BAD_BLOCK);
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+    }
+}
+
+static bcmos_timer_rc epon_oam_dpoe_fw_upgrade_timeout(bcmos_timer *timer, long data)
+{
+    epon_oam_msg_data msg_data;
+
+    msg_data.timeout.state = (epon_oam_dpoe_fw_upgrade_state*)data;
+    epon_oam_send_os_msg(BCMOS_MSG_ID_EPON_OAM_TIMEOUT, &msg_data);
+
+    return BCMOS_TIMER_OK;
+}
+
+bcmos_errno epon_oam_dpoe_start_upgrade(bcmolt_devid device_id,
+                                        bcmolt_epon_ni epon,
+                                        bcmos_mac_address *mac,
+                                        const char *fw_file,
+                                        uint16_t block_size,
+                                        uint8_t timeout_sec)
+{
+    bcmos_errno err = BCM_ERR_OK;
+    bcmolt_epon_link_key link_key = { .epon_ni = epon, .mac_address = *mac };
+    bcmos_timer_parm tp =
+    {
+        .name = "fw_upgrade_timer",
+        .owner = BCMOS_MODULE_ID_USER_APPL_EPON_OAM,
+        .periodic = BCMOS_FALSE,
+        .handler = epon_oam_dpoe_fw_upgrade_timeout
+    };
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    epon_oam_dpoe_fw_upgrade_state *state;
+
+    if (NULL != epon_oam_dpoe_fw_upgrade_state_get(&link_key))
+    {
+        return BCM_ERR_ALREADY;
+    }
+    state = epon_oam_dpoe_fw_upgrade_state_add(&link_key);
+    if (NULL == state)
+    {
+        return BCM_ERR_NOMEM;
+    }
+
+    state->device_id = device_id;
+    state->key = link_key;
+    state->file = fopen(fw_file, "rb");
+    if (NULL == state->file)
+    {
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+        return BCM_ERR_IO;
+    }
+    state->block_size = block_size;
+    state->buf = bcmos_calloc(block_size);
+    if (NULL == state->buf)
+    {
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+        return BCM_ERR_NOMEM;
+    }
+    state->last_block = 0xffff;/*-1*/
+    state->retries = 0;
+    state->done = BCMOS_FALSE;
+    tp.data = (long)state;
+    err = bcmos_timer_create(&state->timer, &tp);
+    if (BCM_ERR_OK != err)
+    {
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+        return err;
+    }
+    state->timeout_us = timeout_sec * 1000 * 1000;
+    state->last_time = bcmos_timestamp();
+    bcmos_timer_start(&state->timer, state->timeout_us);
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    if (BCM_ERR_OK != err)
+    {
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+        return err;
+    }
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_FILE_TRANSFER;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.opcode = BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_WRITE;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_write.filename_count = 0;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.file_transfer.file_transfer.u.file_write.filename = NULL;
+
+    err = epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+    if (BCM_ERR_OK != err)
+    {
+        epon_oam_dpoe_fw_upgrade_state_release(state);
+    }
+    return err;
+}
+
+bcmos_errno epon_oam_dpoe_reset_onu(bcmolt_devid device_id, bcmolt_epon_ni epon, bcmos_mac_address *mac)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* OAM stuff */
+    bcmolt_epon_oam_ethernet_frame oam_frame;
+    bcmolt_epon_oam_ethernet_protocol protocol;
+    bcmolt_epon_oam_dpoe_var_container_base vars[2] = {};
+
+    err = epon_oam_make_dpoe_frame(device_id, epon, &oam_frame, &protocol);
+    BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != err, err);
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.op =
+        BCMOLT_EPON_OAM_DPOE_OPCODE_SET_REQUEST;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars_count =
+        2;
+    protocol.u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value.u.set_request.vars = vars;
+    vars[0].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ACTION;
+    vars[0].u.extended_action.action.leaf = BCMOLT_EPON_OAM_DPOE_LEAF_ACTION_RESET_ONU;
+    vars[1].branch = BCMOLT_EPON_OAM_DPOE_BRANCH_END;
+    vars[1].u.end.unknown_count = 0;
+    vars[1].u.end.unknown = NULL;
+
+    return epon_oam_pack_and_send(device_id, epon, mac, &oam_frame);
+}
+
+static void epon_oam_handle_event(bcmolt_devid device_id,
+                                  bcmolt_epon_oam_oam_pdu_content *content,
+                                  const bcmolt_epon_link_key *key,
+                                  bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    for (uint32_t i = 0; i < content->u.event_notification.tlvs_count; i++)
+    {
+        if (BCMOLT_EPON_OAM_LINK_EVENT_TYPE_ORGANIZATION_SPECIFIC == content->u.event_notification.tlvs[i].type)
+        {
+            switch (content->u.event_notification.tlvs[i].u.organization_specific.value.oui)
+            {
+                case BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE:
+                    if (NULL != cb)
+                    {
+                        cb(device_id, key->epon_ni, &key->mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_EVENT, BCM_ERR_OK);
+                    }
+                    EPON_OAM_LOG(INFO, device_id, key, "Received DPoE event %u\n",
+                        content->u.event_notification.tlvs[i].u.organization_specific.value.u.dpoe.value.event_code);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
+
+static void epon_oam_handle_dpoe_get_response(bcmolt_devid device_id,
+                                              bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe,
+                                              const bcmolt_epon_link_key *key,
+                                              bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    EPON_OAM_LOG(DEBUG, device_id, key, "Rx %u vars\n", dpoe->u.get_response.vars_count);
+    for (uint32_t i = 0; i < dpoe->u.get_response.vars_count; i++)
+    {
+        EPON_OAM_LOG(DEBUG, device_id, key, " %u: branch %u\n", i, dpoe->u.get_response.vars[i].branch);
+        if (BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE == dpoe->u.get_response.vars[i].branch)
+        {
+            if (dpoe->u.get_response.vars[i].u.extended_attribute.attribute.width >
+                BCMOLT_EPON_OAM_DPOE_ERROR_CODE_NO_ERROR)
+            {
+                EPON_OAM_LOG(ERROR, device_id, key, "Error 0x%x getting DPoE extended attribute 0x%04x\n",
+                             dpoe->u.get_response.vars[i].u.extended_attribute.attribute.width,
+                             dpoe->u.get_response.vars[i].u.extended_attribute.attribute.leaf);
+                rc = BCM_ERR_ONU_ERR_RESP;
+            }
+            switch (dpoe->u.get_response.vars[i].u.extended_attribute.attribute.leaf)
+            {
+                case BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_ONU_ID:
+                    EPON_OAM_LOG(INFO, device_id, key, "Got DPoE ONUID %02x%02x%02x%02x%02x%02x\n",
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[0],
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[1],
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[2],
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[3],
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[4],
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.onu_id.id.u8[5]);
+                    break;
+                case BCMOLT_EPON_OAM_DPOE_LEAF_ATTRIBUTE_MAX_LOGICAL_LINKS:
+                    EPON_OAM_LOG(INFO, device_id, key, "Got DPoE Max Logical Links BiDir %u, DownOnly %u\n",
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.max_logical_links.bidirectional,
+                                 dpoe->u.get_response.vars[i].u.extended_attribute.attribute.u.max_logical_links.downstream_only);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    if (NULL != cb)
+    {
+        cb(device_id, key->epon_ni, &key->mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_GET_RESPONSE, rc);
+    }
+}
+
+static void epon_oam_handle_dpoe_set_response(bcmolt_devid device_id,
+                                              bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe,
+                                              const bcmolt_epon_link_key *key,
+                                              bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    bcmos_errno rc = BCM_ERR_OK;
+
+    EPON_OAM_LOG(DEBUG, device_id, key, "Rx %u vars\n", dpoe->u.set_response.vars_count);
+    for (uint32_t i = 0; i < dpoe->u.set_response.vars_count; i++)
+    {
+        EPON_OAM_LOG(DEBUG, device_id, key, " %u: branch %u\n", i, dpoe->u.set_response.vars[i].branch);
+        switch (dpoe->u.set_response.vars[i].branch)
+        {
+            case BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ATTRIBUTE:
+                if (dpoe->u.set_response.vars[i].u.extended_attribute.attribute.width >
+                    BCMOLT_EPON_OAM_DPOE_ERROR_CODE_NO_ERROR)
+                {
+                    EPON_OAM_LOG(ERROR, device_id, key, "Error 0x%x setting DPoE extended attribute 0x%04x\n",
+                                 dpoe->u.set_response.vars[i].u.extended_attribute.attribute.width,
+                                 dpoe->u.set_response.vars[i].u.extended_attribute.attribute.leaf);
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                }
+                else
+                {
+                    EPON_OAM_LOG(INFO, device_id, key, "Successfully set DPoE extended attribute 0x%04x\n",
+                                 dpoe->u.set_response.vars[i].u.extended_attribute.attribute.leaf);
+                }
+                break;
+            case BCMOLT_EPON_OAM_DPOE_BRANCH_EXTENDED_ACTION:
+                if (dpoe->u.set_response.vars[i].u.extended_action.action.width >
+                    BCMOLT_EPON_OAM_DPOE_ERROR_CODE_NO_ERROR)
+                {
+                    EPON_OAM_LOG(ERROR, device_id, key, "Error 0x%x performing DPoE extended action 0x%04x\n",
+                                 dpoe->u.set_response.vars[i].u.extended_action.action.width,
+                                 dpoe->u.set_response.vars[i].u.extended_action.action.leaf);
+                    rc = BCM_ERR_ONU_ERR_RESP;
+                }
+                else
+                {
+                    EPON_OAM_LOG(INFO, device_id, key, "Successfully performed DPoE extended action 0x%04x\n",
+                                 dpoe->u.set_response.vars[i].u.extended_action.action.leaf);
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    if (NULL != cb)
+    {
+        cb(device_id, key->epon_ni, &key->mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_SET_RESPONSE, rc);
+    }
+}
+
+static void epon_oam_handle_dpoe_file_transfer(bcmolt_devid device_id,
+                                               bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe,
+                                               const bcmolt_epon_link_key *key,
+                                               bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    EPON_OAM_LOG(DEBUG, device_id, key, "Got DPoE File Transfer: %u\n", dpoe->u.file_transfer.file_transfer.opcode);
+    switch (dpoe->u.file_transfer.file_transfer.opcode)
+    {
+        case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_ACK:
+            EPON_OAM_LOG(DEBUG, device_id, key, "Rx ack block %u error %u\n",
+                         dpoe->u.file_transfer.file_transfer.u.file_ack.block,
+                         dpoe->u.file_transfer.file_transfer.u.file_ack.error);
+            switch (dpoe->u.file_transfer.file_transfer.u.file_ack.error)
+            {
+                case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_OK:
+                    epon_oam_dpoe_handle_ack(device_id, key, &dpoe->u.file_transfer.file_transfer, cb);
+                    break;
+                default:
+                    EPON_OAM_LOG(ERROR, device_id, key, "File transfer failed on block %u with error %u\n",
+                                 dpoe->u.file_transfer.file_transfer.u.file_ack.block,
+                                 dpoe->u.file_transfer.file_transfer.u.file_ack.error);
+                    break;
+            }
+            break;
+        case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_DATA:
+            EPON_OAM_LOG(DEBUG, device_id, key, "Rx data\n");
+            break;
+        case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_READ:
+            EPON_OAM_LOG(ERROR, device_id, key, "Rx read request\n");
+            break;
+        case BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_OPCODE_FILE_WRITE:
+            EPON_OAM_LOG(ERROR, device_id, key, "Rx write request\n");
+            break;
+        default:
+            EPON_OAM_LOG(ERROR, device_id, key, "Unknown DPoE File Transfer opcode: %u\n",
+                         dpoe->u.file_transfer.file_transfer.opcode);
+            break;
+    }
+}
+
+static void epon_oam_handle_dpoe(bcmolt_devid device_id,
+                                 bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe,
+                                 const bcmolt_epon_link_key *key,
+                                 bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    EPON_OAM_LOG(DEBUG, device_id, key, "Rx DPoE opcode %u\n", dpoe->op);
+    switch (dpoe->op)
+    {
+        case BCMOLT_EPON_OAM_DPOE_OPCODE_GET_RESPONSE:
+            epon_oam_handle_dpoe_get_response(device_id, dpoe, key, cb);
+            break;
+        case BCMOLT_EPON_OAM_DPOE_OPCODE_SET_RESPONSE:
+            epon_oam_handle_dpoe_set_response(device_id, dpoe, key, cb);
+            break;
+        case BCMOLT_EPON_OAM_DPOE_OPCODE_FILE_TRANSFER:
+            epon_oam_handle_dpoe_file_transfer(device_id, dpoe, key, cb);
+            break;
+        default:
+            break;
+    }
+}
+
+static void bcmolt_user_appl_epon_oam_handle_proxy_rx(epon_oam_proxy_rx_data *data)
+{
+    const bcmolt_epon_link_frame_captured *cap = (bcmolt_epon_link_frame_captured*)data->rx;
+    bcmolt_epon_oam_ethernet_frame *oam_frame;
+
+    oam_frame = epon_oam_unpack(data->device_id, cap->data.frame.len, cap->data.frame.val);
+    if (NULL == oam_frame)
+    {
+        return;
+    }
+
+    if (oam_frame->protocols_count == 0)
+    {
+        EPON_OAM_LOG(ERROR, data->device_id, &cap->key, "No protocols in OAM frame\n");
+        bcmos_free(oam_frame);
+        return;
+    }
+
+    EPON_OAM_LOG(DEBUG, data->device_id, &cap->key, "Rx ethertype %04x subtype %02x\n",
+                 oam_frame->protocols[0].ethertype, oam_frame->protocols[0].u.slow_protocol.value.subtype);
+    if ((BCMOLT_EPON_OAM_PROTOCOL_TYPE_SLOW_PROTOCOL == oam_frame->protocols[0].ethertype) &&
+        (BCMOLT_EPON_OAM_SLOW_PROTOCOL_SUBTYPE_OAM == oam_frame->protocols[0].u.slow_protocol.value.subtype))
+    {
+        if (oam_frame->protocols[0].u.slow_protocol.value.u.oam.flags & BCMOLT_EPON_OAM_OAM_FLAGS_LINK_FAULT)
+        {
+            if (NULL != data->cb)
+            {
+                data->cb(data->device_id, cap->key.epon_ni, &cap->key.mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_LINK_FAULT, BCM_ERR_OK);
+            }
+            EPON_OAM_LOG(ERROR, data->device_id, &cap->key, "Received OAM link fault\n");
+        }
+        if (oam_frame->protocols[0].u.slow_protocol.value.u.oam.flags & BCMOLT_EPON_OAM_OAM_FLAGS_DYING_GASP)
+        {
+            if (NULL != data->cb)
+            {
+                data->cb(data->device_id, cap->key.epon_ni, &cap->key.mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_DYING_GASP, BCM_ERR_OK);
+            }
+            EPON_OAM_LOG(ERROR, data->device_id, &cap->key, "Received OAM dying gasp\n");
+        }
+        if (oam_frame->protocols[0].u.slow_protocol.value.u.oam.flags & BCMOLT_EPON_OAM_OAM_FLAGS_CRITICAL_EVENT)
+        {
+            if (NULL != data->cb)
+            {
+                data->cb(data->device_id, cap->key.epon_ni, &cap->key.mac_address, BCMOLT_USER_APPL_EPON_OAM_RX_ID_CRITICAL_EVENT, BCM_ERR_OK);
+            }
+            EPON_OAM_LOG(ERROR, data->device_id, &cap->key, "Received OAM critical event\n");
+        }
+        EPON_OAM_LOG(DEBUG, data->device_id, &cap->key, "Rx opcode %u\n",
+                     oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.code);
+        switch (oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.code)
+        {
+            case BCMOLT_EPON_OAM_OAM_OPCODE_INFO:
+                break; /* ignore info - this should be handled by eon */
+            case BCMOLT_EPON_OAM_OAM_OPCODE_EVENT_NOTIFICATION:
+                epon_oam_handle_event(data->device_id, &oam_frame->protocols[0].u.slow_protocol.value.u.oam.content, &cap->key, data->cb);
+                break;
+            case BCMOLT_EPON_OAM_OAM_OPCODE_ORGANIZATION_SPECIFIC:
+                EPON_OAM_LOG(DEBUG, data->device_id, &cap->key, "Rx OUI %u\n",
+                             oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.oui);
+                switch (oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.oui)
+                {
+                    case BCMOLT_EPON_OAM_WELL_KNOWN_OUI_DPOE:
+                        epon_oam_handle_dpoe(data->device_id, &oam_frame->protocols[0].u.slow_protocol.value.u.oam.content.u.organization_specific.value.u.dpoe.value, &cap->key, data->cb);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    bcmos_free(oam_frame);
+    bcmolt_msg_free(&data->rx->hdr);
+}
+
+static void epon_oam_dpoe_handle_fw_upgrade_timeout(epon_oam_dpoe_fw_upgrade_state *state)
+{
+    EPON_OAM_LOG(ERROR, state->device_id, &state->key, "Firmware upgrade timed out\n");
+    epon_oam_dpoe_send_ack(state->device_id, &state->key, BCMOLT_EPON_OAM_DPOE_FILE_TRANSFER_ERROR_TIMEOUT);
+    epon_oam_dpoe_fw_upgrade_state_release(state);
+}
+
+static void epon_oam_handle_os_msg(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    epon_oam_msg *msg = (epon_oam_msg*)os_msg;
+
+    switch (msg->os_msg.type)
+    {
+        case BCMOS_MSG_ID_EPON_OAM_PROXY_RX:
+            bcmolt_user_appl_epon_oam_handle_proxy_rx(&msg->data.proxy_rx);
+            break;
+        case BCMOS_MSG_ID_EPON_OAM_TIMEOUT:
+            epon_oam_dpoe_handle_fw_upgrade_timeout(msg->data.timeout.state);
+            break;
+        default:
+            BCM_LOG(ERROR, epon_oam_log[0], "Unknown OS message %u\n", msg->os_msg.type);
+            break;
+    }
+    bcmos_free(os_msg);
+}
+
+void bcmolt_user_appl_epon_oam_handle_rx(bcmolt_devid device_id,
+                                         bcmolt_proxy_rx *rx,
+                                         bcmolt_user_appl_epon_oam_handle_rx_cb cb)
+{
+    bcmos_errno rc;
+    const bcmolt_epon_link_frame_captured *cap = (bcmolt_epon_link_frame_captured*)rx;
+    epon_oam_msg_data msg_data = {};
+
+    if (!is_running)
+    {
+        return;
+    }
+
+    if ((BCMOLT_OBJ_ID_EPON_LINK != rx->hdr.obj_type) ||
+        (BCMOLT_EPON_LINK_PROXY_RX_ID_FRAME_CAPTURED != rx->hdr.subgroup) ||
+        (cap->data.frame.val[12] != 0x88) ||
+        (cap->data.frame.val[13] != 0x09) ||
+        (cap->data.frame.val[14] != 0x03))
+    {
+        return; /* not OAM frame captured on a link - ignore */
+    }
+
+    msg_data.proxy_rx.device_id = device_id;
+    msg_data.proxy_rx.cb = cb;
+    rc = bcmolt_msg_clone((bcmolt_msg**)&msg_data.proxy_rx.rx, &rx->hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, epon_oam_log[device_id], "Proxy Rx clone failed: %s\n", bcmos_strerror(rc));
+        return;
+    }
+
+    if (BCM_ERR_OK != epon_oam_send_os_msg(BCMOS_MSG_ID_EPON_OAM_PROXY_RX, &msg_data))
+    {
+        bcmolt_msg_free(&msg_data.proxy_rx.rx->hdr);
+    }
+}
+
+void bcmolt_user_appl_epon_oam_init(void)
+{
+    bcmos_module_parm module_params =
+    {
+        .qparm =
+        {
+            .name = "user_appl_epon_oam_module",
+            .size = MAX_CONCURRENT_LINKS
+        }
+    };
+    bcmos_task_parm task_params =
+    {
+        .name         = "user_appl_epon_oam_task",
+        .priority     = BCMOS_TASK_PRIORITY_12,
+        .core         = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+
+    if (is_running)
+    {
+        return;
+    }
+
+#ifdef ENABLE_LOG
+    int i;
+    char log_name[MAX_DEV_LOG_ID_NAME] = {};
+    for (i=0; i<BCMTR_MAX_OLTS; i++)
+    {
+        snprintf(log_name, sizeof(log_name)-1, "epon_oam%d", i);
+        epon_oam_log[i] = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+    }
+#endif
+    dpoe_fw_upgrade_state = hash_table_create(MAX_CONCURRENT_LINKS,
+                                              sizeof(epon_oam_dpoe_fw_upgrade_state),
+                                              sizeof(bcmolt_epon_link_key),
+                                              "dpoe_fw_upgrade_state");
+    if (BCM_ERR_OK != bcmos_task_create(&epon_oam_task, &task_params))
+    {
+        BCM_LOG(FATAL, epon_oam_log[0], "Failed to create EPON OAM task!\n");
+    }
+    if (BCM_ERR_OK != bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_EPON_OAM, &epon_oam_task, &module_params))
+    {
+        BCM_LOG(FATAL, epon_oam_log[0], "Failed to create EPON OAM module!\n");
+    }
+    is_running = BCMOS_TRUE;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.h b/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.h
new file mode 100644
index 0000000..c5dc9ed
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam/bcmolt_user_appl_epon_oam.h
@@ -0,0 +1,167 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_EPON_OAM_H_
+#define _BCMOLT_USER_APPL_EPON_OAM_H_
+
+#include "bcmcli.h"
+#include "bcmolt_msg.h"
+#include "bcmolt_epon_oam_types.h"
+
+#define MAX_QUEUE_SETS          4
+#define MAX_REPORT_THRESHOLDS   8
+
+static const bcmos_mac_address slow_protocol_multicast_mac = {{0x01,0x80,0xc2,0x00,0x00,0x02}};
+
+/* Typedef required for conveying a 2-dimensional array to epon_oam_dpoe_set_report_thresholds(). */
+typedef uint16_t bcmolt_epon_oam_queue_sets[MAX_QUEUE_SETS][MAX_REPORT_THRESHOLDS];
+
+typedef enum
+{
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_LINK_FAULT,
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_DYING_GASP,
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_CRITICAL_EVENT,
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_EVENT,
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_GET_RESPONSE,
+    BCMOLT_USER_APPL_EPON_OAM_RX_ID_DPOE_SET_RESPONSE,
+} bcmolt_user_appl_epon_oam_rx_id;
+
+typedef enum
+{
+    DPOE_RULE_VLAN_MODE_NONE,
+    DPOE_RULE_VLAN_MODE_ADD,
+    DPOE_RULE_VLAN_MODE_REMOVE,
+
+    DPOE_RULE_VLAN_MODE_COUNT
+} dpoe_rule_vlan_mode;
+
+/* During parsing of OAM indication, this optional callback may be called. */
+typedef void (*bcmolt_user_appl_epon_oam_handle_rx_cb)(bcmolt_devid device_id,
+                                                       bcmolt_epon_ni epon_ni,
+                                                       const bcmos_mac_address *mac,
+                                                       bcmolt_user_appl_epon_oam_rx_id id,
+                                                       bcmos_errno rc);
+
+/* Callback 'cb' is optional. */
+void bcmolt_user_appl_epon_oam_handle_rx(bcmolt_devid device_id,
+                                         bcmolt_proxy_rx *rx,
+                                         bcmolt_user_appl_epon_oam_handle_rx_cb cb);
+
+bcmos_errno epon_oam_pack_frame(bcmolt_epon_oam_ethernet_frame *oam_frame, uint8_t **buffer, uint16_t *length);
+
+bcmos_errno epon_oam_pack_and_send(bcmolt_devid device_id,
+                                   bcmolt_epon_ni epon,
+                                   const bcmos_mac_address *mac,
+                                   bcmolt_epon_oam_ethernet_frame *oam_frame);
+
+bcmolt_epon_oam_ethernet_frame *epon_oam_unpack(bcmolt_devid device_id, uint32_t len, uint8_t *bytes);
+
+bcmos_errno epon_oam_dpoe_get_critical_info(bcmolt_devid device_id,
+                                            bcmolt_epon_ni epon,
+                                            bcmos_mac_address *mac);
+
+bcmos_errno epon_oam_dpoe_set_oam_rate(bcmolt_devid device_id,
+                                       bcmolt_epon_ni epon,
+                                       bcmos_mac_address *mac,
+                                       uint8_t min,
+                                       uint8_t max);
+
+bcmos_errno epon_oam_dpoe_set_report_thresholds(bcmolt_devid device_id,
+                                                bcmolt_epon_ni epon,
+                                                bcmos_mac_address *mac,
+                                                uint8_t report_values_per_queue_set,
+                                                bcmolt_epon_oam_queue_sets queue_sets,
+                                                uint8_t num_queue_sets);
+
+bcmos_errno epon_oam_dpoe_set_encryption(
+    bcmolt_devid device_id,
+    bcmolt_epon_ni epon,
+    bcmos_mac_address *mac,
+    bcmolt_epon_oam_dpoe_encryption_mode enc_mode,
+    uint16_t period);
+
+bcmos_bool epon_oam_is_dpoe(bcmolt_epon_oam_ethernet_frame *oam);
+
+bcmos_bool epon_oam_is_dpoe_encrypt_response(bcmolt_epon_oam_dpoe_vendor_extended_base *dpoe);
+
+bcmos_errno epon_oam_dpoe_clear_ingress_rules_network_pon(bcmolt_devid device_id,
+                                                          bcmolt_epon_ni epon,
+                                                          bcmos_mac_address *mac);
+bcmos_errno epon_oam_dpoe_clear_ingress_rules_user_port(bcmolt_devid device_id,
+                                                        bcmolt_epon_ni epon,
+                                                        bcmos_mac_address *mac);
+bcmos_errno epon_oam_dpoe_clear_ingress_rules(bcmolt_devid device_id,
+                                              bcmolt_epon_ni epon,
+                                              bcmos_mac_address *mac);
+
+bcmos_errno epon_oam_dpoe_set_basic_queue_config(bcmolt_devid device_id,
+                                                 bcmolt_epon_ni epon,
+                                                 bcmos_mac_address *mac,
+                                                 uint8_t up_queue_size,
+                                                 uint8_t dn_queue_size);
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules_network_pon(bcmolt_devid device_id,
+                                                        bcmolt_epon_ni epon,
+                                                        bcmos_mac_address *mac,
+                                                        dpoe_rule_vlan_mode vlan_mode,
+                                                        uint8_t vlan_tag[4]);
+bcmos_errno epon_oam_dpoe_add_ingress_rules_user_port(bcmolt_devid device_id,
+                                                      bcmolt_epon_ni epon,
+                                                      bcmos_mac_address *mac,
+                                                      dpoe_rule_vlan_mode vlan_mode,
+                                                      uint8_t vlan_tag[4]);
+bcmos_errno epon_oam_dpoe_add_ingress_rules(bcmolt_devid device_id,
+                                            bcmolt_epon_ni epon,
+                                            bcmos_mac_address *mac);
+
+bcmos_errno epon_oam_dpoe_add_ingress_rules_with_vlan(bcmolt_devid device_id,
+                                                      bcmolt_epon_ni epon,
+                                                      bcmos_mac_address *mac,
+                                                      uint8_t vlan_tag[4]);
+
+bcmos_errno epon_oam_dpoe_enable_user_traffic(bcmolt_devid device_id,
+                                              bcmolt_epon_ni epon,
+                                              bcmos_mac_address *mac);
+
+bcmos_errno epon_oam_dpoe_disable_user_traffic(bcmolt_devid device_id,
+                                               bcmolt_epon_ni epon,
+                                               bcmos_mac_address *mac);
+
+bcmos_errno epon_oam_dpoe_start_upgrade(bcmolt_devid device_id,
+                                        bcmolt_epon_ni epon,
+                                        bcmos_mac_address *mac,
+                                        const char *fw_file,
+                                        uint16_t block_size,
+                                        uint8_t timeout_sec);
+
+bcmos_errno epon_oam_dpoe_reset_onu(bcmolt_devid device_id, bcmolt_epon_ni epon, bcmos_mac_address *mac);
+
+void bcmolt_user_appl_epon_oam_init(void);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/Makefile b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/Makefile
new file mode 100644
index 0000000..8bc0811
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/Makefile
@@ -0,0 +1,10 @@
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_user_appl_epon_oam_cli
+    MOD_TYPE = lib
+	MOD_DEPS = utils dev_log common_epon_oam bcm_user_appl_epon_oam
+    srcs = bcmolt_user_appl_epon_oam_cli.c
+    ifeq ("$(OS_KERNEL)", "linux")
+	MOD_DEPS += dev_log_linux
+    endif
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.c b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.c
new file mode 100644
index 0000000..ba8eaa6
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.c
@@ -0,0 +1,360 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "bcmos_system.h"
+#include "bcmolt_math.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_api.h"
+#include "bcmolt_model_types.h"
+#include "bcmolt_user_appl_epon_oam.h"
+#include "bcmolt_user_appl_epon_oam_cli.h"
+#include "bcmolt_epon_oam_types.h"
+
+static bcmos_errno epon_oam_dpoe_get_critical_info_cli(bcmcli_session *session,
+                                                       const bcmcli_cmd_parm parm[],
+                                                       uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_get_critical_info(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_set_oam_rate_cli(bcmcli_session *session,
+                                                  const bcmcli_cmd_parm parm[],
+                                                  uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    uint8_t min = (uint8_t)bcmcli_find_named_parm(session, "min")->value.unumber;
+    uint8_t max = (uint8_t)bcmcli_find_named_parm(session, "max")->value.unumber;
+
+    return epon_oam_dpoe_set_oam_rate(current_device, epon, &mac, min, max);
+}
+
+static bcmos_errno epon_oam_dpoe_set_report_thresholds_cli(bcmcli_session *session,
+                                                           const bcmcli_cmd_parm parm[],
+                                                           uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    bcmcli_cmd_parm *queue_set_parms[MAX_QUEUE_SETS];
+
+    /* OAM stuff */
+    bcmolt_epon_oam_queue_sets queue_sets;
+    uint8_t i, j, num_queue_sets = 0;
+
+    /* process input */
+    queue_set_parms[0] = bcmcli_find_named_parm(session, "queue_0");
+    queue_set_parms[1] = bcmcli_find_named_parm(session, "queue_1");
+    queue_set_parms[2] = bcmcli_find_named_parm(session, "queue_2");
+    queue_set_parms[3] = bcmcli_find_named_parm(session, "queue_3");
+    for (i = 0; i < MAX_QUEUE_SETS; i++)
+    {
+        if (NULL != queue_set_parms[i])
+        {
+            num_queue_sets++;
+            if (queue_set_parms[i]->array_size != queue_set_parms[0]->array_size)
+            {
+                bcmcli_session_print(session, "All queue sets must have the same number of report thresholds!\n");
+                return BCM_ERR_PARM;
+            }
+            for (j = 0; j < queue_set_parms[i]->array_size; j++)
+            {
+                if (queue_set_parms[i]->values[j].unumber > 65535)
+                {
+                    bcmcli_session_print(session, "Queue Set %u, Threshold %u: %lu is not a valid report threshold!\n",
+                                         i, j, queue_set_parms[i]->values[j].unumber);
+                    return BCM_ERR_PARM;
+                }
+                queue_sets[i][j] = (uint16_t)queue_set_parms[i]->values[j].unumber;
+            }
+        }
+        else
+        {
+            for (j = i + 1; j < MAX_QUEUE_SETS; j++)
+            {
+                if (NULL != queue_set_parms[j])
+                {
+                    bcmcli_session_print(session, "Included queue sets must be sequential from 0!\n");
+                    return BCM_ERR_PARM;
+                }
+            }
+        }
+    }
+
+    return epon_oam_dpoe_set_report_thresholds(current_device,
+                                               epon,
+                                               &mac,
+                                               queue_set_parms[0]->array_size,
+                                               queue_sets,
+                                               num_queue_sets);
+}
+
+static bcmos_errno epon_oam_dpoe_clear_ingress_rules_network_pon_cli(bcmcli_session *session,
+                                                                     const bcmcli_cmd_parm parm[],
+                                                                     uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_clear_ingress_rules_network_pon(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_clear_ingress_rules_user_port_cli(bcmcli_session *session,
+                                                                   const bcmcli_cmd_parm parm[],
+                                                                   uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_clear_ingress_rules_user_port(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_clear_ingress_rules_cli(bcmcli_session *session,
+                                                         const bcmcli_cmd_parm parm[],
+                                                         uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_clear_ingress_rules(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_set_basic_queue_config_cli(bcmcli_session *session,
+                                                            const bcmcli_cmd_parm parm[],
+                                                            uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    uint8_t up_queue_size = (uint8_t)bcmcli_find_named_parm(session, "up_size")->value.unumber;
+    uint8_t dn_queue_size = (uint8_t)bcmcli_find_named_parm(session, "dn_size")->value.unumber;
+
+    return epon_oam_dpoe_set_basic_queue_config(current_device, epon, &mac, up_queue_size, dn_queue_size);
+}
+
+static bcmos_errno epon_oam_dpoe_add_ingress_rules_network_pon_cli(bcmcli_session *session,
+                                                                   const bcmcli_cmd_parm parm[],
+                                                                   uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_add_ingress_rules_network_pon(current_device, epon, &mac, DPOE_RULE_VLAN_MODE_NONE, NULL);
+}
+
+static bcmos_errno epon_oam_dpoe_add_ingress_rules_user_port_cli(bcmcli_session *session,
+                                                                 const bcmcli_cmd_parm parm[],
+                                                                 uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_add_ingress_rules_user_port(current_device, epon, &mac, DPOE_RULE_VLAN_MODE_NONE, NULL);
+}
+
+static bcmos_errno epon_oam_dpoe_add_ingress_rules_cli(bcmcli_session *session,
+                                                       const bcmcli_cmd_parm parm[],
+                                                       uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_add_ingress_rules(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_add_ingress_rules_with_vlan_cli(bcmcli_session *session,
+                                                                 const bcmcli_cmd_parm parm[],
+                                                                 uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    uint32_t vlan = bcmcli_find_named_parm(session, "vlan")->value.unumber;
+    uint8_t vlan_tag[4];
+
+    vlan = BCMOS_ENDIAN_CPU_TO_BIG_U32(vlan);
+    vlan_tag[0] = (vlan >> 24) & 0xff;
+    vlan_tag[1] = (vlan >> 16) & 0xff;
+    vlan_tag[2] = (vlan >> 8) & 0xff;
+    vlan_tag[3] = (vlan >> 0) & 0xff;
+
+    return epon_oam_dpoe_add_ingress_rules_with_vlan(current_device, epon, &mac, vlan_tag);
+}
+
+static bcmos_errno epon_oam_dpoe_enable_user_traffic_cli(bcmcli_session *session,
+                                                         const bcmcli_cmd_parm parm[],
+                                                         uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_enable_user_traffic(current_device, epon, &mac);
+}
+
+static bcmos_errno epon_oam_dpoe_start_upgrade_cli(bcmcli_session *session,
+                                                   const bcmcli_cmd_parm parm[],
+                                                   uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+    const char *fw_file = bcmcli_find_named_parm(session, "fw_file")->value.string;
+    /* This should be based on the negotiated PDU size! */
+    uint16_t block_size = (uint16_t)bcmcli_find_named_parm(session, "block_size")->value.unumber;
+    uint8_t timeout = (uint8_t)bcmcli_find_named_parm(session, "timeout")->value.unumber;
+
+    return epon_oam_dpoe_start_upgrade(current_device, epon, &mac, fw_file, block_size, timeout);
+}
+
+static bcmos_errno epon_oam_dpoe_reset_onu_cli(bcmcli_session *session,
+                                               const bcmcli_cmd_parm parm[],
+                                               uint16_t n_parms)
+{
+    bcmolt_epon_ni epon = (bcmolt_epon_ni)bcmcli_find_named_parm(session, "epon_ni")->value.unumber;
+    bcmos_mac_address mac = bcmcli_find_named_parm(session, "mac_address")->value.mac;
+
+    return epon_oam_dpoe_reset_onu(current_device, epon, &mac);
+}
+
+void bcmolt_user_appl_epon_oam_cli_init(bcmcli_entry *top_dir)
+{
+#ifdef ENABLE_CLI
+    static const char *dir_name = "epon_oam";
+
+    if (bcmcli_dir_find(top_dir, dir_name))
+    {
+        return;
+    }
+
+    bcmcli_entry *oam_dir = bcmcli_dir_add(top_dir, dir_name, "EPON OAM", BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(NULL == oam_dir);
+
+    static bcmcli_parm_value queue_0[MAX_REPORT_THRESHOLDS];
+    static bcmcli_parm_value queue_1[MAX_REPORT_THRESHOLDS];
+    static bcmcli_parm_value queue_2[MAX_REPORT_THRESHOLDS];
+    static bcmcli_parm_value queue_3[MAX_REPORT_THRESHOLDS];
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_get_critical_info", "DPoE get ONU ID and max links",
+                    epon_oam_dpoe_get_critical_info_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_set_oam_rate", "DPoE set OAM rate",
+                    epon_oam_dpoe_set_oam_rate_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0),
+                    BCMCLI_MAKE_PARM("min", "Minimum OAM rate", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("max", "Maximum OAM rate", BCMCLI_PARM_UNUMBER, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_set_report_thresholds", "DPoE set report threhsolds",
+                    epon_oam_dpoe_set_report_thresholds_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0),
+                    BCMCLI_MAKE_PARM_ARRAY("queue_0",
+                                           "Report threhsold for queue set 0",
+                                           BCMCLI_PARM_UNUMBER,
+                                           BCMCLI_PARM_FLAG_NONE,
+                                           MAX_REPORT_THRESHOLDS,
+                                           queue_0),
+                    BCMCLI_MAKE_PARM_ARRAY("queue_1",
+                                           "Report threhsold for queue set 0",
+                                           BCMCLI_PARM_UNUMBER,
+                                           BCMCLI_PARM_FLAG_OPTIONAL,
+                                           MAX_REPORT_THRESHOLDS,
+                                           queue_1),
+                    BCMCLI_MAKE_PARM_ARRAY("queue_2",
+                                           "Report threhsold for queue set 0",
+                                           BCMCLI_PARM_UNUMBER,
+                                           BCMCLI_PARM_FLAG_OPTIONAL,
+                                           MAX_REPORT_THRESHOLDS,
+                                           queue_2),
+                    BCMCLI_MAKE_PARM_ARRAY("queue_3",
+                                           "Report threhsold for queue set 0",
+                                           BCMCLI_PARM_UNUMBER,
+                                           BCMCLI_PARM_FLAG_OPTIONAL,
+                                           MAX_REPORT_THRESHOLDS,
+                                           queue_3));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_clear_ingress_rules_network_pon", "DPoE clear ingress rules for network PON",
+                    epon_oam_dpoe_clear_ingress_rules_network_pon_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_clear_ingress_rules_user_port", "DPoE clear ingress rules for user port",
+                    epon_oam_dpoe_clear_ingress_rules_user_port_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_clear_ingress_rules", "DPoE clear ingress rules",
+                    epon_oam_dpoe_clear_ingress_rules_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_set_basic_queue_config", "DPoE set basic queue config for 1 link/1 port",
+                    epon_oam_dpoe_set_basic_queue_config_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0),
+                    BCMCLI_MAKE_PARM("up_size", "upstream queue size", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("dn_size", "downstream queue size", BCMCLI_PARM_UNUMBER, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_add_ingress_rules_network_pon", "DPoE add ingress rules for network PON",
+                    epon_oam_dpoe_add_ingress_rules_network_pon_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_add_ingress_rules_user_port", "DPoE add ingress rules for user port",
+                    epon_oam_dpoe_add_ingress_rules_user_port_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_add_ingress_rules", "DPoE add ingress rules",
+                    epon_oam_dpoe_add_ingress_rules_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_add_ingress_rules_with_vlan", "DPoE add ingress rules with VLAN",
+                    epon_oam_dpoe_add_ingress_rules_with_vlan_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0),
+                    BCMCLI_MAKE_PARM("vlan", "VLAN", BCMCLI_PARM_UNUMBER, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_enable_user_traffic", "DPoE enable user traffic",
+                    epon_oam_dpoe_enable_user_traffic_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_fw_upgrade", "DPoE firwmare upgrade",
+                    epon_oam_dpoe_start_upgrade_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0),
+                    BCMCLI_MAKE_PARM("fw_file", "Firmware file", BCMCLI_PARM_STRING, 0),
+                    BCMCLI_MAKE_PARM("block_size", "Block size", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("timeout", "Timeout in seconds", BCMCLI_PARM_UNUMBER, 0));
+
+    BCMCLI_MAKE_CMD(oam_dir, "dpoe_reset_onu", "DPoE reset ONU",
+                    epon_oam_dpoe_reset_onu_cli,
+                    BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_UNUMBER, 0),
+                    BCMCLI_MAKE_PARM("mac_address", "link MAC", BCMCLI_PARM_MAC, 0));
+#endif
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.h b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.h
new file mode 100644
index 0000000..99b4ae8
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/epon_oam_cli/bcmolt_user_appl_epon_oam_cli.h
@@ -0,0 +1,38 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_EPON_OAM_CLI_H_
+#define _BCMOLT_USER_APPL_EPON_OAM_CLI_H_
+
+#include "bcmcli.h"
+#include "bcmolt_msg.h"
+
+void bcmolt_user_appl_epon_oam_cli_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/Makefile b/bcm68620_release/release/host_reference/user_appl/image_transfer/Makefile
new file mode 100644
index 0000000..e2392f5
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/Makefile
@@ -0,0 +1,12 @@
+# File transfer application
+
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_image_transfer
+    MOD_TYPE = lib
+    MOD_DEPS = host_api
+    srcs = bcmolt_image_transfer.c bcmolt_image_transfer_user.c bcmolt_image_transfer_cli.c
+    
+    ifeq ("$(OS_KERNEL)", "linux")
+        MOD_DEPS += dev_log_linux
+    endif
+endif
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.c b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.c
new file mode 100644
index 0000000..7085eaa
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.c
@@ -0,0 +1,507 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+ */
+
+#include <bcmtr_interface.h>
+#include <bcmcli.h>
+#include <libgen.h>
+#include "bcmolt_api.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_image_transfer.h"
+#include "bcmolt_image_transfer_user.h"
+
+static bcmuser_mftp_block_size_get mftp_block_size_get;
+static bcmuser_mftp_crc_get mftp_crc_get_cb;
+static bcmuser_mftp_image_read mftp_read_data_cb;
+static bcmuser_mftp_notification_rx mftp_notify_user_cb;
+static mftp_context mftp_context_db[BCMTR_MAX_OLTS];
+
+static bcmos_errno mftp_ind_rx_cb_register(bcmolt_devid device);
+static bcmos_errno mftp_ind_rx_cb_unregister(bcmolt_devid device);
+
+/* ========================================================================== */
+static bcmolt_image_transfer_status mftp_err_to_status(bcmos_errno err)
+{
+    bcmolt_image_transfer_status status;
+    switch (err)
+    {
+    case BCM_ERR_OK:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS;
+        break;
+    case BCM_ERR_NOMEM:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_MEMORY_ALLOCATION_FAILURE;
+        break;
+    case BCM_ERR_INCOMPLETE_TERMINATION:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_PREMATURE_TERMINATION;
+        break;
+    case BCM_ERR_CHECKSUM:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_CRC_ERROR;
+        break;
+    case BCM_ERR_OVERFLOW:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR;
+        break;
+    case BCM_ERR_OUT_OF_SYNC:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC;
+        break;
+    case BCM_ERR_STATE:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_INVALID_STATE;
+        break;
+    case BCM_ERR_IMAGE_TYPE:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE;
+        break;
+    default:
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR;
+        break;
+    }
+    return status;
+}
+
+/* ========================================================================== */
+const char *bcmolt_user_mftp_status_to_str(bcmolt_image_transfer_status status)
+{
+    static const char *mftp_status_to_str_table[BCMOLT_IMAGE_TRANSFER_STATUS__NUM_OF] =
+    {
+        [BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS] = "Succeeded",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_MEMORY_ALLOCATION_FAILURE] = "ERROR: Memory allocation",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE] = "ERROR: Invalid image type",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_CRC_ERROR] = "ERROR: Checksum",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC] = "ERROR: Block out-of-sync",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR] = "ERROR: Buffer overflow",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_INVALID_STATE] = "ERROR: Invalid state",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_PREMATURE_TERMINATION] = "ERROR: Premature termination",
+        [BCMOLT_IMAGE_TRANSFER_STATUS_ACK_TIMEOUT] = "ERROR: ACK timeout"
+    };
+    return (status >= BCMOLT_IMAGE_TRANSFER_STATUS__NUM_OF) ? "<unknown>" : mftp_status_to_str_table[status];
+}
+
+/* ========================================================================== */
+static inline mftp_context *mftp_context_get(bcmolt_devid device)
+{
+    return &mftp_context_db[device];
+}
+
+/* ========================================================================== */
+/** convert device ID to module ID */
+static inline bcmos_module_id mftp_device_id_to_module_id(bcmolt_devid device)
+{
+    return (BCMOS_MODULE_ID_USER_APPL_IMAGE_TRANSFER0 + device);
+}
+
+/* ========================================================================== */
+/** examines the ACK response. */
+static bcmolt_image_transfer_status mftp_check_params(mftp_context *context,
+    bcmolt_device_image_transfer_complete_data *ack_data)
+{
+    bcmolt_image_transfer_status status = BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS;
+
+    if (context->block_num != ack_data->block_number)
+    {
+        BCM_LOG(DEBUG, context->log_id, "ACK block # mismatch: exp=%u ack=%u\n", context->block_num, ack_data->block_number);
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC;
+    }
+    else if (context->image_type != ack_data->image_type)
+    {
+        BCM_LOG(DEBUG, context->log_id, "ACK image type mismatch: exp=%u ack=%u\n", context->image_type, ack_data->image_type);
+        status = BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE;
+    }
+    else if (ack_data->status != BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS)
+    {
+        BCM_LOG(DEBUG, context->log_id, "OLT returned an error: %s\n", bcmolt_user_mftp_status_to_str(ack_data->status));
+        status = ack_data->status;
+    }
+    return status;
+}
+
+/* ========================================================================== */
+static void mftp_terminate(bcmolt_devid device, bcmolt_device_image_type image_type,
+    uint32_t block_num, bcmolt_image_transfer_status status)
+{
+    mftp_context *context = mftp_context_get(device);
+    bcmos_errno rc = mftp_ind_rx_cb_unregister(device);
+
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Failed to un-register IND callback\n");
+    }
+    mftp_notify_user_cb(device, image_type, block_num, status);
+    context->status = MFTP_STATUS_DISABLED;
+}
+
+/* ========================================================================== */
+/** Sends the image transfer start operation to the OLT. */
+static bcmos_errno mftp_send_wrq(bcmolt_devid device, bcmolt_device_image_type image_type,
+    uint32_t image_size, uint32_t crc32, bcmolt_str_64 *image_name)
+{
+    bcmolt_device_image_transfer_start oper;
+    bcmolt_device_key key = { };
+
+    BCMOLT_OPER_INIT(&oper, device, image_transfer_start, key);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_type, image_type);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_size, image_size);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, crc32, crc32);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_name, *image_name);
+    return bcmolt_oper_submit(device, &oper.hdr);
+}
+
+/* ========================================================================== */
+/** Sends a data block to the OLT. */
+static bcmos_errno mftp_send_data(bcmolt_devid device, uint8_t *buf, uint32_t buf_size,
+    uint32_t block_number, bcmos_bool more_data)
+{
+    bcmolt_device_key key = {.reserved = 0};
+    bcmolt_device_image_transfer_data oper = {};
+    bcmolt_u8_list_u16_hex data;
+    data.len = buf_size;
+    data.val = buf;
+
+    /* Builds a transport message for BCMOLT_DEVICE_OPER_ID_IMAGE_TRANSFER_DATA */
+    BCMOLT_OPER_INIT(&oper, device, image_transfer_data, key);
+    oper.hdr.hdr.type = BCMOLT_MSG_TYPE_SET;
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, block_number, block_number);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, more_data, more_data);
+    BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, data, data);
+    return bcmolt_oper_submit(device, &oper.hdr);
+}
+
+/* ========================================================================== */
+/* handler for the OPERATION device.image_transfer_start. */
+bcmos_errno bcmolt_user_mftp_start(bcmolt_devid device, bcmolt_device_image_type image_type)
+{
+    mftp_context *context = mftp_context_get(device);
+    uint32_t image_size;
+    uint32_t crc32;
+    const char *path_name;
+    char file_name[MFTP_MAX_PATH_NAME_LEN];
+    char *base_name;
+    bcmolt_str_64 image_name;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    if (context->status != MFTP_STATUS_DISABLED)
+    {
+        return BCM_ERR_IN_PROGRESS;
+    }
+
+    rc = bcmuser_image_transfer_file_size_get(device, image_type, &image_size);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Failed to get the image size\n");
+        return rc;
+    }
+
+    path_name = bcmuser_device_image_name_get(image_type);
+    if (path_name == NULL)
+    {
+        BCM_LOG(ERROR, context->log_id, "File not found\n");
+        return BCM_ERR_PARM;
+    }
+    if (strlen(path_name) >= sizeof(file_name))
+    {
+        BCM_LOG(ERROR, context->log_id, "Path name too long\n");
+        return BCM_ERR_PARM;
+    }
+    (void)strcpy(file_name, path_name);       /* to make lint happy. */
+
+    base_name = basename(file_name);
+    if (strlen(base_name) >= sizeof(image_name))
+    {
+        BCM_LOG(ERROR, context->log_id, "File name too long\n");
+        return BCM_ERR_PARM;
+    }
+    (void)strcpy(image_name.str, base_name);
+
+    context->status = MFTP_STATUS_WAITING_ACK;
+    context->block_num = 0;
+    context->image_type = image_type;
+    crc32 = mftp_crc_get_cb(device, image_type);
+    BCM_LOG(DEBUG, context->log_id, "Image size=%u crc=0x%x\n", image_size, crc32);
+
+    rc = mftp_ind_rx_cb_register(device);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Failed to register IND callback\n");
+        BUG();
+    }
+
+    rc = mftp_send_wrq(device, image_type, image_size, crc32, &image_name);
+    if (BCM_ERR_OK != rc)
+    {
+        BCM_LOG(DEBUG, context->log_id, "WRQ OPER failed. %s\n", bcmos_strerror(rc));
+        (void)mftp_ind_rx_cb_unregister(device);
+        context->status = MFTP_STATUS_DISABLED;
+    }
+    else
+    {
+        bcmos_timer_start(&context->timer, MFTP_ACK_TIMEOUT_US);
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+static void mftp_process_last_ack(bcmolt_devid device, bcmolt_device_image_transfer_complete_data *ack_data)
+{
+    bcmolt_image_transfer_status status;
+    mftp_context *context = mftp_context_get(device);
+
+    status = mftp_check_params(context, ack_data);
+    mftp_terminate(device, ack_data->image_type, ack_data->block_number, status);
+}
+
+/* ========================================================================== */
+/* reads a data block using customer-provided callback and sends it to the embedded. */
+static void mftp_process_ack(bcmolt_devid device, bcmolt_device_image_transfer_complete_data *ack_data)
+{
+    uint32_t offset = 0;    /* file-position*/
+    uint32_t bytes_read = 0;
+    bcmos_bool more_data;
+    bcmos_errno rc = BCM_ERR_OK;
+    bcmolt_image_transfer_status status;
+    mftp_context *context = mftp_context_get(device);
+
+    status = mftp_check_params(context, ack_data);
+    if (status != BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS)
+    {
+        mftp_terminate(device, context->image_type, ack_data->block_number, status);
+        return;
+    }
+
+    offset = (context->block_num * context->block_size);
+    context->block_num++;
+
+    /* CALL CUSTOMER-PROVIDED CALLBACK */
+    bytes_read = mftp_read_data_cb(device, context->image_type, offset,
+        context->buf, context->block_size);  /* bcmuser_mftp_read_data() */
+
+    more_data = !(bytes_read < context->block_size);
+
+    rc = mftp_send_data(device, context->buf, bytes_read, context->block_num, more_data);
+    if (BCM_ERR_OK != rc)
+    {
+        BCM_LOG(DEBUG, context->log_id, "DATA OPER failed. Block %u. %s\n", context->block_num, bcmos_strerror(rc));
+        mftp_terminate(device, ack_data->image_type, ack_data->block_number, mftp_err_to_status(rc));
+        return;
+    }
+
+    BCM_LOG(DEBUG, context->log_id, "Sent block#=%u buf_size=%u more=%u\n", context->block_num, bytes_read, more_data);
+
+    context->status = (more_data) ? MFTP_STATUS_WAITING_ACK : MFTP_STATUS_WAITING_LAST_ACK;
+
+    bcmos_timer_start(&context->timer, MFTP_ACK_TIMEOUT_US);
+}
+
+/* ========================================================================== */
+/* ACK indication callback */
+static void mftp_process_ind(bcmolt_devid olt, bcmolt_msg *msg)
+{
+    mftp_context *context = mftp_context_get(olt);
+    bcmolt_device_image_transfer_complete *ack;
+    bcmolt_device_image_transfer_complete_data *ack_data;
+
+    ack = (bcmolt_device_image_transfer_complete *)msg;
+    ack_data = &ack->data;
+
+    BCM_LOG(DEBUG, context->log_id, "## ACK: obj=%u, group=%u, image=%u, block=%u, status=%u\n",
+        msg->obj_type, msg->group, ack_data->image_type, ack_data->block_number, ack_data->status);
+
+    switch (context->status)
+    {
+    case MFTP_STATUS_WAITING_ACK:
+        bcmos_timer_stop(&context->timer);
+        mftp_process_ack(olt, ack_data);
+        break;
+    case MFTP_STATUS_WAITING_LAST_ACK:
+        bcmos_timer_stop(&context->timer);
+        mftp_process_last_ack(olt, ack_data);
+        break;
+    default:
+        /* This could happen when the START operation fails. */
+        BCM_LOG(DEBUG, context->log_id, "Unexpected ACK\n");
+        break;
+    }
+
+    bcmolt_msg_free(msg);
+}
+
+/* ========================================================================== */
+static bcmos_errno mftp_ind_rx_cb_register(bcmolt_devid device)
+{
+    bcmtr_handler_parm tparm = {
+        .group = BCMOLT_MGT_GROUP_AUTO,
+        .object = BCMOLT_OBJ_ID_DEVICE,
+        .subgroup = BCMOLT_DEVICE_AUTO_ID_IMAGE_TRANSFER_COMPLETE,
+        .app_cb = mftp_process_ind,
+        .flags = BCMOLT_AUTO_FLAGS_DISPATCH,
+    };
+    mftp_context *context = mftp_context_get(device);
+    uint8_t inst;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    tparm.module = context->module_id;
+
+    for (inst = 0; (inst < BCMTR_MAX_INSTANCES) && (rc == BCM_ERR_OK); inst++)
+    {
+        tparm.instance = inst;
+        rc = bcmtr_msg_handler_unregister(device, &tparm);
+        rc = (rc == BCM_ERR_OK) ? bcmtr_msg_handler_register(device, &tparm) : rc;
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+static bcmos_errno mftp_ind_rx_cb_unregister(bcmolt_devid device)
+{
+    bcmtr_handler_parm tparm = {
+        .group = BCMOLT_MGT_GROUP_AUTO,
+        .object = BCMOLT_OBJ_ID_DEVICE,
+        .subgroup = BCMOLT_DEVICE_AUTO_ID_IMAGE_TRANSFER_COMPLETE,
+    };
+    uint8_t inst;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    for (inst = 0; (inst < BCMTR_MAX_INSTANCES) && (rc == BCM_ERR_OK); inst++)
+    {
+        tparm.instance = inst;
+        rc = bcmtr_msg_handler_unregister(device, &tparm);
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+/* ACK response timeout handler */
+static bcmos_timer_rc mftp_ack_timeout_handler(bcmos_timer *timer, long data)
+{
+    bcmolt_devid device = (bcmolt_devid)data;
+    mftp_context *context = mftp_context_get(device);
+
+    if ((context->status == MFTP_STATUS_WAITING_ACK) || (context->status == MFTP_STATUS_WAITING_LAST_ACK))
+    {
+        mftp_terminate(device, context->image_type, context->block_num, BCMOLT_IMAGE_TRANSFER_STATUS_ACK_TIMEOUT);
+    }
+    else
+    {
+        BCM_LOG(INFO, context->log_id, "timer in irrelevant state\n");
+    }
+    return BCMOS_TIMER_STOP;
+}
+
+/* ========================================================================== */
+static bcmos_errno mftp_init_timers(bcmolt_devid device)
+{
+    mftp_context *context = mftp_context_get(device);
+    bcmos_timer_parm timer_params = {};
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* If timer was already created, then we don't need to re-create it */
+    if ((context->timer.flags & BCMOS_TIMER_FLAG_VALID) != 0)
+        return BCM_ERR_OK;
+
+    timer_params.name     = context->name;
+    timer_params.owner    = context->module_id;
+    timer_params.periodic = BCMOS_FALSE;
+    timer_params.handler  = mftp_ack_timeout_handler;
+    timer_params.data     = (long)device;
+
+    rc = bcmos_timer_create(&context->timer, &timer_params);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Timer creation failed. %s\n", bcmos_strerror(rc));
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+static bcmos_errno mftp_init_tasks(bcmolt_devid device)
+{
+    mftp_context    *context = mftp_context_get(device);
+    bcmos_task_parm  task_params = {};
+    bcmos_errno      rc = BCM_ERR_OK;
+
+    task_params.name         = context->name;
+    task_params.priority     = TASK_PRIORITY_USER_APPL_IMAGE_TRANSFER;
+    task_params.core         = BCMOS_CPU_CORE_ANY; /* No CPU affinity */
+    task_params.init_handler = NULL;
+    task_params.data         = (long)device;
+
+    rc = bcmos_task_create(&context->task, &task_params);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Task creation failed. %s \n", bcmos_strerror(rc));
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+static bcmos_errno mftp_init_modules(bcmolt_devid device)
+{
+    mftp_context      *context = mftp_context_get(device);
+    bcmos_module_parm  module_params = {};
+    bcmos_errno        rc = BCM_ERR_OK;
+
+    context->module_id = mftp_device_id_to_module_id(device);
+    module_params.qparm.name = context->name;
+    module_params.qparm.size = BCMOS_MSG_POOL_DEFAULT_SIZE;
+    module_params.init       = NULL;
+
+    rc = bcmos_module_create(context->module_id, &context->task, &module_params);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, context->log_id, "Module creation failed. %s \n", bcmos_strerror(rc));
+    }
+    return rc;
+}
+
+/* ========================================================================== */
+/* Initializes the MFTP parameters. */
+void bcmolt_user_mftp_init(void)
+{
+    mftp_context *context;
+    bcmolt_devid device;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* The customer should provide these callback functions. */
+    mftp_read_data_cb = bcmuser_image_transfer_read_data;
+    mftp_notify_user_cb = bcmuser_image_transfer_notification_rx;
+    mftp_block_size_get = bcmuser_image_transfer_block_size_get;
+    mftp_crc_get_cb = bcmuser_image_transfer_crc_get;
+
+    /* initialize some parameters, i.e. the .status and .timer.
+       other parameters are (re)built upon START, so no need to init. */
+    for (device = 0; (device < BCMTR_MAX_OLTS) && (rc == BCM_ERR_OK); device++)
+    {
+        context = mftp_context_get(device);
+        context->status = MFTP_STATUS_DISABLED;
+        context->block_size = mftp_block_size_get();
+        context->buf = bcmos_calloc(context->block_size);
+        rc = (context->buf == NULL) ? BCM_ERR_NOMEM : rc;
+        snprintf(context->name, sizeof(context->name), "mftp%u", device);
+        rc = (rc == BCM_ERR_OK) ? mftp_init_tasks(device) : rc;
+        rc = (rc == BCM_ERR_OK) ? mftp_init_modules(device) : rc;
+        rc = (rc == BCM_ERR_OK) ? mftp_init_timers(device) : rc;
+        context->log_id = bcm_dev_log_id_register(context->name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+    }
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.h b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.h
new file mode 100644
index 0000000..0ffea3c
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer.h
@@ -0,0 +1,86 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+ */
+
+#ifndef _BCMOLT_IMAGE_TRANSFER_H_
+#define _BCMOLT_IMAGE_TRANSFER_H_
+
+#include <bcmolt_model_types.h>
+#include <bcmos_system.h>
+
+/* Returns a data block of given size of given image type at given offset. */
+typedef int (*bcmuser_mftp_image_read)(bcmolt_devid device, bcmolt_device_image_type image_type,
+    uint32_t offset, uint8_t *buf, uint32_t buf_size);
+
+/* Returns the size of data block which is used for the data transfer. */
+typedef uint32_t (*bcmuser_mftp_block_size_get)(void);
+
+/* Returns the CRC32 checksum of entire image. */
+typedef uint32_t (*bcmuser_mftp_crc_get)(bcmolt_devid device,
+    bcmolt_device_image_type image_type);
+
+/* Callback to notify the user of the completion of the image transfer. */
+typedef void (*bcmuser_mftp_notification_rx)(bcmolt_devid device,
+    bcmolt_device_image_type image_type, uint32_t block_num, bcmolt_image_transfer_status status);
+
+typedef enum
+{
+    MFTP_STATUS_DISABLED,
+    MFTP_STATUS_WAITING_ACK,
+    MFTP_STATUS_WAITING_LAST_ACK
+} mftp_status;
+
+typedef struct
+{
+    /* runtime parameters */
+    mftp_status status;
+    uint32_t block_num;     /* block number sent. */
+    bcmolt_device_image_type image_type;
+
+    /* init-time parameters */
+    uint32_t block_size;
+    uint8_t *buf;
+    char name[MAX_TIMER_NAME_SIZE];
+    bcmos_task task;
+    bcmos_module_id module_id;
+    bcmos_timer timer;
+    dev_log_id log_id;
+} mftp_context;
+
+
+const char *bcmolt_user_mftp_status_to_str(bcmolt_image_transfer_status status);
+
+/** handler for the OPERATION device.image_transfer_start. */
+bcmos_errno bcmolt_user_mftp_start(bcmolt_devid device, bcmolt_device_image_type image_type);
+
+/** Initializes the MFTP parameters. */
+void bcmolt_user_mftp_init(void);
+
+#endif
+
+
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.c b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.c
new file mode 100644
index 0000000..a29243a
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.c
@@ -0,0 +1,87 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+#include <bcmolt_model_types.h>
+#include <bcmolt_msg.h>
+#include <bcmolt_api.h>
+#include <bcm_api_cli_helpers.h>
+#include "bcmolt_image_transfer.h"
+#include "bcmolt_image_transfer_cli.h"
+
+static bcmos_bool bcmolt_user_appl_device_state_is_ready(bcmolt_devid device)
+{
+    bcmos_errno err;
+    bcmolt_device_cfg cfg = {};
+    bcmolt_device_key key = {};
+
+    BCMOLT_CFG_INIT(&cfg, device, key);
+    BCMOLT_CFG_PROP_GET(&cfg, device, state);
+    err = bcmolt_cfg_get(device, &cfg.hdr);
+    return (err == BCM_ERR_OK) && (cfg.data.state == BCMOLT_DEVICE_STATE_READY);
+}
+
+/** start the image transfer.
+ */
+static bcmos_errno bcmolt_user_appl_cli_image_transfer_start(bcmcli_session *session,
+    const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmolt_device_image_type image_type;
+    bcmolt_devid device = 0;
+    bcmos_errno err;
+
+    if (!bcmolt_user_appl_device_state_is_ready(device))
+    {
+        bcmcli_session_print(session, "device not connected\n");
+        return BCM_ERR_NOT_CONNECTED;
+    }
+
+    image_type = bcmcli_find_named_parm(session, "image_type")->value.unumber;
+
+    err = bcmolt_user_mftp_start(device, image_type);
+    if (err != BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "%s\n", bcmos_strerror(err));
+    }
+    return err;
+}
+
+bcmos_errno bcmolt_user_appl_cli_image_transfer_init(bcmcli_entry *top_dir)
+{
+    bcmcli_entry *dir = bcmcli_dir_add(top_dir, "image_transfer",
+        "Image Transfer user application", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD(dir, "start", "Start Image Transfer application", bcmolt_user_appl_cli_image_transfer_start,
+        BCMCLI_MAKE_PARM_ENUM("image_type", "Image type", bcmolt_device_image_type_string_table, BCMCLI_PARM_FLAG_NONE));
+
+    return BCM_ERR_OK;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.h b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.h
new file mode 100644
index 0000000..469f6d8
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_cli.h
@@ -0,0 +1,38 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+*/
+
+#ifndef _BCMOLT_IMAGE_TRANSFER_CLI_H_
+#define _BCMOLT_IMAGE_TRANSFER_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+
+bcmos_errno bcmolt_user_appl_cli_image_transfer_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.c b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.c
new file mode 100644
index 0000000..b60fc83
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.c
@@ -0,0 +1,212 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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 is contains the customer-provided code.
+ * This file is provided as an example.
+ * The customer may change the file path/names.
+ * The customer may change the file access functions.
+ * The customer may adjust the data block size.
+ * Adding a new image type requires the object model change and sync up with
+ * the embedded size, which should be done by the Broadcom engineering.
+ */
+
+#include <bcmolt_conv.h>
+#include "bcmolt_image_transfer.h"
+#include "bcmolt_image_transfer_user.h"
+#include "bcmolt_utils.h"
+
+/* example names for debugging purpose only */
+#define BOOT_FILE "/opt/bcm68620/bcm68620_boot.bin"
+#define APPL_FILE "/opt/bcm68620/bcm68620_appl.bin"
+#define ITU_PON_ONU_FW_FILE "/opt/bcm68620/gpon_onu_fw.w"
+#define EPON_ONU_FW_FILE "/opt/bcm68620/epon_onu_fw.tkf"
+
+static int2str_t image_type2str[] =
+{
+    {BCMOLT_DEVICE_IMAGE_TYPE_BOOTLOADER, BOOT_FILE},
+    {BCMOLT_DEVICE_IMAGE_TYPE_APPLICATION, APPL_FILE},
+    {BCMOLT_DEVICE_IMAGE_TYPE_ITU_PON_ONU_FIRMWARE, ITU_PON_ONU_FW_FILE},
+    {BCMOLT_DEVICE_IMAGE_TYPE_EPON_ONU_FIRMWARE, EPON_ONU_FW_FILE},
+    {-1}
+};
+
+/** Get the device file name.
+ * 
+ * \param[in]   image_type      Image type. defined in the object model.
+ * 
+ * \return      string          Pointer to device image name, including the path.
+ */
+const char *bcmuser_device_image_name_get(bcmolt_device_image_type image_type)
+{
+    if (image_type < BCMOLT_DEVICE_IMAGE_TYPE__NUM_OF)
+    {
+        return int2str(image_type2str, image_type);
+    }
+    return NULL;
+}
+
+/** Open a file with given image type.
+ *
+ * \param[in]   image_type      Image type. defined in the object model.
+ * \param[out]  fpp             File pointer
+ *
+ * \return      bcmos_errno     Error code
+ */
+static bcmos_errno bcmuser_image_transfer_file_open(bcmolt_device_image_type image_type, FILE **fpp)
+{
+    bcmos_errno rc = BCM_ERR_PARM;
+
+    if (image_type < BCMOLT_DEVICE_IMAGE_TYPE__NUM_OF)
+    {
+        FILE *fp;
+        const char *fname = bcmuser_device_image_name_get(image_type);
+        fp = fopen(fname, "rb");
+        if (fp != NULL)
+        {
+            *fpp = fp;
+            rc = BCM_ERR_OK;
+        }
+    }
+    return rc;
+}
+
+/** Get the size of the file. 
+ * 
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * \param[out]  fsize           File size
+ * 
+ * \return      bcmos_errno 
+ */
+bcmos_errno bcmuser_image_transfer_file_size_get(bcmolt_devid device,
+    bcmolt_device_image_type image_type, uint32_t *fsize)
+{
+    FILE *fp;
+    long fp_prev;
+    bcmos_errno rc = BCM_ERR_OK;
+    (void)device;
+
+    rc = bcmuser_image_transfer_file_open(image_type, &fp);
+    if (rc == BCM_ERR_OK)
+    {
+        fp_prev = ftell(fp);
+        BUG_ON(fp_prev < 0);
+        BUG_ON(fseek(fp, 0, SEEK_END) != 0);
+        *fsize = ftell(fp);
+        BUG_ON(fseek(fp, fp_prev, SEEK_SET) != 0); /* go back to where we were. */
+        BUG_ON(fclose(fp) != 0);
+    }
+    return rc;
+}
+
+/** Read data from given offset.
+ *
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * \param[in]   offset          File position indicator
+ * \param[out]  buf             Buffer to store the data
+ * \param[in]   buf_size        Buffer size
+ *
+ * \return      Bytes read
+ */
+int bcmuser_image_transfer_read_data(bcmolt_devid device, bcmolt_device_image_type image_type,
+    uint32_t offset, uint8_t *buf, uint32_t buf_size)
+{
+    FILE *fp;
+    uint32_t bytes_read = 0;
+    (void)device;
+
+    if (bcmuser_image_transfer_file_open(image_type, &fp) == BCM_ERR_OK)
+    {
+        BUG_ON(fseek(fp, 0, SEEK_END) != 0);
+        if (offset < ftell(fp))
+        {
+            BUG_ON(fseek(fp, offset, SEEK_SET) != 0);
+            bytes_read = fread(buf, 1, buf_size, fp);
+        }
+        BUG_ON(fclose(fp) != 0);
+    }
+    return bytes_read;
+}
+
+/** Get the unit size of the transfer data block.
+ *
+ * \return      Size of the block in bytes.
+ */
+uint32_t bcmuser_image_transfer_block_size_get(void)
+{
+    return MFTP_DATA_BLOCK_SIZE;
+}
+
+/** Returns the CRC32 checksum of entire image.
+ * 
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * 
+ * \return      CRC32 checksum 
+ */
+uint32_t bcmuser_image_transfer_crc_get(bcmolt_devid device, bcmolt_device_image_type image_type)
+{
+    FILE *fp;
+    uint32_t crc = 0;
+    (void)device;
+
+    if (bcmuser_image_transfer_file_open(image_type, &fp) == BCM_ERR_OK)
+    {
+        while (1)
+        {
+            char buf[512]; /* any size is OK. */
+            uint32_t bytes_read;
+
+            bytes_read = fread(buf, 1, sizeof(buf), fp);
+            if (bytes_read == 0)
+                break;
+            crc = eth_calc_crc32(crc, buf, bytes_read);
+        }
+        BUG_ON(fclose(fp) != 0);
+    }
+    return crc;
+}
+
+/** Process the notification from the image transfer module.
+ *
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model. 
+ * \param[in]   block_num       last block number 
+ * \param[in]   status          Image transfer status 
+ */
+void bcmuser_image_transfer_notification_rx(bcmolt_devid device,
+    bcmolt_device_image_type image_type, uint32_t block_num,
+    bcmolt_image_transfer_status status)
+{
+    const char *str = bcmolt_user_mftp_status_to_str(status);
+    bcmos_printf("Image transfer: %s: type=%u block=%u\n", str, image_type, block_num);
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.h b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.h
new file mode 100644
index 0000000..3a0366b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/image_transfer/bcmolt_image_transfer_user.h
@@ -0,0 +1,100 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+ */
+
+#ifndef _BCMOLT_IMAGE_TRANSFER_USER_H_
+#define _BCMOLT_IMAGE_TRANSFER_USER_H_
+
+#include <bcmolt_model_types.h>
+#include <bcmos_system.h>
+
+#define MFTP_DATA_BLOCK_SIZE  512
+
+#define MFTP_ACK_TIMEOUT_US   (2 * 1000000)
+
+#define MFTP_MAX_PATH_NAME_LEN  256
+
+/** Get the device file name.
+ * 
+ * \param[in]   image_type      Image type. defined in the object model.
+ * 
+ * \return      string          Device image name, including the path.
+ */
+const char *bcmuser_device_image_name_get(bcmolt_device_image_type image_type);
+
+/** Get the size of the file. 
+ * 
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * \param[out]  fsize           File size
+ * 
+ * \return      bcmos_errno 
+ */
+bcmos_errno bcmuser_image_transfer_file_size_get(bcmolt_devid device,
+    bcmolt_device_image_type image_type, uint32_t *fsize);
+
+/** Read data from given offset.
+ *
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * \param[in]   offset          File position indicator
+ * \param[out]  buf             Buffer to store the data
+ * \param[in]   buf_size        Buffer size
+ *
+ * \return      Bytes read
+ */
+int bcmuser_image_transfer_read_data(bcmolt_devid device, bcmolt_device_image_type image_type,
+    uint32_t offset, uint8_t *buf, uint32_t buf_size);
+
+/** Get the unit size of the transfer data block.
+ *
+ * \return      Size of the block in bytes.
+ */
+uint32_t bcmuser_image_transfer_block_size_get(void);
+
+/** Returns the CRC32 checksum of entire image.
+ * 
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model.
+ * 
+ * \return      CRC32 checksum 
+ */
+uint32_t bcmuser_image_transfer_crc_get(bcmolt_devid device, bcmolt_device_image_type image_type);
+
+/** Process the notification from the image transfer module.
+ *
+ * \param[in]   device          Device ID
+ * \param[in]   image_type      Image type. defined in the object model. 
+ * \param[in]   block_num       last block number 
+ * \param[in]   status          Image transfer status 
+ */
+void bcmuser_image_transfer_notification_rx(bcmolt_devid device,
+    bcmolt_device_image_type image_type, uint32_t block_num, bcmolt_image_transfer_status status);
+
+#endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/Makefile b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/Makefile
new file mode 100755
index 0000000..9756ee5
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/Makefile
@@ -0,0 +1,12 @@
+# ONU tuning application
+
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_user_appl_onu_tuning
+    MOD_TYPE = lib
+    MOD_DEPS = host_api common_gpon bcm_board
+    srcs = bcmolt_user_appl_onu_tuning.c bcmolt_user_appl_onu_tuning_cli.c
+    ifeq ("$(OS_KERNEL)", "linux")
+	MOD_DEPS += dev_log_linux
+    endif
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.c b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.c
new file mode 100755
index 0000000..8d3b97b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.c
@@ -0,0 +1,312 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmolt_host_api.h>
+#include "bcmolt_user_appl_onu_tuning.h"
+
+#define ONU_TUNING_TASK_MSG_Q_SIZE 256
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    bcmolt_auto *ind;
+    bcmolt_devid device_id;
+} ot_task_msg;
+
+static bcmolt_onu_tuning_context ot_context[BCMTR_MAX_OLTS];
+
+
+static bcmos_errno onu_tuning_init_task(bcmolt_devid device_id)
+{
+    bcmos_errno rc;
+    bcmos_task_parm task_params = {};
+    snprintf(ot_context[device_id].task.name, sizeof(ot_context[device_id].task.name), "user_appl_onu_tuning%u", device_id);
+    task_params.name = ot_context[device_id].task.name;
+    task_params.priority = TASK_PRIORITY_USER_APPL_ONU_TUNING;
+    task_params.core = BCMOS_CPU_CORE_ANY; /* No CPU affinity */
+    task_params.init_handler = NULL;
+    task_params.data = (long)device_id;
+    rc = bcmos_task_create(&ot_context[device_id].task, &task_params);
+    BCMOS_TRACE_CHECK_RETURN(rc, rc, "bcmos_task_create()\n");
+
+    return rc;
+}
+
+static bcmos_errno onu_tuning_init_module(bcmolt_devid device_id)
+{
+    /* This value is used inside bcmolt_onu_tuning_module_init for timer owner */
+    ot_context[device_id].device = device_id;
+    bcmos_module_parm module_params =
+    {
+        .qparm = { .name = "user_appl_onu_tuning",
+		.size = ONU_TUNING_TASK_MSG_Q_SIZE },
+    };
+
+    return bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_ONU_TUNING_DEV0 + device_id, &ot_context[device_id].task, &module_params);
+}
+
+void bcmolt_onu_tuning_appl_init(bcmolt_devid device_id)
+{
+    bcmos_errno err;
+
+#ifdef ENABLE_LOG
+    char log_name[MAX_DEV_LOG_ID_NAME] = {};
+    snprintf(log_name, sizeof(log_name) - 1, "user_appl_onu_tuning%u", device_id);
+    ot_context[device_id].log_id = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_DEBUG, DEV_LOG_ID_TYPE_BOTH);
+#endif
+
+    err = bcmos_mutex_create(&ot_context[device_id].mutex, 0, NULL);
+    BUG_ON(err != BCM_ERR_OK);
+
+    err = onu_tuning_init_task(device_id);
+    BUG_ON(err != BCM_ERR_OK);
+
+    err = onu_tuning_init_module(device_id);
+    BUG_ON(err != BCM_ERR_OK);
+
+}
+
+bcmos_errno bcmolt_onu_tuning_appl_start(bcmolt_devid device_id)
+{
+    bcmos_errno ret = BCM_ERR_OK;
+
+    bcmos_mutex_lock(&ot_context[device_id].mutex);
+
+    if (ot_context[device_id].is_running)
+    {
+        BCM_LOG(ERROR, ot_context[device_id].log_id, "ONU tuning application already running on device=%u\n", device_id);
+        ret = BCM_ERR_STATE;
+        goto exit;
+    }
+
+    ot_context[device_id].is_running = BCMOS_TRUE;
+    ot_context[device_id].device = device_id;
+
+    BCM_LOG(INFO, ot_context[device_id].log_id, "ONU tuning application started on device=%u\n", device_id);
+    ret = BCM_ERR_OK;
+
+exit:
+    bcmos_mutex_unlock(&ot_context[device_id].mutex);
+    return ret;
+}
+
+bcmos_errno bcmolt_onu_tuning_appl_stop(bcmolt_devid device_id)
+{
+    bcmos_errno ret;
+
+    bcmos_mutex_lock(&ot_context[device_id].mutex);
+
+    if (!ot_context[device_id].is_running)
+    {
+        ret = BCM_ERR_STATE;
+        goto exit;
+    }
+
+    ot_context[device_id].is_running = BCMOS_FALSE;
+    bcmos_timer_stop(&ot_context[device_id].timer);
+    BCM_LOG(INFO, ot_context[device_id].log_id, "ONU tuning application stopped on device=%u\n", device_id);
+    ret = BCM_ERR_OK;
+
+exit:
+    bcmos_mutex_unlock(&ot_context[device_id].mutex);
+    return ret;
+}
+
+bcmos_bool bcmolt_onu_tuning_appl_is_running(bcmolt_devid device_id)
+{
+    bcmos_bool ret;
+
+    bcmos_mutex_lock(&ot_context[device_id].mutex);
+    ret = ot_context[device_id].is_running;
+    bcmos_mutex_unlock(&ot_context[device_id].mutex);
+
+    return ret;
+}
+
+static bcmos_bool bcmolt_onu_tuning_is_interested(bcmolt_auto *ind)
+{
+    switch (ind->hdr.obj_type)
+    {
+    case BCMOLT_OBJ_ID_XGPON_ONU:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_XGPON_ONU_AUTO_ID_TUNING_RESPONSE:
+            return BCMOS_TRUE;
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_TUNING_IN_COMPLETED:
+		   return BCMOS_TRUE;
+	    default:
+		   break;
+        }
+        break;
+    default:
+        break;
+    }
+
+    return BCMOS_FALSE;
+}
+
+static void bcmolt_onu_tuning_ind_cb(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+	ot_task_msg *msg = (ot_task_msg *)os_msg;
+	bcmolt_pon_ni pon_ni;
+	bcmolt_pon_onu_id onu_id;
+	bcmolt_auto *ind = msg->ind;
+	bcmos_errno rc;
+
+	switch (msg->ind->hdr.obj_type)
+	{
+	case BCMOLT_OBJ_ID_XGPON_ONU:
+		switch (msg->ind->hdr.subgroup)
+		{
+			case BCMOLT_XGPON_ONU_AUTO_ID_TUNING_RESPONSE:
+			{
+				bcmolt_xgpon_onu_tuning_response *onu_tuning_response = (bcmolt_xgpon_onu_tuning_response *)ind;
+				onu_id = onu_tuning_response->key.onu_id;
+				pon_ni = onu_tuning_response->key.pon_ni;
+
+				if (onu_tuning_response->data.ack == BCMOS_TRUE && onu_tuning_response->data.result == BCMOLT_RESULT_SUCCESS)
+				{
+					BCM_LOG(INFO, ot_context[msg->device_id].log_id, "Indication BCMOLT_XGPON_ONU_AUTO_ID_TUNING_RESPONSE handled: PON:%u ONU:%u target_pon_ni=%u\n", pon_ni, onu_id, ot_context[msg->device_id].onu_db[onu_id].target_pon_ni);
+
+					/* start tuning in onu on target pon id */
+					bcmolt_xgpon_onu_key key = { .pon_ni = ot_context[msg->device_id].onu_db[onu_id].target_pon_ni, .onu_id = onu_id };
+					bcmolt_xgpon_onu_onu_tuning_in onu_tuning_in;
+
+					BCMOLT_OPER_INIT(&onu_tuning_in, xgpon_onu, onu_tuning_in, key);
+					rc = bcmolt_oper_submit(msg->device_id, &onu_tuning_in.hdr);
+					if (rc != BCM_ERR_OK)
+						BCM_LOG(ERROR, ot_context[msg->device_id].log_id, "ONU=%u tuning in to PON=%u failed\n", onu_id, ot_context[msg->device_id].onu_db[onu_id].target_pon_ni);
+				}
+
+				break;
+			}
+			case BCMOLT_XGPON_ONU_AUTO_ID_ONU_TUNING_IN_COMPLETED:
+			{
+				bcmolt_xgpon_onu_onu_tuning_in_completed *onu_tuning_in_completed = (bcmolt_xgpon_onu_onu_tuning_in_completed *)ind;
+				onu_id = onu_tuning_in_completed->key.onu_id;
+				pon_ni = onu_tuning_in_completed->key.pon_ni;
+				if (onu_tuning_in_completed->data.result == BCMOLT_RESULT_SUCCESS)
+				{
+					BCM_LOG(INFO, ot_context[msg->device_id].log_id, "Indication BCMOLT_XGPON_ONU_AUTO_ID_ONU_TUNING_IN_COMPLETED handled: PON:%u ONU:%u\n", pon_ni, onu_id);
+
+					/* start tuning out onu on source pon id */
+					bcmolt_xgpon_onu_key key = { .pon_ni = ot_context[msg->device_id].onu_db[onu_id].source_pon_ni, .onu_id = onu_id };
+					bcmolt_xgpon_onu_onu_tuning_out onu_tuning_out;
+
+					BCMOLT_OPER_INIT(&onu_tuning_out, xgpon_onu, onu_tuning_out, key);
+					BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, target_ds_pon_id, ot_context[msg->device_id].onu_db[onu_id].target_pon_id);
+					BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, target_us_pon_id, ot_context[msg->device_id].onu_db[onu_id].target_pon_id);
+					BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, time_to_switch, 1000);
+					BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, rollback, BCMOS_FALSE);
+					BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, status, BCMOLT_STATUS_OFF);
+					rc = bcmolt_oper_submit(msg->device_id, &onu_tuning_out.hdr);
+					if (rc != BCM_ERR_OK)
+						BCM_LOG(ERROR, ot_context[msg->device_id].log_id, "ONU=%u tuning out from PON=%u failed\n", onu_id, ot_context[msg->device_id].onu_db[onu_id].source_pon_ni);
+				}
+				break;
+			}
+			default:
+				break;
+		}
+	break;
+	default:
+		break;
+	}
+
+	/* free the cloned indication since we're done processing it */
+	bcmolt_msg_free(&msg->ind->hdr);
+	/* free the internal OS message handle */
+	bcmos_free(os_msg);
+}
+
+bcmos_errno bcmolt_onu_tuning_process_ind(bcmolt_devid device_id, bcmolt_auto *ind)
+{
+    bcmos_errno err;
+    bcmolt_msg *ind_clone = NULL;
+
+    /* if the application isn't running, don't listen to any indications */
+    if (!bcmolt_onu_tuning_appl_is_running(device_id))
+        return BCM_ERR_OK;
+
+    /* check to see if we have a handler for this type of indication */
+    if (!bcmolt_onu_tuning_is_interested(ind))
+        return BCM_ERR_OK;
+
+    /* clone the indication into newly-allocated memory so we can handle it after the original message has been freed */
+    err = bcmolt_msg_clone(&ind_clone, &ind->hdr);
+    if (err != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, ot_context[device_id].log_id, "Indication clone failed: %s\n", bcmos_strerror(err));
+        return err;
+    }
+
+    /* send the clone to the internal task's message queue */
+    ot_task_msg *msg = bcmos_calloc(sizeof(*msg));
+    if (msg == NULL)
+    {
+        BCM_LOG(ERROR, ot_context[device_id].log_id, "Message calloc failed: %s\n", bcmos_strerror(err));
+        bcmolt_msg_free(ind_clone);
+        return BCM_ERR_NOMEM;
+    }
+    msg->os_msg.handler = bcmolt_onu_tuning_ind_cb;
+    msg->os_msg.release = bcmolt_os_msg_release_cb;
+    msg->device_id = device_id;
+    msg->ind = (bcmolt_auto *)ind_clone;
+
+    err = bcmos_msg_send_to_module(TASK_PRIORITY_USER_APPL_ONU_TUNING + device_id, &msg->os_msg, 0);
+    if (err != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, ot_context[device_id].log_id, "Message send failed: %s\n", bcmos_strerror(err));
+        bcmolt_msg_free(ind_clone);
+        return err;
+    }
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno onu_tuning_update_onu_db (bcmolt_devid device_id, bcmolt_xgpon_onu_id onu_id, bcmolt_pon_ni source_pon_ni, bcmolt_pon_ni target_pon_ni, bcmolt_pon_id target_pon_id, bcmos_bool rollback)
+{
+	bcmos_errno ret;
+	 bcmos_mutex_lock(&ot_context[device_id].mutex);
+
+	    if (!ot_context[device_id].is_running)
+	    {
+	        ret = BCM_ERR_STATE;
+	        goto exit;
+	    }
+	    ot_context[device_id].onu_db[onu_id].target_pon_ni = target_pon_ni;
+	    ot_context[device_id].onu_db[onu_id].source_pon_ni = source_pon_ni;
+	    ot_context[device_id].onu_db[onu_id].target_pon_id = target_pon_id;
+	    ot_context[device_id].onu_db[onu_id].rollback = rollback;
+	    ret = BCM_ERR_OK;
+
+exit:
+	bcmos_mutex_unlock(&ot_context[device_id].mutex);
+	return ret;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.h b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.h
new file mode 100755
index 0000000..681fcc2
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning.h
@@ -0,0 +1,98 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_ONU_TUNING_H_
+#define _BCMOLT_USER_APPL_ONU_TUNING_H_
+
+#include <bcmolt_host_api.h>
+
+typedef struct
+{
+	bcmolt_pon_ni target_pon_ni;
+	bcmolt_pon_ni source_pon_ni;
+	bcmolt_pon_id target_pon_id;
+	uint32_t time_to_switch;
+	bcmos_bool rollback;
+}bcmolt_onu_tuning_onu_db;
+
+typedef struct
+{
+    bcmos_task task;
+    bcmos_msg_pool msg_pool;
+    bcmos_mutex mutex;
+    bcmos_timer timer;
+    dev_log_id log_id;
+    bcmos_bool is_running;
+    bcmolt_devid device;
+    uint32_t *timeouts;
+    bcmolt_onu_tuning_onu_db onu_db[XGPON_MAX_NUM_OF_ONUS];
+} bcmolt_onu_tuning_context;
+
+typedef enum
+{
+
+	ONU_TUNING_MODE,
+} bcmolt_onu_tuning_mode;
+
+/** Initialize the ONU Tuning application (this should be called as part of application startup). */
+void bcmolt_onu_tuning_appl_init(bcmolt_devid device_id);
+
+/** Start the ONU Tuning application.
+ *
+ * \param[in] device            The device ID on which the application will be started.
+ * \return        BCM_ERR_OK if the application was started successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_onu_tuning_appl_start(bcmolt_devid device_id);
+
+/** Stop the ONU Tuning application.
+ *
+ * \return        BCM_ERR_OK if the application was stopped successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_onu_tuning_appl_stop(bcmolt_devid device_id);
+
+/** Query whether the ONU Tuning application is currently running.
+ *
+ * \return        BCMOS_TRUE if the application is running, BCMOS_FALSE otherwise.
+ */
+bcmos_bool bcmolt_onu_tuning_appl_is_running(bcmolt_devid device_id);
+
+/** Process an indication received.  If the ONU Tuning application is interested in the indication, the
+ * appropriate action will be taken.  If the application is not interested, the indication will be ignored.
+ *
+ *
+ * Note: this function does not free the indication.
+ * The caller must free it using bcmos_msg_free() after calling this function.
+ *
+ * \param[in] ind The indication that was received.
+ * \return        BCM_ERR_OK if the indication was processed successfully or ignored, <0 otherwise.
+ */
+bcmos_errno bcmolt_onu_tuning_process_ind(bcmolt_devid device_id, bcmolt_auto *ind);
+bcmos_errno onu_tuning_update_onu_db (bcmolt_devid device_id, bcmolt_xgpon_onu_id onu_id, bcmolt_pon_ni source_pon_ni, bcmolt_pon_ni target_pon_ni, bcmolt_pon_id target_pon_id, bcmos_bool rollback);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.c b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.c
new file mode 100755
index 0000000..9d529e3
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.c
@@ -0,0 +1,187 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmolt_host_api.h>
+#include "bcmolt_user_appl_onu_tuning.h"
+#include "bcmolt_user_appl_onu_tuning_cli.h"
+#include <bcmolt_dev_selector.h>
+
+static bcmos_errno bcmolt_user_appl_onu_tuning_cli_create(bcmcli_entry *top_dir);
+
+static bcmcli_entry *onu_tuning_cli_dir;
+static bcmcli_entry *onu_tuning_cli_top_dir;
+static bcmos_bool onu_tuning_is_registered;
+
+/* Destroy CLI commands */
+static void onu_tuning_cli_destroy(void)
+{
+    if (onu_tuning_cli_dir)
+    {
+        bcmcli_token_destroy(onu_tuning_cli_dir);
+        onu_tuning_cli_dir = NULL;
+    }
+}
+
+/* Current device change indication */
+static void bcmolt_user_appl_onu_tuning_device_change_ind(bcmcli_session *session, bcmolt_devid dev)
+{
+    bcmolt_system_mode system_mode;
+    bcmos_errno err = bcmolt_system_mode_get(dev, &system_mode);
+
+    if (err != BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "Error device Id\n");
+        return;
+    }
+
+    if (system_mode == BCMOLT_SYSTEM_MODE_NGPON2__2_X_10_G)
+    {
+        bcmcli_session_print(session, "Building onu_tuning CLI for device %d\n", dev);
+
+        err = bcmolt_user_appl_onu_tuning_cli_create(onu_tuning_cli_top_dir);
+        if (err)
+        {
+            bcmcli_session_print(session, "Error building onu_tuning CLI\n");
+        }
+    }
+    else
+    {
+    	onu_tuning_cli_destroy();
+    }
+}
+
+static bcmos_errno onu_tuning_cmd_start(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    return bcmolt_onu_tuning_appl_start(current_device);
+}
+
+static bcmos_errno onu_tuning_cmd_stop(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    return bcmolt_onu_tuning_appl_stop(current_device);
+}
+
+static bcmos_errno onu_handover_cmd(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+	bcmolt_xgpon_onu_id onu_id;
+	bcmolt_pon_ni target_pon_ni;
+	bcmolt_pon_ni source_pon_ni;
+	uint32_t target_pon_administrative_label;
+	uint8_t target_pon_dwlch_id;
+	uint32_t time_to_switch;        /* Time to switch in ms */
+	bcmcli_cmd_parm *rollback_parm = bcmcli_find_named_parm(session, "rollback");
+	bcmos_bool rollback;
+	bcmos_errno rc = BCM_ERR_OK;
+	bcmolt_pon_id target_pon_id;
+
+
+	source_pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "source_pon_ni")->value.unumber;
+	target_pon_ni = (bcmolt_pon_ni)bcmcli_find_named_parm(session, "target_pon_ni")->value.unumber;
+	onu_id = (bcmolt_pon_onu_id)bcmcli_find_named_parm(session, "onu")->value.unumber;
+	target_pon_administrative_label = (uint32_t)bcmcli_find_named_parm(session, "target_pon_id_administrative_label")->value.unumber;
+	target_pon_dwlch_id = (uint8_t)bcmcli_find_named_parm(session, "target_pon_id_dwlch_id")->value.unumber;
+	target_pon_id.administrative_label = target_pon_administrative_label;
+	target_pon_id.dwlch_id = target_pon_dwlch_id;
+	time_to_switch = (uint32_t)bcmcli_find_named_parm(session, "time_to_switch")->value.unumber;
+
+	if (rollback_parm->value.unumber == 0)
+		rollback = BCMOS_FALSE;
+	else
+		rollback = BCMOS_TRUE;
+	/* update tuning in parameters in data base */
+	onu_tuning_update_onu_db(current_device, onu_id, source_pon_ni, target_pon_ni, target_pon_id, rollback);
+
+	bcmcli_session_print(session, "Tuning out ONU=%u source_pon_ni=%u target_pon_ni=%u \n", onu_id, source_pon_ni, target_pon_ni);
+
+	/* start tuning out onu on source pon id */
+	bcmolt_xgpon_onu_key key = { .pon_ni = source_pon_ni, .onu_id = onu_id };
+	bcmolt_xgpon_onu_onu_tuning_out onu_tuning_out;
+
+	BCMOLT_OPER_INIT(&onu_tuning_out, xgpon_onu, onu_tuning_out, key);
+	BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, target_ds_pon_id, target_pon_id);
+	BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, target_us_pon_id, target_pon_id);
+	BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, time_to_switch, time_to_switch);
+	BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, rollback, BCMOS_TRUE);
+	BCMOLT_OPER_PROP_SET(&onu_tuning_out, xgpon_onu, onu_tuning_out, status, BCMOLT_STATUS_ON);
+	rc = bcmolt_oper_submit(current_device, &onu_tuning_out.hdr);
+	if (rc != BCM_ERR_OK)
+		bcmcli_session_print(session, "ONU=%u tuning out from PON=%u failed\n", onu_id, source_pon_ni);
+
+	return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_user_appl_onu_tuning_cli_init(bcmcli_entry *top_dir)
+{
+    bcmos_errno err = BCM_ERR_OK;
+
+    /* Subscribe for device change indication */
+    if (!onu_tuning_is_registered)
+    {
+    	onu_tuning_is_registered = BCMOS_TRUE;
+
+        err = bcmolt_dev_sel_ind_register(bcmolt_user_appl_onu_tuning_device_change_ind);
+    }
+    return err ? err : bcmolt_user_appl_onu_tuning_cli_create(top_dir);
+}
+
+static bcmos_errno bcmolt_user_appl_onu_tuning_cli_create(bcmcli_entry *top_dir)
+{
+    bcmcli_entry *dir;
+
+    if (bcmcli_dir_find(top_dir, "onu_tuning"))
+    {
+        return BCM_ERR_OK;
+    }
+
+    dir = bcmcli_dir_add(
+        top_dir,
+        "onu_tuning",
+        "NGPON2 ONU tuning user application",
+        BCMCLI_ACCESS_ADMIN,
+        NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "start", "Start ONU tuning application", onu_tuning_cmd_start);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "stop", "Stop ONU tuning application", onu_tuning_cmd_stop);
+
+    BCMCLI_MAKE_CMD(dir, "onu_handover", "ONU handover", onu_handover_cmd,
+		BCMCLI_MAKE_PARM("onu", "ONU ID", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM("source_pon_ni", "Source PON_NI", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM("target_pon_ni", "Target PON_NI", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM("target_pon_id_administrative_label", "Target PON ID Administrative label", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM("target_pon_id_dwlch_id", "Target PON ID Channel id", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM("time_to_switch", "Time_To_Switch", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_NONE),
+		BCMCLI_MAKE_PARM_ENUM("rollback", "Rollback", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL));
+
+
+    onu_tuning_cli_dir = dir;
+    onu_tuning_cli_top_dir = top_dir;
+
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.h b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.h
new file mode 100755
index 0000000..ba9d764
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/ngpon2_onu_tuning/bcmolt_user_appl_onu_tuning_cli.h
@@ -0,0 +1,37 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_ONU_TUNING_CLI_H_
+#define _BCMOLT_USER_APPL_ONU_TUNING_CLI_H_
+
+#include <bcmolt_host_api.h>
+
+bcmos_errno bcmolt_user_appl_onu_tuning_cli_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/omon/Makefile b/bcm68620_release/release/host_reference/user_appl/omon/Makefile
new file mode 100644
index 0000000..e400562
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/omon/Makefile
@@ -0,0 +1,20 @@
+# EPON optical monitoring application
+
+ifeq ("$(ENABLE_CLI)", "y")
+
+	MOD_NAME = bcm_user_appl_omon
+	MOD_TYPE = lib
+	MOD_DEPS = host_api
+	ifneq ("$(SIMULATION_BUILD)", "y")
+		MOD_DEPS +=  bcm_board i2c_devs
+	endif
+
+	ifeq ("$(OS_KERNEL)", "linux")
+		MOD_DEPS += dev_log_linux
+	endif
+
+	ifneq ("$(SIMULATION_BUILD)", "y")
+		srcs = omon.c
+	endif
+
+endif
diff --git a/bcm68620_release/release/host_reference/user_appl/omon/omon.c b/bcm68620_release/release/host_reference/user_appl/omon/omon.c
new file mode 100644
index 0000000..f32ff7e
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/omon/omon.c
@@ -0,0 +1,1383 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcm_dev_log.h>
+#include <bcmolt_board.h>
+#include "omon.h"
+#include "bcmolt_utils.h"
+/* These macros need to be defined before the following include. */
+#define BCM_I2C_DEV_ADDR_START typedef enum {
+#define BCM_I2C_DEV_ADDR(name, desc, val) name = val,
+#define BCM_I2C_DEV_ADDR_END } bcm_i2c_dev_addr;
+#include "bcmolt_i2c_devs_addr.h"
+
+
+/* Fixed sample delay exceeds the sum of the maximum grant size and a worst case
+   retrieval time based on various optics modules' data sheets */
+#define OMON_FIXED_SAMPLE_DELAY_US      12000
+#define SFF_8472_IDENTIFIER_ADDR        0x00    /* SFP and XFP Identifier byte*/
+#define SFF_8077i_IDENTIFIER_ADDR       128     /* XFP Identifier byte */
+#define SFF_8472_RX_POWER_ADDR          104     /* SFP offeset into A2 */
+#define SFF_8077i_RX_POWER_ADDR         104     /* XFP byte 104/105 into A0  (yes A0) */
+#define SFF_8077i_VENDOR_NAME_ADDR      148     /* XFP bytes 163-148 into A0   */
+#define SFF_8077i_VENDOR_NAME_LEN       16      /* XFP name length  */
+#define SFF_8077i_VENDOR_PN_ADDR        168     /* XFP bytes 183-168 into A0   */
+#define SFF_8077i_VENDOR_PN_LEN         16      /* XFP part number length   */
+#define SFF_8077i_VENDOR_REV_ADDR       184     /* XFP bytes 185-184 into A0   */
+#define SFF_8077i_VENDOR_REV_LEN        2       /* XFP revision len   */
+#define SFF_8077i_VENDOR_SN_ADDR        196     /* XFP bytes 211-196 into A0   */
+#define SFF_8077i_VENDOR_SN_LEN         16      /* XFP serial number len   */
+#define OMON_TASK_MSG_Q_SIZE            16      /* allow one message per port */
+#define SFF_PAGE_ADDR1                  0       /* use SFP_I2C_ADDR1 for I2C read A0 */
+#define SFF_PAGE_ADDR2                  1       /* use SFP_I2C_ADDR2 for 12C read A2 */
+
+#define SFF_8472_VENDOR_NAME_ADDR      20      /* SFP bytes 20-35 into A0   */
+#define SFF_8472_VENDOR_NAME_LEN       16      /* SFP name length  */
+#define SFF_8472_VENDOR_OUI_ADDR       37      /* SFP bytes 37-39 into A0   */
+#define SFF_8472_VENDOR_OUI_LEN        3       /* SFP oui length  */
+#define SFF_8472_VENDOR_PN_ADDR        40      /* SFP bytes 40-55 into A0   */
+#define SFF_8472_VENDOR_PN_LEN         16      /* SFP part number length   */
+#define SFF_8472_VENDOR_REV_ADDR       56      /* SFP bytes 56-59 into A0   */
+#define SFF_8472_VENDOR_REV_LEN        4       /* SFP revision len   */
+#define SFF_8472_VENDOR_SN_ADDR        68      /* SFP bytes 68-83 into A0   */
+#define SFF_8472_VENDOR_SN_LEN         16      /* SFP serial number len   */
+#define SFF_8472_VENDOR_SFF_ADDR       94      /* SFP bytes 94 into A0   */
+#define SFF_8472_VENDOR_SFF_LEN        1       /* SFP SFF-8472 Compliance revision len   */
+
+#define SFF_8472_DIAG_MON_ADDR         92      /* SFP bytes 92 into A0   */
+#define SFF_8472_ENHANCED_OPT_ADDR     93      /* SFP bytes 93 into A0   */
+#define SFF_8472_TEMPERATURE_ADDR      96      /* SFP bytes 96 into A0   */
+#define SFF_8472_DIAG_MON_LEN          1       /* SFP SFF-8472 Diagnostic Monitoring Type len   */
+
+/* Table 32 5.2 Identifier values SFF8077i*/
+#define SFF_IDENTIFIER_SFP  0x03
+#define SFF_IDENTIFIER_XFP  0x06
+
+typedef uint32_t omon_rssi_value;
+
+
+/* Uniquely identifies specific EPON links under management by this
+   application */
+typedef struct
+{
+    bcmolt_devid device_id;
+    bcmolt_epon_ni epon_ni;
+    bcmos_mac_address mac_address;
+} omon_link_key;
+
+
+typedef struct
+{
+    uint32_t  caddr;    /* I2C switch control address */
+    uint32_t  sctrl;    /* I2C switch control command value */
+} i2c_coords;
+
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    omon_link_key link_key;
+    uint16_t trigger_width_ns;
+    uint16_t trigger_delay_ns;
+    uint16_t sample_period_us;
+    bcmcli_session *session;
+    bcmolt_epon_llid llid;
+    uint8_t scan_mode;
+} omon_task_msg;
+
+
+static dev_log_id omon_log_id = DEV_LOG_INVALID_ID;
+static bcmos_task omon_task;
+static bcmos_bool is_running = BCMOS_FALSE;
+
+
+/*******************************************************************************
+ * BCM968620 platform dependent I2C access
+ ******************************************************************************/
+
+
+uint8_t bcm968620_client_switchctrl_sfp[16][2] =
+{
+    {   0x72,     0x80, },
+    {   0x72,     0x40, },
+    {   0x72,     0x20, },
+    {   0x72,     0x10, },
+    {   0x72,      0x8, },
+    {   0x72,      0x4, },
+    {   0x72,      0x2, },
+    {   0x72,      0x1, },
+    {   0x73,     0x10, },
+    {   0x73,     0x20, },
+    {   0x73,     0x40, },
+    {   0x73,     0x80, },
+    {   0x73,      0x1, },
+    {   0x73,      0x2, },
+    {   0x73,      0x4, },
+    {   0x73,      0x8, },
+};
+
+
+uint8_t bcm968620_client_switchctrl_xfp[8][2] =
+{
+    /* caddr,    sctrl */
+    {   0x72,      0x8, },
+    {   0x72,      0x4, },
+    {   0x72,      0x2, },
+    {   0x72,      0x1, },
+    {   0x73,     0x10, },
+    {   0x73,     0x20, },
+    {   0x73,     0x40, },
+    {   0x73,     0x80, },
+};
+
+static bcmos_bool omon_is_epon_ni_valid(bcmolt_epon_ni epon)
+{
+    bcmolt_system_mode system_mode;
+
+    bcmolt_system_mode_get(current_device, &system_mode);
+
+    switch (system_mode)
+    {
+        case BCMOLT_SYSTEM_MODE_EPON__16_X:
+            return epon < 16;
+        case BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA:
+        case BCMOLT_SYSTEM_MODE_EPON__8_X_10_G:
+        case BCMOLT_SYSTEM_MODE_EPON__8_X:
+            return epon < 8;
+        case BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA:
+        case BCMOLT_SYSTEM_MODE_EPON__4_X_10_G:
+        case BCMOLT_SYSTEM_MODE_EPON__4_X:
+            return epon < 4;
+        case BCMOLT_SYSTEM_MODE_EPON__2_X_10_G:
+            return epon < 2;
+        default:
+            return BCMOS_FALSE;
+    }
+}
+
+static bcmos_bool trx_id_from_epon_ni(bcmolt_epon_ni epon, uint8_t *trx_id)
+{
+    static const uint8_t epon_4x[4] = { 2, 3, 6, 7 };
+    static const uint8_t epon_2x_10g[2] = { 3, 4 };
+    bcmolt_system_mode system_mode;
+
+    bcmolt_system_mode_get(current_device, &system_mode);
+
+    switch (system_mode)
+    {
+        case BCMOLT_SYSTEM_MODE_EPON__16_X:
+        case BCMOLT_SYSTEM_MODE_EPON__8_X:
+        case BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA:
+        case BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA:
+        case BCMOLT_SYSTEM_MODE_EPON__8_X_10_G:
+        case BCMOLT_SYSTEM_MODE_EPON__4_X_10_G:
+            *trx_id = (uint8_t)epon;
+            return BCMOS_TRUE;
+        case BCMOLT_SYSTEM_MODE_EPON__4_X:
+            *trx_id = epon_4x[epon];
+            return BCMOS_TRUE;
+        case BCMOLT_SYSTEM_MODE_EPON__2_X_10_G:
+            *trx_id = epon_2x_10g[epon];
+            return BCMOS_TRUE;
+        default:
+            *trx_id = (uint8_t)-1;
+            return BCMOS_FALSE;
+    }
+}
+
+static char * bcmolt_get_scan_result(bcmolt_rogue_scan_status status)
+{
+    switch (status)
+    {
+        case BCMOLT_ROGUE_SCAN_STATUS_COMPLETE:
+            return "OK: EPON ROGUE SCAN IS COMPLETE";
+        case BCMOLT_ROGUE_SCAN_STATUS_LLID_STATE_IS_BAD:
+            return "FAIL: REQUESTED LLID IS IN USE";
+        case BCMOLT_ROGUE_SCAN_STATUS_SCAN_ERR_INTERNAL:
+            return "FAIL: SCAN HAD AN INTERNAL SW ERROR";
+        case BCMOLT_ROGUE_SCAN_STATUS_SCAN_ERR_NORES:
+            return "FAIL: NO RESOURCES AVAILABLE";
+        case BCMOLT_ROGUE_SCAN_STATUS_LLID_IS_OOR:
+            return "FAIL: REQUESTED LLID IS OUT OF RANGE";
+        default:
+            return "OK: BCMOLT_ROGUE_SCAN_STATUS_COMPLETE";
+    }
+}
+
+/**
+ * \brief Get I2C address
+ *
+ * This function returns the I2C mux target address for the given link_key
+ * object.
+ *
+ * \param epon_ni EPON port index
+ * \param coords I2C mux target container
+ *
+ * \return
+ * None
+ */
+static
+void omon_get_i2c_coords(uint8_t trx_id, i2c_coords* coords)
+{
+    bcmolt_system_mode system_mode;
+
+    bcmolt_system_mode_get(0, &system_mode);
+
+    if (system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA)
+    {
+        coords->caddr  = bcm968620_client_switchctrl_xfp[trx_id][0];
+        coords->sctrl  = bcm968620_client_switchctrl_xfp[trx_id][1];
+    }
+    else
+    {
+        coords->caddr  = bcm968620_client_switchctrl_sfp[trx_id][0];
+        coords->sctrl  = bcm968620_client_switchctrl_sfp[trx_id][1];
+    }
+} /* omon_get_i2c_coords */
+
+
+/**
+ * \brief Configure I2C mux
+ *
+ * This function configures the I2C mux to target the correct optics module for
+ * the currently executing RSSI measurement.
+ *
+ * \param epon_ni EPON port index
+ *
+ * \return
+ * BCM_ERR_OK if successful, error code if not
+ */
+static
+bcmos_errno omon_platform_configure_i2c(uint8_t trx_id, uint8_t page)
+{
+
+     i2c_coords coords;
+     bcmos_errno rc;
+
+     omon_get_i2c_coords(trx_id, &coords);
+
+     rc = bcm_board_dev_change(coords.caddr);
+     if (rc != BCM_ERR_OK)
+     {
+          BCM_LOG(WARNING, omon_log_id,
+                  "bcm_board_dev_change(%u) failed\n", coords.caddr);
+     }
+     else
+     {
+         rc = bcm_board_switch_write(coords.sctrl);
+         if (rc != BCM_ERR_OK)
+         {
+              BCM_LOG(WARNING, omon_log_id,
+                      "bcm_board_switch_write(%u) failed\n", coords.sctrl);
+         }
+         else
+         {
+             if (page == SFF_PAGE_ADDR1)
+             {
+                 rc = bcm_board_dev_change(SFP_I2C_ADDR1);
+             }
+             else
+             {   // SFF_PAGE_ADDR2
+                 rc = bcm_board_dev_change(SFP_I2C_ADDR2);
+             }
+             if (rc != BCM_ERR_OK)
+             {
+                 BCM_LOG(WARNING, omon_log_id,
+                           "bcm_board_dev_change(%u) failed\n", (SFP_I2C_ADDR1+page));
+             }
+         }
+     }
+     return rc;
+
+} /* omon_platform_configure_i2c */
+
+
+/**
+ * \brief Read Manufacturing data from Optic Module
+ *
+ * This function reads the XFP Tranceiver Data (according to
+ * SFF8077i for an XFP) from an optics module. The result is
+ * displayed for this sample code.
+ *
+ * \param epon_ni EPON NI to take measurement
+ *
+ * \return
+ * BCM_ERR_OK if successful, error code if not
+ */
+static
+bcmos_errno omon_trx_status_read(bcmolt_epon_ni epon_ni, bcmcli_session *session)
+{
+    uint32_t sff_identifier = 1;
+    bcmos_errno rc = BCM_ERR_OK;
+    uint8_t i;
+
+    // 80771 XFP Specific stuff
+    uint32_t raw_vendorId_8077i;
+    uint8_t vendorId_8077i[SFF_8077i_VENDOR_NAME_LEN+1];
+    uint32_t raw_vendorPn_8077i;
+    uint8_t vendorPn_8077i[SFF_8077i_VENDOR_PN_LEN+1];
+    uint32_t raw_vendorSn_8077i;
+    uint8_t vendorSn_8077i[SFF_8077i_VENDOR_SN_LEN+1];
+    uint32_t raw_vendorRev_8077i;
+    uint8_t vendorRev_8077i[SFF_8077i_VENDOR_REV_LEN+1];
+
+    memset(&vendorId_8077i, 0, sizeof(vendorId_8077i));
+    memset(&vendorPn_8077i, 0, sizeof(vendorPn_8077i));
+    memset(&vendorSn_8077i, 0, sizeof(vendorSn_8077i));
+    memset(&vendorRev_8077i, 0, sizeof(vendorRev_8077i));
+
+    // 8472 SFP Specific stuff
+    uint32_t raw_vendorId_8472;
+    uint8_t vendorId_8472[SFF_8472_VENDOR_NAME_LEN+1];
+    uint32_t raw_vendorPn_8472;
+    uint8_t vendorPn_8472[SFF_8472_VENDOR_PN_LEN+1];
+    uint32_t raw_vendorSn_8472;
+    uint8_t vendorSn_8472[SFF_8472_VENDOR_SN_LEN+1];
+    uint32_t raw_vendorRev_8472;
+    uint8_t vendorRev_8472[SFF_8472_VENDOR_REV_LEN+1];
+    uint32_t raw_vendorSff_8472;
+    uint8_t vendorSff_8472[SFF_8472_VENDOR_SFF_LEN+1];
+    uint32_t raw_vendorOui_8472;
+    uint8_t vendorOui_8472[SFF_8472_VENDOR_OUI_LEN+1];
+    uint32_t raw_diagMon_8472;
+    uint8_t diagMon_8472 = 0;
+    uint32_t raw_enhancedOpt_8472;
+    uint8_t enhancedOpt_8472 = 0;
+
+    memset(&vendorId_8472, 0, sizeof(vendorId_8472));
+    memset(&vendorPn_8472, 0, sizeof(vendorPn_8472));
+    memset(&vendorSn_8472, 0, sizeof(vendorSn_8472));
+    memset(&vendorRev_8472, 0, sizeof(vendorRev_8472));
+    memset(&vendorSff_8472, 0, sizeof(vendorSff_8472));
+    memset(&vendorOui_8472, 0, sizeof(vendorOui_8472));
+
+    uint8_t* tmpresult;
+    int16_t temp_result = 0;
+    uint32_t raw_temp_result = 0;
+
+    uint8_t trx_id;
+
+    if ( ! trx_id_from_epon_ni(epon_ni, &trx_id) )
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "TRX Id is invalid for EPON NI %d \n", epon_ni);
+        return BCM_ERR_RANGE;
+    }
+
+    char *pSfpType;
+
+    pSfpType = "UNKN";
+
+
+    // Check for transevier presence
+    if ( (rc = omon_platform_configure_i2c(trx_id, SFF_PAGE_ADDR1)) == BCM_ERR_OK)
+    {
+        /* Read module identifier */
+        if ( (rc = bcm_board_dev_read(8, SFF_8472_IDENTIFIER_ADDR, &sff_identifier)) == BCM_ERR_OK)
+        {
+            switch (sff_identifier & 0x000F)
+            {
+                case SFF_IDENTIFIER_SFP:
+                    pSfpType = "SFP\0";
+                    // Read the vendor Id
+                    for (i=0; i<SFF_8472_VENDOR_NAME_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_NAME_ADDR+i, &raw_vendorId_8472);
+                        vendorId_8472[i] = (uint8_t)raw_vendorId_8472;
+                    }
+
+                    // Read the part Number, serial number, SFF revision, and Revision
+                    for (i=0; i<SFF_8472_VENDOR_PN_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_PN_ADDR+i, &raw_vendorPn_8472);
+                        vendorPn_8472[i] = (uint8_t)raw_vendorPn_8472;
+                    }
+
+                    for (i=0; i<SFF_8472_VENDOR_SN_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_SN_ADDR+i, &raw_vendorSn_8472);
+                        vendorSn_8472[i] = (uint8_t)raw_vendorSn_8472;
+                    }
+
+                    for (i=0; i<SFF_8472_VENDOR_REV_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_REV_ADDR+i, &raw_vendorRev_8472);
+                        vendorRev_8472[i] = (uint8_t)raw_vendorRev_8472;
+                    }
+
+                    for (i=0; i<SFF_8472_VENDOR_SFF_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_SFF_ADDR+i, &raw_vendorSff_8472);
+                        vendorSff_8472[i] = (uint8_t)raw_vendorSff_8472;
+                    }
+                    for (i=0; i<SFF_8472_VENDOR_OUI_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8472_VENDOR_OUI_ADDR+i, &raw_vendorOui_8472);
+                        vendorOui_8472[i] = (uint8_t)raw_vendorOui_8472;
+                    }
+
+                    rc = rc | bcm_board_dev_read(8, SFF_8472_DIAG_MON_ADDR, &raw_diagMon_8472);
+                    diagMon_8472 = (uint8_t)raw_diagMon_8472;
+
+                    rc = rc | bcm_board_dev_read(8, SFF_8472_ENHANCED_OPT_ADDR, &raw_enhancedOpt_8472);
+                    enhancedOpt_8472 = (uint8_t)raw_enhancedOpt_8472;
+
+                    // Shift to A2
+                    rc = rc | omon_platform_configure_i2c(trx_id, SFF_PAGE_ADDR2);
+                    rc = rc | bcm_board_dev_read(16, SFF_8472_TEMPERATURE_ADDR, &raw_temp_result);
+
+                    tmpresult = (uint8_t*)(&raw_temp_result);
+
+                    temp_result = tmpresult[3];
+                    temp_result = (temp_result << 8) | tmpresult[2];
+
+                    break;
+
+                case SFF_IDENTIFIER_XFP:
+
+                    pSfpType = "XFP\0";
+                    // Read the vendor Id
+                    for (i=0; i<SFF_8077i_VENDOR_NAME_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8077i_VENDOR_NAME_ADDR+i, &raw_vendorId_8077i);
+                        vendorId_8077i[i] = (uint8_t)raw_vendorId_8077i;
+                    }
+
+                    // Read the part Number, SN, and Revision
+                    for (i=0; i<SFF_8077i_VENDOR_PN_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8077i_VENDOR_PN_ADDR+i, &raw_vendorPn_8077i);
+                        vendorPn_8077i[i] = (uint8_t)raw_vendorPn_8077i;
+                    }
+
+                    for (i=0; i<SFF_8077i_VENDOR_SN_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8077i_VENDOR_SN_ADDR+i, &raw_vendorSn_8077i);
+                        vendorSn_8077i[i] = (uint8_t)raw_vendorSn_8077i;
+                    }
+
+                    for (i=0; i<SFF_8077i_VENDOR_REV_LEN; i++)
+                    {
+                        rc = rc | bcm_board_dev_read(8, SFF_8077i_VENDOR_REV_ADDR+i, &raw_vendorRev_8077i);
+                        vendorRev_8077i[i] = (uint8_t)raw_vendorRev_8077i;
+                    }
+
+                    break;
+                default:
+                    rc = BCM_ERR_NODEV;
+                    break;
+            }
+        }
+        else
+        {
+            BCM_LOG(ERROR, omon_log_id,
+                    "TRX Could not read device : SFF_8472_IDENTIFIER_ADDR for PON %d rc %d (%s)\n",
+                    epon_ni, rc, bcmos_strerror(rc));
+        }
+    }
+    else
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "TRX Could not configure platform : SFF_PAGE_ADDR1 for PON %d rc %d (%s)\n",
+                epon_ni, rc, bcmos_strerror(rc));
+        rc = BCM_ERR_NODEV;
+    }
+
+
+    if (rc == BCM_ERR_OK)
+    {
+        if ( (sff_identifier & 0x000F) == SFF_IDENTIFIER_XFP)
+        {
+        bcmcli_session_print(session,
+                             "\n[OMON] %s Tranceiver is present on PON %d\r\n"
+                             "  [OMON] Trx Vendor Id is: %s\r\n "
+                             " [OMON] Vendor Part Number: %s\r\n "
+                             " [OMON] Vendor Serial Number: %s\r\n "
+                             " [OMON] Vendor Revision: %s\r\n ",
+                                 pSfpType, epon_ni, vendorId_8077i,
+                                 vendorPn_8077i, vendorSn_8077i, vendorRev_8077i);
+        }
+        else
+        {
+            // SFP stuff
+            bcmcli_session_print(session,
+                                 "\n[OMON] %s Tranceiver is present on PON %d\r\n"
+                                 "  [OMON] Trx Vendor Id is: %s\r\n "
+                                 " [OMON] Vendor Part Number: %s\r\n "
+                                 " [OMON] Vendor Serial Number: %s\r\n "
+                                 " [OMON] Vendor Revision: %s\r\n "
+                                 " [OMON] Vendor OUI: %s\r\n "
+                                 " [OMON] Transceiver Temperature: %dC\r\n "
+                                 " [OMON] Enhanced Options SFF-8472 Byte 93: %x\r\n "
+                                 " [OMON] Diagnostic Monitor Type SFF-8472 Byte 92: %x\r\n ",
+                                 pSfpType, epon_ni, vendorId_8472,
+                                 vendorPn_8472, vendorSn_8472, vendorRev_8472, vendorOui_8472,
+                                 temp_result/256, enhancedOpt_8472, diagMon_8472 );
+        }
+    }
+
+    return rc;
+} /* omon_rssi_read */
+
+/**
+ * \brief Read RSSI results from I2C
+ *
+ * This function reads the RX Power Measurement (SFF8472 for an
+ * SFP and SFF8077i for an XFP) from an optics module. The
+ * result is stored in the global omon_state.
+ *
+ * \param epon_ni EPON NI to take measurement
+ * \param rssi_value Returned RSSI value if successful
+ *
+ * \return
+ * BCM_ERR_OK if successful, error code if not
+ */
+static
+bcmos_errno omon_rssi_read(bcmolt_epon_ni epon_ni, uint32_t *rssi_value)
+{
+    uint32_t raw_rssi_result = 0;
+    uint32_t sff_identifier = 1;
+    uint8_t trx_id;
+    uint32_t rssi_result = 0;
+    bcmos_errno rc = BCM_ERR_OK;
+    uint8_t* result;
+
+    if ( ! trx_id_from_epon_ni(epon_ni, &trx_id) )
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "TRX Id is invalid for EPON NI %d \n", epon_ni);
+        return BCM_ERR_RANGE;
+    }
+
+    // Check for transevier presence
+    if (bcm_board_trx_present(trx_id) == BCM_ERR_OK)
+    {
+        rc = omon_platform_configure_i2c(trx_id, SFF_PAGE_ADDR1);
+        /* Read module identifier */
+
+        if (rc == BCM_ERR_OK)
+        {
+            /* Read module identifier */
+            rc = bcm_board_dev_read(8, SFF_8472_IDENTIFIER_ADDR, &sff_identifier);
+
+            if (rc == BCM_ERR_OK)
+            {
+                result = (uint8_t*)(&raw_rssi_result);
+
+                switch (sff_identifier & 0x000F)
+                {
+                    case SFF_IDENTIFIER_SFP:
+                        rc = omon_platform_configure_i2c(trx_id, SFF_PAGE_ADDR2);
+                        if (rc == BCM_ERR_OK)
+                        {
+                            rc = bcm_board_dev_read(16, SFF_8472_RX_POWER_ADDR, &raw_rssi_result);
+                        }
+                        break;
+                    case SFF_IDENTIFIER_XFP:
+
+                        rc = bcm_board_dev_read(16, SFF_8077i_RX_POWER_ADDR, &raw_rssi_result);
+
+                        break;
+                    default:
+                        rc = BCM_ERR_NODEV;
+                        break;
+                }
+
+                if (rc == BCM_ERR_OK)
+                {
+                    rssi_result = result[3];
+                    rssi_result = (rssi_result << 8) | result[2];
+
+                    *rssi_value = rssi_result;
+
+                }
+            }
+            else
+            {
+                BCM_LOG(ERROR, omon_log_id,
+                        "TRX Could not read device : SFF_8472_IDENTIFIER_ADDR for PON %d rc %d (%s)\n",
+                        epon_ni, rc, bcmos_strerror(rc));
+                rc = BCM_ERR_IO;
+            }
+        }
+        else
+        {
+            BCM_LOG(ERROR, omon_log_id,
+                    "TRX Could not read platform : SFF_PAGE_ADDR1 for PON %d rc %d (%s)\n",
+                    epon_ni, rc, bcmos_strerror(rc));
+            rc = BCM_ERR_IO;
+        }
+    }
+    else
+    {
+        rc = BCM_ERR_NODEV;
+    }
+
+    return rc;
+} /* omon_rssi_read */
+
+
+/*******************************************************************************
+ * Platform independent RSSI measurement
+ ******************************************************************************/
+
+/**
+ * \brief Read XFP/SFP Data from Transceiver Module1
+ *
+ * This function reads the manufacturing data from the SFP
+ * module via I2C.
+ *
+ * \param link_key EPON NI
+ *
+ * \return
+ * None
+ */
+static
+void omon_trx_status_initiate(bcmcli_session *session,
+                                     omon_link_key *link_key)
+{
+
+    bcmos_errno rc;
+
+    /* Perform the I2C operations specified in SFF-8472 (SFP)/SFF-8077i (XFP) to
+       retrieve the pmanufacturer data from the optics module. */
+
+    if (! omon_is_epon_ni_valid(link_key->epon_ni))
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] TRX Status: epon_ni of (%d) is out of range for platform\n", link_key->epon_ni);
+        return ;
+    }
+    // Check for transceiver presence
+
+    uint8_t trx_id;
+
+    if ( ! trx_id_from_epon_ni(link_key->epon_ni, &trx_id) )
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] TRX Id is invalid for EPON NI %d \n", link_key->epon_ni);
+        return;
+    }
+
+    rc = bcm_board_trx_present(trx_id);
+
+    if (rc == BCM_ERR_NODEV)
+    {
+        bcmcli_session_print(session,
+                "[OMON] TRX Status: Trx_id %d Not Present on PON %d\r\n ", trx_id, link_key->epon_ni);
+        return;
+    }
+
+    rc = omon_trx_status_read(trx_id, session);
+
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] Issue TRX Status: Read operation failed with rc %d (%s)\n",
+                rc, bcmos_strerror(rc));
+        return;
+    }
+
+    /* Notify external host code of the outcome through an optional callback,
+       defaulting to output in the CLI session where the command was initiated
+       if no callback is supplied. */
+
+    bcmcli_session_print(session,
+                         "\n[OMON] TRX Status: Data on PON %d Complete\r\n", link_key->epon_ni);
+}
+/**
+ * \brief Read RSSI Measurement Result from Transceiver Module
+ *
+ * This function reads the stored RSSI measurement from the
+ * XFP/SFP module via I2C.
+ *
+ * \param link_key EPON NI
+ *
+ * \return
+ * None
+ */
+static
+void omon_rssi_read_initiate(bcmcli_session *session,
+                                     omon_link_key *link_key)
+{
+    uint32_t rssi_value = 0;
+    bcmos_errno rc;
+
+    if (! omon_is_epon_ni_valid(link_key->epon_ni))
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] Rssi Read: epon_ni of (%d) is out of range for platform\n", link_key->epon_ni);
+        return ;
+    }
+
+    /* Perform the I2C operations specified in SFF-8472 (SFP)/SFF-8077i (XFP) to
+       retrieve the power measurements from the optics module. */
+    rc = omon_rssi_read(link_key->epon_ni, &rssi_value);
+
+    if (rc == BCM_ERR_NODEV)
+    {
+        bcmcli_session_print(session,
+                "[OMON] Rssi Read: TRX Not Present on PON %d\r\n ", link_key->epon_ni);
+        return;
+    }
+
+    if (rc == BCM_ERR_IO)
+    {
+        bcmcli_session_print(session,
+            "[OMON] Rssi Read: Could not read device for PON %d \n",link_key->epon_ni);
+        return;
+    }
+
+    if (rc != BCM_ERR_OK)
+    {
+        bcmcli_session_print(session,
+                "Issue RSSI Read operation failed with rc %d (%s)\n",
+                rc, bcmos_strerror(rc));
+        return;
+    }
+    /* Notify external host code of the outcome through an optional callback,
+       defaulting to output in the CLI session where the command was initiated
+       if no callback is supplied. */
+
+    bcmcli_session_print(session,
+            "[OMON] RSSI Read RX Power: Pon_ni=%d rx power %d.%duW\r\n",
+            link_key->epon_ni,
+            rssi_value/10,
+            rssi_value%10);
+
+    bcmcli_session_print(session,
+                         "\n[OMON] RSSI Read on PON %d Complete\r\n",
+                         link_key->epon_ni);
+}
+/*omon_rssi_read_initiate*/
+
+
+/**
+ * \brief Request Maple Firmware to run the Rogue ONU LLID scan.
+ *
+ * This function sends an API message to the OLT to run a scan
+ * on a specific EPON LLID or across all LLIDs in the PON.
+ * Results are returned upon completion. Rogue LLIDs are marked
+ * and quarentined for the host to examine.
+ *
+ * \param link_key EPON link information
+ * \param epopn_llid  Specific LLID to scan
+ * \param scan_mode   Specifies a single LLID or All.
+ *
+ * \return
+ * None
+ */
+
+static
+void omon_run_rogue_llid_scan(bcmcli_session *session,
+                                     omon_link_key *link_key,
+                                     bcmolt_epon_llid llid,
+                                     uint8_t scan_mode)
+{
+    const bcmolt_epon_ni_key ni_key = { .epon_ni = link_key->epon_ni };
+    bcmolt_epon_ni_rogue_llid_scan issue_llid_scan_op;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    /* Perform an API operation directing the Maple firmware to issue an bcmolt_epon_ni_issue_rogue_rx_power
+       RSSI grant to the specified EPON link. BCMOLT_EPON_NI_OPER_ID_ISSUE_ROGUE_RX_POWER*/
+
+    BCMOLT_OPER_INIT(&issue_llid_scan_op, epon_ni, rogue_llid_scan , ni_key);
+    BCMOLT_OPER_PROP_SET(&issue_llid_scan_op, epon_ni, rogue_llid_scan, mode, scan_mode);
+    BCMOLT_OPER_PROP_SET(&issue_llid_scan_op, epon_ni, rogue_llid_scan, llid, llid);
+
+    BCM_LOG_CALLER_FMT(INFO, omon_log_id,
+            "Issue Rogue Scan Operation for LLID 0x%x, Pon_ni=%d\r\n", llid, link_key->epon_ni);
+
+    rc = bcmolt_oper_submit(link_key->device_id, &issue_llid_scan_op.hdr);
+
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "Rogue Scan Operation Failed with rc %d (%s)\n",
+                rc, bcmos_strerror(rc));
+
+        return;
+    }
+}/*omon_run_rogue_llid_scan*/
+
+/**
+ * \brief Execute RSSI measurement
+ *
+ * This function sends an API message to the OLT to issue an RSSI grant.  It
+ * then reads back the results via I2C. Set grant_length_tq to 0 to retreive the
+ * RSSI value without issuing the strobe command to the OLT.
+ *
+ * \param link_key EPON RSSI link information
+ * \param grant_length_tq Size of the RSSI grant
+ * \param start_offset Strobe offset from start of the RSSI grant
+ * \param end_offset Strobe offset from the end of the RSSI grant
+ *
+ * \return
+ * None
+ */
+static
+void omon_rssi_measurement_initiate(bcmcli_session *session,
+                                    omon_link_key *link_key,
+                                    uint16_t trigger_width_tq,
+                                    uint16_t trigger_delay_tq,
+                                    uint16_t sample_period)
+{
+    const bcmolt_epon_ni_key ni_key = { .epon_ni = link_key->epon_ni };
+    bcmolt_epon_ni_issue_rssi_grant issue_rssi_grant_op;
+    bcmos_errno rc = BCM_ERR_OK;
+
+    if (! omon_is_epon_ni_valid(link_key->epon_ni))
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] Rx Power: epon_ni of (%d) is out of range for platform\n", link_key->epon_ni);
+        return ;
+    }
+
+    uint8_t trx_id;
+
+    if ( ! trx_id_from_epon_ni(link_key->epon_ni, &trx_id) )
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "[OMON] Rx Power: TRX Id is invalid for EPON NI %d \n", link_key->epon_ni);
+        return;
+    }
+
+    // Check for transevier presence
+    rc = bcm_board_trx_present(trx_id);
+
+    if (rc == BCM_ERR_NODEV)
+    {
+        bcmcli_session_print(session,
+                "[OMON] Rx Power: TRX Not Present on PON %d\r\n ", link_key->epon_ni);
+        return;
+    }
+
+    /* Perform an API operation directing the Maple firmware to issue an bcmolt_epon_ni_issue_rogue_rx_power
+       RSSI grant to the specified EPON link. BCMOLT_EPON_NI_OPER_ID_ISSUE_ROGUE_RX_POWER*/
+
+    BCMOLT_OPER_INIT(&issue_rssi_grant_op, epon_ni, issue_rssi_grant , ni_key);
+    BCMOLT_OPER_PROP_SET(&issue_rssi_grant_op, epon_ni, issue_rssi_grant,
+                         granted_link, link_key->mac_address);
+    BCMOLT_OPER_PROP_SET(&issue_rssi_grant_op, epon_ni, issue_rssi_grant,
+                         trigger_width, trigger_width_tq);
+    BCMOLT_OPER_PROP_SET(&issue_rssi_grant_op, epon_ni, issue_rssi_grant,
+                         trigger_delay, trigger_delay_tq);
+    BCMOLT_OPER_PROP_SET(&issue_rssi_grant_op, epon_ni, issue_rssi_grant,
+                         sample_period, sample_period);
+
+    rc = bcmolt_oper_submit(link_key->device_id, &issue_rssi_grant_op.hdr);
+    if (rc != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, omon_log_id,
+                "issue RSSI grant operation failed with rc %d (%s)\n",
+                rc, bcmos_strerror(rc));
+
+        return;
+    }
+}
+/*omon_rssi_measurement_initiate*/
+
+// rssi specific indication handler interface --
+static bcmos_errno bcmolt_user_appl_rssi_handle_ind(bcmolt_devid device_id,
+                                                    uint8_t instance,
+                                                    bcmolt_auto *ind)
+{
+
+    bcmolt_epon_ni_rssi_measurement_completed* omon_ind;
+    uint32_t rssi_value = 0;
+    bcmolt_epon_ni epon_ni;
+    bcmos_errno rc = BCM_ERR_OK;
+    char tBuf[40];
+
+    omon_ind = (bcmolt_epon_ni_rssi_measurement_completed *) ind;
+    epon_ni = omon_ind->key.epon_ni;
+
+    if (omon_ind->data.status == BCMOLT_RESULT_SUCCESS)
+    {
+        /* Perform the I2C operations specified in SFF-8472 (SFP)/SFF-8077i (XFP) to
+           retrieve the power measurements from the optics module. */
+        rc = omon_rssi_read(epon_ni, &rssi_value);
+
+    }
+    /* Notify external host code of the outcome through an optional callback,
+       defaulting to output in the CLI session where the command was initiated
+       if no callback is supplied. */
+
+    BCM_LOG_CALLER_FMT(INFO, omon_log_id,
+            " RSSI Indication Rcv: for Link Mac %s, LLID 0x%x, Dev=%01d, Pon_ni=%d\r\n",
+                       bcmos_mac_2_str(&omon_ind->data.link_mac, tBuf),
+                         omon_ind->data.llid,
+                         device_id,
+                         omon_ind->key.epon_ni);
+
+    if (rc == BCM_ERR_OK)
+    {
+        BCM_LOG(INFO, omon_log_id,
+                 " %s measured rx power %d.%duW raw_data %d\n",
+                   (omon_ind->data.status == BCMOLT_RESULT_SUCCESS ? "Succeeded" : "Failed"),
+                   rssi_value/10, rssi_value%10, rssi_value);
+    }
+    else
+    {
+        BCM_LOG(INFO, omon_log_id,
+            "pon %d, failed with errno %d (%s)\n",
+                  epon_ni, rc, bcmos_strerror(rc));
+    }
+    return rc;
+}
+
+// epon rogue onu specific indication handler interface --
+static bcmos_errno bcmolt_user_appl_rogue_handle_ind(bcmolt_devid device_id,
+                                                     uint8_t instance,
+                                                     bcmolt_auto *ind)
+{
+    (void)instance;
+    bcmolt_epon_denied_link_rogue_violation* rogue_ind;
+    char tBuf[40];
+
+    rogue_ind = (bcmolt_epon_denied_link_rogue_violation *) ind;
+
+    /* Notify external host code of the outcome through an optional callback,
+       defaulting to output in the CLI session where the command was initiated
+       if no callback is supplied. */
+
+    BCM_LOG_CALLER_FMT(INFO, omon_log_id,
+            "Rogue ONU Scan Violation: AlmState = %s, Link Mac %s, LLID 0x%x, Range=%0d, Pon_ni=%2d\r\n",
+                       (rogue_ind->data.alarm_status.alarm_status == BCMOLT_STATUS_ON ? "ON" : "OFF"),
+                       bcmos_mac_2_str(&rogue_ind->key.mac_address, tBuf),
+                         rogue_ind->data.alarm_status.denied_llid,
+                         rogue_ind->data.alarm_status.denied_range,
+                         rogue_ind->key.epon_ni);
+
+    return BCM_ERR_OK;
+}
+
+// epon rogue scan complete specific indication handler interface --
+static bcmos_errno bcmolt_user_appl_rogue_complete_handle_ind(bcmolt_devid device_id,
+                                                              uint8_t instance,
+                                                              bcmolt_auto *ind)
+{
+    (void)instance;
+    bcmolt_epon_ni_rogue_scan_complete* rogue_ind;
+
+    rogue_ind = (bcmolt_epon_ni_rogue_scan_complete *) ind;
+
+    /* Notify external host code of the outcome through an optional callback,
+       defaulting to output in the CLI session where the command was initiated
+       if no callback is supplied. */
+
+    BCM_LOG_CALLER_FMT(INFO, omon_log_id,
+            "Rogue ONU scan complete for Pon %d, Status = %s\r\n",
+                       rogue_ind->key.epon_ni,
+                       bcmolt_get_scan_result(rogue_ind->data.return_ind_status));
+    return BCM_ERR_OK;
+}
+
+// public indication handler interface -- called in transport layer context
+bcmos_errno bcmolt_user_appl_omon_handle_ind(bcmolt_devid device_id, uint8_t instance, bcmolt_auto *ind)
+{
+
+    // Not an error, we just don't care about this indication.
+    bcmos_errno rc = BCM_ERR_OK;
+
+    // We look at message targetting epon ni.
+    if (ind->hdr.obj_type == BCMOLT_OBJ_ID_EPON_NI)
+    {
+        // We look at RSSI Completion event indications.
+        if (ind->hdr.subgroup == (uint16_t)BCMOLT_EPON_NI_AUTO_ID_RSSI_MEASUREMENT_COMPLETED)
+        {
+            rc = bcmolt_user_appl_rssi_handle_ind(device_id,instance,ind);
+        }
+        else
+        {
+            // We look for Rogue Scan Complete event indications.
+            if (ind->hdr.subgroup == (uint16_t)BCMOLT_EPON_NI_AUTO_ID_ROGUE_SCAN_COMPLETE)
+            {
+                rc = bcmolt_user_appl_rogue_complete_handle_ind(device_id, instance, ind);
+            }
+        }
+
+        if (rc != BCM_ERR_OK )
+        {
+            BCM_LOG_CALLER_FMT(INFO, omon_log_id,
+                    "OMON Handle Indication Error: Pon %d, Type %d, Rc %d\r\n",
+                               instance, ind->hdr.subgroup, rc);
+        }
+
+        // Not an error, we just don't care about this indication.
+        return BCM_ERR_OK;
+    }
+
+    // We look at message targetting epon denied links.
+    if (ind->hdr.obj_type == BCMOLT_OBJ_ID_EPON_DENIED_LINK)
+    {
+        // We look at Denied Link Events of this type.
+        if (ind->hdr.subgroup == BCMOLT_EPON_DENIED_LINK_AUTO_ID_ROGUE_VIOLATION)
+        {
+            rc = bcmolt_user_appl_rogue_handle_ind(device_id, instance, ind);
+        }
+        else
+        {
+            // Not an error, we just don't care about this object.
+            return BCM_ERR_OK;
+        }
+    }
+    return rc;
+
+} /* bcmolt_user_appl_omon_handle_ind */
+
+/**
+ * \brief Optical monitoring task message handler
+ *
+ * This function is the optical monitoring task handler.  At this time the only
+ * message is BCMOS_MSG_ID_INITIATE_RSSI_SAMPLE which starts the RSSI
+ * measurement on the OLT.
+ *
+ * \param module_id BCMOS_MODULE_ID_USER_APPL_OMON
+ * \param os_msg Message contents
+ */
+static
+void omon_os_msg_handle(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    omon_task_msg *msg = (omon_task_msg *)os_msg;
+
+    switch (msg->os_msg.type)
+    {
+    case BCMOS_MSG_ID_INITIATE_RX_POWER:
+            omon_rssi_measurement_initiate(msg->session,
+                                       &msg->link_key,
+                                       msg->trigger_width_ns ,
+                                       msg->trigger_delay_ns,
+                                       msg->sample_period_us);
+        break;
+    case BCMOS_MSG_ID_INITIATE_TRX_STATUS:
+
+            omon_trx_status_initiate(msg->session,
+                                     &msg->link_key);
+            break;
+        case BCMOS_MSG_ID_INITIATE_ROGUE_SCAN:
+            omon_run_rogue_llid_scan(msg->session,
+                                     &msg->link_key,
+                                     msg->llid,
+                                     msg->scan_mode);
+
+        break;
+    case BCMOS_MSG_ID_INITIATE_RSSI_READ:
+            omon_rssi_read_initiate(msg->session,
+                                     &msg->link_key);
+      break;
+    default:
+        break;
+    }
+    bcmos_free(os_msg);
+} /* omon_os_msg_handle */
+
+
+/**
+ * \brief Start the optical monitoring task
+ *
+ * This function starts the user level optical monitoring task and creates the
+ * optical monitoring module.  It should only be called once at start up.
+ *
+ * \return
+ * None
+ */
+void bcmolt_epon_omon_appl_init(void)
+{
+    bcmos_errno rc;
+    bcmos_task_parm task_params =
+    {
+        .name = "user_appl_omon",
+        .priority = TASK_PRIORITY_USER_APPL_OMON,
+        .core = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+    bcmos_module_parm module_params =
+    {
+        .qparm =
+        {
+            .name = "user_appl_omon",
+            .size = OMON_TASK_MSG_Q_SIZE
+        }
+    };
+
+    if (is_running)
+    {
+        return;
+    }
+
+    omon_log_id = bcm_dev_log_id_register("user_appl_omon",
+                                           DEV_LOG_LEVEL_INFO,
+                                           DEV_LOG_ID_TYPE_BOTH);
+    BUG_ON(DEV_LOG_INVALID_ID == omon_log_id);
+
+    rc = bcmos_task_create(&omon_task, &task_params);
+    BUG_ON(rc != BCM_ERR_OK);
+
+    rc = bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_OMON, &omon_task,
+                             &module_params);
+    BUG_ON(rc != BCM_ERR_OK);
+
+    is_running = BCMOS_TRUE;
+} /* bcmolt_epon_omon_appl_init */
+
+
+/*******************************************************************************
+ * CLI
+ ******************************************************************************/
+
+
+/**
+ * \brief RX power sample CLI command
+ *
+ * This function handles the "sample" CLI command.  It processes the CLI
+ * parameters and dispatches a start sample message to the optical monitoring
+ * task.
+ *
+ * \param session CLI session ID
+ * \param parm Command parameters
+ * \param n_parms Number of parameters
+ *
+ * \return
+ * BCM_ERR_OK if successful, error code if not
+ */
+static
+bcmos_errno omon_cmd_rx_power(bcmcli_session *session,
+                            const bcmcli_cmd_parm parm[],
+                            uint16_t n_parms)
+{
+    omon_link_key link_key;
+    bcmos_errno rc;
+    bcmcli_cmd_parm *epon_ni;
+    bcmcli_cmd_parm *mac_addr;
+    bcmcli_cmd_parm *Twidth_ns;
+    bcmcli_cmd_parm *Tdelay_ns;
+    bcmcli_cmd_parm *Tsample_us;
+    omon_task_msg *msg;
+
+    /* Get the CLI parameters. */
+    epon_ni    = bcmcli_find_named_parm(session, "epon_ni");
+    mac_addr   = bcmcli_find_named_parm(session, "granted_link");
+    Twidth_ns  = bcmcli_find_named_parm(session, "trigger_width");  // RSSI Twidth in ns
+    Tdelay_ns  = bcmcli_find_named_parm(session, "trigger_delay");  // RSSI Tdelay in ns
+    Tsample_us = bcmcli_find_named_parm(session, "sample_period");  // RSSI Tsample in us
+
+    link_key.device_id = current_device;
+    link_key.epon_ni = (bcmolt_epon_ni)epon_ni->value.number;
+    link_key.mac_address = (bcmos_mac_address)mac_addr->value.mac;
+
+    /* Send an indication to the internal task's message queue to be
+       processed. */
+    msg = bcmos_calloc(sizeof(*msg));
+    BUG_ON(msg == NULL);
+    msg->os_msg.type = BCMOS_MSG_ID_INITIATE_RX_POWER;
+    msg->os_msg.handler = omon_os_msg_handle;
+    msg->link_key = link_key;
+    msg->trigger_width_ns = Twidth_ns->value.number;
+    msg->trigger_delay_ns = Tdelay_ns->value.number;
+    msg->sample_period_us = Tsample_us->value.number;
+    msg->session = session;
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_OMON,
+                                  &msg->os_msg, 0);
+    BUG_ON(rc);
+
+
+    return rc;
+} /* omon_cmd_sample */
+
+static
+bcmos_errno omon_cmd_trx_data(bcmcli_session *session,
+                              const bcmcli_cmd_parm parm[],
+                              uint16_t n_parms)
+{
+    omon_link_key link_key;
+    bcmos_errno rc;
+    bcmcli_cmd_parm *epon_ni;
+    omon_task_msg *msg;
+
+    /* Get the CLI parameters. */
+    epon_ni = bcmcli_find_named_parm(session, "epon_ni");
+
+    link_key.device_id = current_device;
+    link_key.epon_ni = (bcmolt_epon_ni)epon_ni->value.number;
+
+    /* Send an indication to the internal task's message queue to be
+       processed. */
+    msg = bcmos_calloc(sizeof(*msg));
+    BUG_ON(msg == NULL);
+    msg->os_msg.type = BCMOS_MSG_ID_INITIATE_TRX_STATUS;
+    msg->os_msg.handler = omon_os_msg_handle;
+    msg->link_key = link_key;
+    msg->session = session;
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_OMON,
+                                  &msg->os_msg, 0);
+    BUG_ON(rc);
+
+
+    return rc;
+}
+/* omon_cmd_trx_data */
+
+static
+bcmos_errno omon_cmd_read_rssi_result(bcmcli_session *session,
+                              const bcmcli_cmd_parm parm[],
+                              uint16_t n_parms)
+{
+    omon_link_key link_key;
+    bcmos_errno rc;
+    bcmcli_cmd_parm *epon_ni;
+    omon_task_msg *msg;
+
+    /* Get the CLI parameters. */
+    epon_ni = bcmcli_find_named_parm(session, "epon_ni");
+
+    link_key.device_id = current_device;
+    link_key.epon_ni = (bcmolt_epon_ni)epon_ni->value.number;
+
+    /* Send an indication to the internal task's message queue to be
+       processed. */
+    msg = bcmos_calloc(sizeof(*msg));
+    BUG_ON(msg == NULL);
+    msg->os_msg.type = BCMOS_MSG_ID_INITIATE_RSSI_READ;
+    msg->os_msg.handler = omon_os_msg_handle;
+    msg->link_key = link_key;
+    msg->session = session;
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_OMON,
+                                  &msg->os_msg, 0);
+    BUG_ON(rc);
+
+
+    return rc;
+}
+/* omon_cmd_read_rssi_result */
+
+static
+bcmos_errno omon_cmd_llid_scan(bcmcli_session *session,
+                              const bcmcli_cmd_parm parm[],
+                              uint16_t n_parms)
+{
+    omon_link_key link_key;
+    bcmos_errno rc;
+    bcmcli_cmd_parm *epon_ni;
+    bcmcli_cmd_parm *epon_llid;
+    bcmcli_cmd_parm *epon_mode;
+    omon_task_msg *msg;
+
+    /* Get the CLI parameters. */
+    epon_ni = bcmcli_find_named_parm(session, "epon_ni");
+    epon_mode = bcmcli_find_named_parm(session, "scan_mode");
+    epon_llid = bcmcli_find_named_parm(session, "epon_llid");
+
+    link_key.device_id = current_device;
+    link_key.epon_ni = (bcmolt_epon_ni)epon_ni->value.number;
+
+    /* Send an indication to the internal task's message queue to be
+       processed. */
+    msg = bcmos_calloc(sizeof(*msg));
+    BUG_ON(msg == NULL);
+    msg->os_msg.type = BCMOS_MSG_ID_INITIATE_ROGUE_SCAN;
+    msg->os_msg.handler = omon_os_msg_handle;
+    msg->link_key = link_key;
+    msg->session = session;
+    msg->llid = epon_llid->value.number;
+    msg->scan_mode = epon_mode->value.number;
+    rc = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_OMON,
+                                  &msg->os_msg, 0);
+    BUG_ON(rc);
+
+
+    return rc;
+}
+
+
+/**
+ * \brief Install CLI commands
+ *
+ * This function creates and optical monitoring command directoy "omon" and
+ * installs optical monitoring CLI commands to this directory.
+ *
+ * \param top_dir Parent of "omon" directory
+ *
+ * \return
+ * BCM_ERR_OK if successful, error code if not
+ */
+void bcmolt_user_appl_epon_omon_cli_init(bcmcli_entry *top_dir)
+{
+    static const char *dir_name = "omon";
+
+    if (bcmcli_dir_find(top_dir, dir_name))
+    {
+        return;
+    }
+
+    bcmcli_entry *dir = bcmcli_dir_add(top_dir,
+                                       dir_name,
+                                       "EPON optical monitoring commands",
+                                       BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(dir == NULL);
+
+    BCMCLI_MAKE_CMD(dir, "rx_power", "Issue RX Power Measurement", omon_cmd_rx_power,
+            BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0),
+            BCMCLI_MAKE_PARM("granted_link", "Link mac address, 000000000000 for Idle Power", BCMCLI_PARM_MAC, 0),
+            BCMCLI_MAKE_PARM("trigger_width",
+                             "RSSI Trigger Width (Tw) in ns, Desired width of RSSI Trigger based on the Optical Module Specifications."
+                             "This is a mandatory parameter used for all RX Power measurements ( including Idle power) and must not be 0."
+                             "Note: The granularity of the device is 1 TQ (16ns) the input will be rounded up to the next TQ."
+                             , BCMCLI_PARM_NUMBER, 0),
+            BCMCLI_MAKE_PARM("trigger_delay",
+                             "RSSI Trigger Delay (Td) in ns, Desired delay to meet the trigger delay timing requirement of the Optical Module Specifications."
+                             "The rssi trigger delay is measured from the start of sync_time and is adjusted as needed. "
+                             "The trigger delay moves the assertion of the RSSI Trigger strobe into the grant window."
+                             "Note: The granularity of the device is 1 TQ (16ns) the input will be rounded up to the next TQ."
+                             , BCMCLI_PARM_NUMBER, 0),
+            BCMCLI_MAKE_PARM("sample_period",
+                             "(Ti2c) Sample period in uS, internal I2C hold/prohibit time where access to device is not allowed."
+                             "During this period the internal RSSI data is invalid and I2C opertions on this device must not be executed."
+                             "A value of 500uS is recommended for most applications. "
+                             , BCMCLI_PARM_NUMBER, 0));
+
+    BCMCLI_MAKE_CMD(dir, "trx_data", "Read Tranceiver Data", omon_cmd_trx_data,
+            BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0));
+
+    BCMCLI_MAKE_CMD(dir, "read_rssi", "Read RSSI Result from Tranceiver", omon_cmd_read_rssi_result,
+            BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0));
+
+    BCMCLI_MAKE_CMD(dir, "llid_scan", "Run Rogue ONU LLID Scan", omon_cmd_llid_scan,
+            BCMCLI_MAKE_PARM("epon_ni", "EPON NI", BCMCLI_PARM_NUMBER, 0),
+            BCMCLI_MAKE_PARM("scan_mode", "LLID scan mode 1 for full pon scan, 0 for targeted (llid required for targeted)", BCMCLI_PARM_NUMBER, 0),
+            BCMCLI_MAKE_PARM("epon_llid", "EPON llid to scan when mode=0", BCMCLI_PARM_HEX, 0));
+}
+/* bcmolt_user_appl_epon_omon_cli_init */
+
+
+/* End of file omon.c */
+
diff --git a/bcm68620_release/release/host_reference/user_appl/omon/omon.h b/bcm68620_release/release/host_reference/user_appl/omon/omon.h
new file mode 100644
index 0000000..0e31b7a
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/omon/omon.h
@@ -0,0 +1,69 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _OMON_H_
+#define _OMON_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcmolt_utils.h>
+#include <bcm_dev_log.h>
+
+
+
+/**
+ * \brief Start the optical monitoring task
+ *
+ * This function starts the user level optical monitoring task and creates the
+ * optical monitoring module.  It should only be called once at start up.
+ *
+ * \return
+ * None
+ */
+extern
+void bcmolt_epon_omon_appl_init (void);
+
+
+/**
+ * \brief Install CLI commands
+ *
+ * This function creates and optical monitoring command directoy "omon" and
+ * installs optical monitoring CLI commands to this directory.
+ *
+ * \param top_dir Parent of "omon" directory
+ */
+
+void bcmolt_user_appl_epon_omon_cli_init(bcmcli_entry *top_dir);
+
+
+bcmos_errno bcmolt_user_appl_omon_handle_ind(bcmolt_devid device_id, uint8_t instance, bcmolt_auto *ind);
+
+
+#endif /* End of file omon.h */
diff --git a/bcm68620_release/release/host_reference/user_appl/playback/Makefile b/bcm68620_release/release/host_reference/user_appl/playback/Makefile
new file mode 100644
index 0000000..0374028
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/playback/Makefile
@@ -0,0 +1,12 @@
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_user_appl_playback
+    MOD_TYPE = lib
+    MOD_DEPS = utils dev_log common_api transport
+    srcs = bcmolt_user_appl_playback.c
+    ifeq ("$(OS_KERNEL)", "linux")
+	MOD_DEPS += dev_log_linux
+    endif
+
+    USE_LINT=yes
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.c b/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.c
new file mode 100644
index 0000000..a3b463b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.c
@@ -0,0 +1,1573 @@
+/*
+  <: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.
+
+  :>
+*/
+
+#include "bcmos_system.h"
+#include "bcm_dev_log.h"
+#include "bcmolt_api.h"
+#include "bcm_api_cli_helpers.h"
+#include "bcmolt_model_types.h"
+#include "bcmtr_debug.h"
+#include "bcmtr_interface.h"
+#include "bcmolt_user_appl_playback.h"
+#include "bcmolt_bit_utils.h"
+
+/* 'MPBx' in ASCII (M)aple (P)lay(b)ack version x */
+#define VERSION0    0x4d504230
+#define VERSION1    0x4d504231
+
+static const uint32_t CHUNK_SIZE = 2000;
+
+static const char *cap_loc_str[BCMOLT_API_CAPTURE_LOCATION__NUM_OF] =
+{
+    "device",
+    "host"
+};
+
+typedef struct
+{
+    dev_log_id log_id;
+} playback_context;
+
+static playback_context pb_ctxt[BCMTR_MAX_OLTS];
+
+typedef enum
+{
+    PB_FORMAT_API_CLI,
+    PB_FORMAT_C_API,
+
+    PB_FORMAT__COUNT
+} pb_format;
+
+typedef enum
+{
+    PB_CLI_EXTRA_NONE,
+    PB_CLI_EXTRA_MULTI,
+    PB_CLI_EXTRA_STAT,
+    PB_CLI_EXTRA_SUBGROUP
+} pb_cli_extra;
+
+typedef enum
+{
+    PB_FIELDS_NONE,
+    PB_FIELDS_GET,
+    PB_FIELDS_MULTI,
+    PB_FIELDS_SET
+} pb_fields;
+
+typedef struct
+{
+    uint32_t version;
+    bcmolt_api_capture_location location;
+    bcmolt_firmware_sw_version fw_ver;
+    bcmolt_host_sw_version host_ver;
+    uint32_t buf_size;
+    void *capture_buffer;
+} bcmolt_playback;
+
+typedef struct
+{
+    bcmolt_buf buf;
+    bcmtr_capture_entry hdr;
+    uint8_t *msg_start;
+} playback_iterator_raw;
+
+typedef struct
+{
+    playback_iterator_raw raw;
+    bcmolt_buf msg_buf;
+    bcmolt_msg *msg;
+    bcmos_errno err;
+} playback_iterator;
+
+#define FOR_EACH_CAPTURE_ENTRY(it, buffer, size) \
+    bcmolt_buf_init(&(it).buf, size, buffer, BCMOLT_BUF_ENDIAN_FIXED); \
+    while (bcmtr_capture_entry_get_next(&(it).buf, &(it).hdr, &(it).msg_start))
+
+#define FOR_EACH_PLAYBACK_MSG(it, buffer, size) \
+    bcmolt_buf_init(&(it).raw.buf, size, buffer, BCMOLT_BUF_ENDIAN_FIXED); \
+    (it).msg = NULL; \
+    while (playback_next_entry_unpacked(&(it)))
+
+#define DEVICE_REF "device_id"
+#define RC_REF "rc"
+#define VAL_REF "val"
+#define MSG_REF "msg"
+#define KEY_REF "key"
+#define LIST_MEM_REF "list_mem"
+#define MSG_SET_REF "msg_set"
+#define MAX_MSGS_REF "max_msgs"
+#define INVERT_FILTER_REF "invert_filter"
+#define STAT_FLAGS_REF "stat_flags"
+#define DYN_LIST_SIZE "APICLI_DYNAMIC_LIST_BUFFER_SIZE"
+
+static void playback_iterator_cleanup(playback_iterator *it)
+{
+    if ((it->err == BCM_ERR_OK) && (it->msg != NULL))
+    {
+        bcmolt_msg_free(it->msg);
+        it->msg = NULL;
+    }
+}
+
+static bcmos_bool playback_next_entry_unpacked(playback_iterator *it)
+{
+    playback_iterator_cleanup(it);
+    bcmos_bool ret = bcmtr_capture_entry_get_next(&it->raw.buf, &it->raw.hdr, &it->raw.msg_start);
+    if (ret)
+    {
+        bcmolt_buf_init(&it->msg_buf, it->raw.hdr.msg_size, it->raw.msg_start, BCMOLT_BUF_ENDIAN_FIXED);
+        it->err = bcmolt_msg_unpack(&it->msg_buf, &it->msg);
+    }
+    return ret;
+}
+
+static bcmos_errno playback_read_block(uint32_t offset, uint32_t size, uint8_t *buf)
+{
+    bcmos_errno err;
+    bcmolt_debug_cfg debug_cfg;
+    bcmolt_debug_key debug_key = { };
+    bcmolt_api_capture_buffer_reader reader = { .offset = offset, .size = size };
+
+    BCMOLT_CFG_INIT(&debug_cfg, debug, debug_key);
+    BCMOLT_CFG_PROP_SET(&debug_cfg, debug, api_capture_buffer_read, reader);
+    err = bcmolt_cfg_set(current_device, &debug_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Failed to update reader (%u, %u)!\n", offset, size);
+    }
+    else
+    {
+        BCMOLT_CFG_INIT(&debug_cfg, debug, debug_key);
+        BCMOLT_CFG_PROP_GET(&debug_cfg, debug, api_capture_buffer);
+        debug_cfg.data.api_capture_buffer.val = buf;
+        err = bcmolt_cfg_get(current_device, &debug_cfg.hdr);
+        if (BCM_ERR_OK != err)
+        {
+            BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Failed to retrieve capture buffer chunk (%u, %u)!\n",
+                offset, size);
+        }
+    }
+
+    return err;
+}
+
+static bcmos_errno playback_read(void **buffer, uint32_t *length, bcmolt_api_capture_location *capture_location)
+{
+    bcmos_errno err;
+    bcmolt_debug_cfg debug_cfg;
+    bcmolt_debug_key debug_key = { };
+
+    if ((buffer == NULL) || (length == NULL))
+    {
+        BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "buffer (%p) and length (%p) cannot be NULL\n", buffer, length);
+    }
+
+    BCMOLT_CFG_INIT(&debug_cfg, debug, debug_key);
+    BCMOLT_CFG_PROP_GET(&debug_cfg, debug, api_capture_stats);
+    BCMOLT_CFG_PROP_GET(&debug_cfg, debug, api_capture_cfg);
+    err = bcmolt_cfg_get(current_device, &debug_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Failed to retrieve capture stats!\n");
+    }
+    else
+    {
+        void *capture_buffer;
+        uint32_t buf_size = debug_cfg.data.api_capture_stats.readable_bytes;
+        uint32_t offset = 0;
+
+        *capture_location = debug_cfg.data.api_capture_cfg.location;
+
+        BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Retrieving %u byte buffer\n", buf_size);
+        capture_buffer = bcmos_alloc(buf_size);
+        if (NULL != capture_buffer)
+        {
+            while ((offset + CHUNK_SIZE) < buf_size)
+            {
+                BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Reading bytes %u - %u\n", offset, offset + CHUNK_SIZE);
+                err = playback_read_block(offset, CHUNK_SIZE, (uint8_t*)capture_buffer + offset);
+                if (BCM_ERR_OK != err)
+                {
+                    break;
+                }
+                offset += CHUNK_SIZE;
+            }
+
+            if (BCM_ERR_OK == err)
+            {
+                BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Reading bytes %u - %u\n", offset, buf_size);
+                err = playback_read_block(offset, buf_size - offset, (uint8_t*)capture_buffer + offset);
+            }
+        }
+
+        *buffer = capture_buffer;
+        *length = buf_size;
+    }
+
+    return err;
+}
+
+static bcmos_errno playback_dump(bcmcli_session *session, void *capture_buf, uint32_t buf_size)
+{
+    playback_iterator it;
+    bcmos_errno err = BCM_ERR_OK;
+
+    FOR_EACH_PLAYBACK_MSG(it, capture_buf, buf_size)
+    {
+        BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Dumping message at %u of %u (%u)\n",
+                bcmolt_buf_get_used(&it.raw.buf), buf_size, it.raw.hdr.msg_size);
+        bcmcli_session_print(session, "\n%08x %u:\n", it.raw.hdr.timestamp, it.raw.hdr.event);
+        if (BCM_ERR_OK == it.err)
+        {
+            err = apicli_msg_dump(session, it.msg);
+            BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Dump status: %s\n", bcmos_strerror(err));
+        }
+        else
+        {
+            BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Unpacking failed: %s\n", bcmos_strerror(err));
+            if ((it.raw.msg_start + it.raw.hdr.msg_size) > (it.raw.buf.start + it.raw.buf.len))
+            {
+                bcmcli_session_print(session, "Message length is insane!\n");
+            }
+            else
+            {
+                bcmcli_session_hexdump(session, it.raw.msg_start, 0, it.raw.hdr.msg_size, NULL);
+            }
+        }
+    }
+
+    return err;
+}
+
+static bcmos_bool playback_should_send(bcmolt_api_capture_location capture_location, bcmtr_cld_event_type event_type)
+{
+    switch (capture_location)
+    {
+        case BCMOLT_API_CAPTURE_LOCATION_DEVICE:
+            switch (event_type)
+            {
+                case BCMTR_CLD_EV_RECV:
+                case BCMTR_CLD_EV_RECV_DISCARD:
+                    return BCMOS_TRUE;
+                default:
+                    return BCMOS_FALSE;
+            }
+        case BCMOLT_API_CAPTURE_LOCATION_HOST:
+            switch (event_type)
+            {
+                case BCMTR_CLD_EV_SEND:
+                case BCMTR_CLD_EV_RESEND:
+                    return BCMOS_TRUE;
+                default:
+                    return BCMOS_FALSE;
+            }
+        default:
+            return BCMOS_FALSE;
+    }
+}
+
+/*lint -e{429} */
+static bcmos_errno playback_replay(
+    bcmolt_devid device,
+    void *capture_buf,
+    uint32_t buf_size,
+    bcmolt_api_capture_location location,
+    bcmos_bool keep_time)
+{
+    bcmos_errno err = BCM_ERR_OK;
+    playback_iterator_raw it;
+    uint32_t last_time_us = 0;
+    bcmos_bool first = BCMOS_TRUE;
+
+    FOR_EACH_CAPTURE_ENTRY(it, capture_buf, buf_size)
+    {
+        bcmolt_buf msg_buf;
+        bcmolt_msg *msg = NULL;
+
+        BCM_LOG(DEBUG, pb_ctxt[device].log_id, "Processing message (%u,%u,%u)\n",
+                it.hdr.event, it.hdr.timestamp, it.hdr.msg_size);
+        if (playback_should_send(location, (bcmtr_cld_event_type)it.hdr.event))
+        {
+            if (keep_time)
+            {
+                if (!first)
+                {
+                    /* approximate original timing; doesn't account for processing time in this code - this could be
+                       improved */
+                    BCM_LOG(DEBUG, pb_ctxt[device].log_id, "Sleeping for %u us\n", it.hdr.timestamp - last_time_us);
+                    bcmos_usleep(it.hdr.timestamp - last_time_us);
+                }
+                else
+                {
+                    first = BCMOS_FALSE;
+                }
+                last_time_us = it.hdr.timestamp;
+            }
+            BCM_LOG(DEBUG, pb_ctxt[device].log_id, "Unpacking message\n");
+            bcmolt_buf_init(&msg_buf, it.hdr.msg_size, it.msg_start, BCMOLT_BUF_ENDIAN_FIXED);
+            err = bcmolt_msg_unpack(&msg_buf, &msg);
+            if (BCM_ERR_OK == err)
+            {
+                BCM_LOG(DEBUG, pb_ctxt[device].log_id, "Sending message\n");
+                err = bcmtr_send(device, msg, BCMTR_SEND_FLAGS_NONE);
+                bcmolt_msg_free(msg);
+                if (BCM_ERR_OK != err)
+                {
+                    BCM_LOG(INFO, pb_ctxt[device].log_id, "Sending failed: %s\n", bcmos_strerror(err));
+                    return err;
+                }
+            }
+            else
+            {
+                BCM_LOG(INFO, pb_ctxt[device].log_id, "Unpacking failed: %s\n", bcmos_strerror(err));
+                return err;
+            }
+        }
+    }
+
+    return err;
+}
+
+static bcmos_errno playback_cli_dump(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    bcmos_errno err;
+    void *capture_buffer;
+    uint32_t buf_size;
+    bcmolt_api_capture_location location;
+
+    err = playback_read(&capture_buffer, &buf_size, &location);
+
+    if (BCM_ERR_OK == err)
+    {
+        bcmcli_print(session, "Capture from %s:\n", cap_loc_str[location]);
+        playback_dump(session, capture_buffer, buf_size);
+    }
+
+    bcmos_free(capture_buffer);
+
+    return err;
+}
+
+static void playback_fw_version_get(bcmolt_devid device, bcmolt_firmware_sw_version *fw)
+{
+    bcmos_errno err;
+    bcmolt_device_cfg dev_cfg;
+    bcmolt_device_key dev_key = { };
+
+    BCMOLT_CFG_INIT(&dev_cfg, device, dev_key);
+    BCMOLT_CFG_PROP_GET(&dev_cfg, device, firmware_sw_version);
+    err = bcmolt_cfg_get(device, &dev_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        BCM_LOG(WARNING, pb_ctxt[device].log_id, "Failed to retrieve fw version!\n");
+    }
+    else
+    {
+        *fw = dev_cfg.data.firmware_sw_version;
+    }
+}
+
+static void playback_host_version_get(bcmolt_devid device, bcmolt_host_sw_version *host)
+{
+    bcmos_errno err;
+    bcmolt_device_cfg dev_cfg;
+    bcmolt_device_key dev_key = { };
+
+    BCMOLT_CFG_INIT(&dev_cfg, device, dev_key);
+    BCMOLT_CFG_PROP_GET(&dev_cfg, device, host_sw_version);
+    err = bcmolt_cfg_get(device, &dev_cfg.hdr);
+    if (BCM_ERR_OK != err)
+    {
+        BCM_LOG(WARNING, pb_ctxt[device].log_id, "Failed to retrieve host version!\n");
+    }
+    else
+    {
+        *host = dev_cfg.data.host_sw_version;
+    }
+}
+
+static bcmos_errno playback_cli_save(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    const char *filename = bcmcli_find_named_parm(session, "file")->value.string;
+    bcmolt_playback mpb = {};
+    bcmos_errno err;
+    FILE *file;
+    uint32_t temp;
+
+    err = playback_read(&mpb.capture_buffer, &mpb.buf_size, &mpb.location);
+
+    if (BCM_ERR_OK == err)
+    {
+        playback_fw_version_get(current_device, &mpb.fw_ver);
+        playback_host_version_get(current_device, &mpb.host_ver);
+
+        file = fopen(filename, "wb");
+        /* write file version */
+        temp = BCMOLT_BUF_ENDIAN_CPU_TO_BUF(U32, VERSION1);
+        fwrite(&temp, sizeof(uint32_t), 1, file);
+        /* write capture location */
+        temp = BCMOLT_BUF_ENDIAN_CPU_TO_BUF(U32, (uint32_t)mpb.location);
+        fwrite(&temp, sizeof(uint32_t), 1, file);
+        /* write firmware version */
+        fwrite(&mpb.fw_ver.major, sizeof(uint8_t), 1, file);
+        fwrite(&mpb.fw_ver.minor, sizeof(uint8_t), 1, file);
+        fwrite(&mpb.fw_ver.revision, sizeof(uint8_t), 1, file);
+        temp = BCMOLT_BUF_ENDIAN_CPU_TO_BUF(U32, mpb.fw_ver.model);
+        fwrite(&temp, sizeof(uint32_t), 1, file);
+        fwrite(mpb.fw_ver.build_time, sizeof(mpb.fw_ver.build_time), 1, file);
+        /* write host version */
+        fwrite(&mpb.host_ver.major, sizeof(uint8_t), 1, file);
+        fwrite(&mpb.host_ver.minor, sizeof(uint8_t), 1, file);
+        fwrite(&mpb.host_ver.revision, sizeof(uint8_t), 1, file);
+        temp = BCMOLT_BUF_ENDIAN_CPU_TO_BUF(U32, mpb.host_ver.model);
+        fwrite(&temp, sizeof(uint32_t), 1, file);
+        fwrite(mpb.host_ver.build_time, sizeof(mpb.host_ver.build_time), 1, file);
+        /* write capture buffer */
+        temp = BCMOLT_BUF_ENDIAN_CPU_TO_BUF(U32, mpb.buf_size);
+        fwrite(&temp, sizeof(uint32_t), 1, file);
+        fwrite(mpb.capture_buffer, mpb.buf_size, 1, file);
+        fclose(file);
+    }
+
+    bcmos_free(mpb.capture_buffer);
+
+    return err;
+}
+
+static bcmos_bool playback_version_match(const bcmolt_playback *mpb)
+{
+    bcmolt_firmware_sw_version fw_curr = {};
+    bcmolt_host_sw_version host_curr = {};
+
+    playback_fw_version_get(current_device, &fw_curr);
+    playback_host_version_get(current_device, &host_curr);
+
+    if ((mpb->fw_ver.model != fw_curr.model) || (mpb->host_ver.model != host_curr.model) ||
+        (fw_curr.model == 0) || (host_curr.model == 0))
+    {
+        BCM_LOG(WARNING, pb_ctxt[current_device].log_id,
+                "Possible version mismatch: Capture FW %u, HOST %u; Current FW %u, HOST %u\n",
+                mpb->fw_ver.model, mpb->host_ver.model, fw_curr.model, host_curr.model);
+        return BCMOS_FALSE;
+    }
+
+    return BCMOS_TRUE;
+}
+
+static bcmos_errno playback_file_open(const char *filename, bcmolt_playback *mpb)
+{
+    FILE *file;
+    uint32_t temp;
+    bcmos_errno err = BCM_ERR_OK;
+    uint32_t items_read;
+
+    file = fopen(filename, "rb");
+    items_read = fread(&temp, sizeof(uint32_t), 1, file);
+    if (items_read != 1)
+        return BCM_ERR_PARSE;
+
+    mpb->version = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+    switch (mpb->version)
+    {
+        case VERSION0:
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            temp = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            mpb->location = (bcmolt_api_capture_location)temp;
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            mpb->buf_size = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            mpb->capture_buffer = bcmos_alloc(mpb->buf_size);
+            items_read = fread(mpb->capture_buffer, mpb->buf_size, 1, file);
+            if (items_read != 1)
+                break;
+            break;
+        case VERSION1:
+            /* read capture location */
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            temp = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            mpb->location = (bcmolt_api_capture_location)temp;
+            /* read firmware version */
+            items_read = fread(&mpb->fw_ver.major, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&mpb->fw_ver.minor, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&mpb->fw_ver.revision, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            mpb->fw_ver.model = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            items_read = fread(mpb->fw_ver.build_time, sizeof(mpb->fw_ver.build_time), 1, file);
+            if (items_read != 1)
+                break;
+            /* read host version */
+            items_read = fread(&mpb->host_ver.major, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&mpb->host_ver.minor, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&mpb->host_ver.revision, sizeof(uint8_t), 1, file);
+            if (items_read != 1)
+                break;
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            mpb->host_ver.model = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            items_read = fread(mpb->host_ver.build_time, sizeof(mpb->host_ver.build_time), 1, file);
+            if (items_read != 1)
+                break;
+            /* read capture buffer */
+            items_read = fread(&temp, sizeof(uint32_t), 1, file);
+            if (items_read != 1)
+                break;
+            mpb->buf_size = BCMOLT_BUF_ENDIAN_BUF_TO_CPU(U32, temp);
+            mpb->capture_buffer = bcmos_alloc(mpb->buf_size);
+            items_read = fread(mpb->capture_buffer, mpb->buf_size, 1, file);
+            if (items_read != 1)
+                break;
+            break;
+        default:
+            BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Unknown version: %u\n", mpb->version);
+            err = BCM_ERR_PARSE;
+            break;
+    }
+
+    fclose(file);
+
+    return err;
+}
+
+static bcmos_errno playback_cli_replay(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    const char *filename = bcmcli_find_named_parm(session, "file")->value.string;
+    bcmos_bool ignore_ver = BCMOS_FALSE;
+    bcmos_errno err;
+    bcmolt_playback mpb = {};
+
+    bcmcli_cmd_parm *iv_parm = bcmcli_find_named_parm(session, "ignore_version");
+    if (iv_parm != NULL)
+    {
+        ignore_ver = iv_parm->value.enum_val == (long)BCMOS_TRUE;
+    }
+
+    err = playback_file_open(filename, &mpb);
+
+    if (BCM_ERR_OK == err)
+    {
+        if (playback_version_match(&mpb) || ignore_ver)
+        {
+            err = playback_replay(current_device, mpb.capture_buffer, mpb.buf_size, mpb.location, BCMOS_TRUE);
+        }
+        else
+        {
+            bcmcli_print(session, "Possible version mismatch; use ignore_version=yes to force playback\n");
+            err = BCM_ERR_IMAGE_TYPE;
+        }
+        bcmos_free(mpb.capture_buffer);
+    }
+
+    return err;
+}
+
+static bcmos_errno playback_api_cli_append_prop(
+    bcmolt_string *api_cli,
+    uint32_t size,
+    void *data,
+    const bcmcli_prop_descr *pd,
+    const char *prefix)
+{
+    bcmos_errno err;
+    bcmcli_session *str;
+    void *prop_data = (void *)((long)data + pd->offset);
+
+    BCMOS_CHECK_RETURN_ERROR(pd->offset >= size, BCM_ERR_INTERNAL);
+    err = bcmcli_session_open_string(&str, api_cli);
+    BCMOS_RETURN_IF_ERROR(err);
+    err = apicli_dump_prop_param(str, pd, prop_data, prefix);
+    bcmcli_session_close(str);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_api_cli_key_write(
+    bcmolt_string* api_cli,
+    const bcmolt_msg* msg,
+    uint32_t key_size,
+    uint32_t key_offset)
+{
+    bcmos_errno err;
+    const bcmcli_prop_descr *pd;
+    void *data = (void *)((long)msg + key_offset);
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, BCMOLT_MGT_GROUP_KEY, 0, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        err = playback_api_cli_append_prop(api_cli, key_size, data, pd, " ");
+        BCMOS_RETURN_IF_ERROR(err);
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_api_cli_pm_write(bcmolt_string *api_cli, const bcmolt_msg* msg, bcmolt_presence_mask pm)
+{
+    int n;
+    const bcmcli_prop_descr *pd;
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (!(pm & (1ULL << prop)))
+        {
+            continue;
+        }
+        n = bcmolt_string_append(api_cli, " %s=yes", pd->name);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_api_cli_props_write(
+    bcmolt_string *api_cli,
+    const bcmolt_msg* msg,
+    const char *prefix,
+    uint32_t size,
+    uint32_t offset)
+{
+    bcmos_errno err;
+    const bcmcli_prop_descr *pd;
+    void *data = (void *)((long)msg + offset);
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (!(msg->presence_mask & (1ULL << prop)))
+        {
+            continue;
+        }
+        err = playback_api_cli_append_prop(api_cli, size, data, pd, prefix);
+        BCMOS_RETURN_IF_ERROR(err);
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_api_cli_cmd_get(
+    bcmolt_string *api_cli,
+    const bcmolt_msg* msg,
+    const char *cmd,
+    pb_cli_extra extra_parms,
+    pb_fields field_parms)
+{
+    int n;
+    bcmos_errno err;
+    const char *name;
+    const char *desc;
+    uint32_t key_size;
+    uint32_t key_offset;
+    uint32_t data_size;
+    uint32_t data_offset;
+
+    BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "Start %s\n", cmd);
+
+    err = api_cli_object_name(msg->obj_type, &name, &desc);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    n = bcmolt_string_append(api_cli, "/api/%s object=%s", cmd, name);
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+
+    switch (extra_parms)
+    {
+        case PB_CLI_EXTRA_MULTI:
+            n = bcmolt_string_append(
+                api_cli,
+                " max_msgs=%u filter_invert=%s",
+                msg->msg_set->max_instances,
+                BITS_SET(msg->msg_set->filter_flags, BCMOLT_FILTER_FLAGS_INVERT_SELECTION) ? "yes" : "no");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            break;
+        case PB_CLI_EXTRA_STAT:
+            n = bcmolt_string_append(api_cli, " clear=%s", BITS_SET(msg->type, BCMOLT_MSG_TYPE_CLEAR) ? "yes" : "no");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            break;
+        case PB_CLI_EXTRA_SUBGROUP:
+            err = api_cli_object_subgroup_name(msg->obj_type, msg->group, msg->subgroup, &name, &desc);
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(api_cli, " sub=%s", name);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            break;
+        default:
+            break;
+    }
+
+    /* get message info */
+    err = api_cli_object_struct_size(
+        msg->obj_type,
+        msg->group,
+        msg->subgroup,
+        &key_size,
+        &key_offset,
+        &data_size,
+        &data_offset);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    if (BCMOLT_MGT_GROUP_AUTO_CFG != msg->group)
+    {
+        /* write key */
+        err = playback_api_cli_key_write(api_cli, msg, key_size, key_offset);
+        BCMOS_RETURN_IF_ERROR(err);
+    }
+
+    switch (field_parms)
+    {
+        case PB_FIELDS_GET:
+            /* write presence mask */
+            err = playback_api_cli_pm_write(api_cli, msg, msg->presence_mask);
+            BCMOS_RETURN_IF_ERROR(err);
+            break;
+        case PB_FIELDS_MULTI:
+            /* write filter */
+            err = playback_api_cli_props_write(api_cli, msg, " filter.", data_size, data_offset);
+            BCMOS_RETURN_IF_ERROR(err);
+            /* write presence mask */
+            err = playback_api_cli_pm_write(api_cli, msg, msg->msg_set->presence_mask);
+            BCMOS_RETURN_IF_ERROR(err);
+            break;
+        case PB_FIELDS_SET:
+            /* write properties */
+            err = playback_api_cli_props_write(api_cli, msg, " ", data_size, data_offset);
+            BCMOS_RETURN_IF_ERROR(err);
+            break;
+        default:
+            break;
+    }
+
+    n = bcmolt_string_append(api_cli, "\n");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+
+    BCM_LOG(DEBUG, pb_ctxt[current_device].log_id, "End %s\n", cmd);
+
+    return err;
+}
+
+static bcmos_errno playback_api_cli_get(bcmolt_string* api_cli, const bcmolt_msg* msg)
+{
+    switch (msg->group)
+    {
+        case BCMOLT_MGT_GROUP_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "get", PB_CLI_EXTRA_NONE, PB_FIELDS_GET);
+                case BCMOLT_MSG_TYPE_GET_MULTI:
+                    return playback_api_cli_cmd_get(api_cli, msg, "multiget", PB_CLI_EXTRA_MULTI, PB_FIELDS_MULTI);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "set", PB_CLI_EXTRA_NONE, PB_FIELDS_SET);
+                case BCMOLT_MSG_TYPE_CLEAR:
+                    return playback_api_cli_cmd_get(api_cli, msg, "clear", PB_CLI_EXTRA_NONE, PB_FIELDS_NONE);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_STAT:
+            return playback_api_cli_cmd_get(api_cli, msg, "stat", PB_CLI_EXTRA_STAT, PB_FIELDS_GET);
+        case BCMOLT_MGT_GROUP_STAT_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "saget", PB_CLI_EXTRA_SUBGROUP, PB_FIELDS_NONE);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "saset", PB_CLI_EXTRA_SUBGROUP, PB_FIELDS_SET);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_AUTO_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "acget", PB_CLI_EXTRA_NONE, PB_FIELDS_GET);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_api_cli_cmd_get(api_cli, msg, "acset", PB_CLI_EXTRA_NONE, PB_FIELDS_SET);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_OPER:
+            return playback_api_cli_cmd_get(api_cli, msg, "oper", PB_CLI_EXTRA_SUBGROUP, PB_FIELDS_SET);
+        case BCMOLT_MGT_GROUP_PROXY:
+            return playback_api_cli_cmd_get(api_cli, msg, "send", PB_CLI_EXTRA_SUBGROUP, PB_FIELDS_SET);
+        default:
+            return BCM_ERR_INTERNAL;
+    }
+}
+
+static bcmos_errno playback_convert_api_cli(bcmolt_playback *mpb, FILE *out_file)
+{
+    playback_iterator it;
+    bcmos_errno err;
+    bcmolt_string *str;
+
+    err = bcmolt_string_create(&str, 2048);
+    BCMOS_RETURN_IF_ERROR(err);
+    FOR_EACH_PLAYBACK_MSG(it, mpb->capture_buffer, mpb->buf_size)
+    {
+        if (playback_should_send(mpb->location, (bcmtr_cld_event_type)it.raw.hdr.event))
+        {
+            if (BCM_ERR_OK == it.err)
+            {
+                bcmolt_string_reset(str);
+                err = playback_api_cli_get(str, it.msg);
+                fwrite(bcmolt_string_get(str), sizeof(char), strlen(bcmolt_string_get(str)), out_file);
+            }
+            else
+            {
+                err = it.err;
+                BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Unpacking failed: %s\n", bcmos_strerror(err));
+            }
+        }
+    }
+    bcmolt_string_destroy(str);
+
+    return err;
+}
+
+static bcmos_bool playback_field_contains_var_list(const bcmcli_type_descr *type)
+{
+    if (type == NULL)
+    {
+        return BCMOS_FALSE;
+    }
+
+    if (type->base_type == BCMOLT_BASE_TYPE_ID_ARR_DYN)
+    {
+        return BCMOS_TRUE;
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_ARR_FIXED)
+    {
+        return playback_field_contains_var_list(type->x.arr_fixed.elem_type);
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_STRUCT)
+    {
+        for (uint16_t i = 0; i < type->x.s.num_fields; ++i)
+        {
+            if (playback_field_contains_var_list(type->x.s.fields[i].type))
+            {
+                return BCMOS_TRUE;
+            }
+        }
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_UNION)
+    {
+        for (uint16_t i = 0; i < type->x.u.num_common_fields; ++i)
+        {
+            if (playback_field_contains_var_list(type->x.u.common_fields[i].type))
+            {
+                return BCMOS_TRUE;
+            }
+        }
+        for (uint16_t i = 0; type->x.u.common_fields[type->x.u.classifier_idx].type->x.e[i].name != NULL; ++i)
+        {
+            if (playback_field_contains_var_list(type->x.u.union_fields[i].type))
+            {
+                return BCMOS_TRUE;
+            }
+        }
+    }
+    else
+    {
+        /* not a variable sized list */
+    }
+
+    return BCMOS_FALSE;
+}
+
+static bcmos_bool playback_msg_contains_var_list(const bcmolt_msg* msg)
+{
+    const bcmcli_prop_descr *pd;
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (playback_field_contains_var_list(pd->type))
+        {
+            return BCMOS_TRUE;
+        }
+    }
+
+    return BCMOS_FALSE;
+}
+
+static bcmos_errno playback_string_write_upper(bcmolt_string *dest, const char* src)
+{
+    int n;
+
+    while (*src != '\0')
+    {
+        n = bcmolt_string_append(dest, "%c", (char)toupper((int)*src));
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        ++src;
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_pm_write(
+    bcmolt_string *c_api,
+    const bcmolt_msg* msg,
+    bcmolt_presence_mask pm,
+    const char *extra,
+    const char *field,
+    const char *obj)
+{
+    int n;
+    bcmos_errno err;
+    const bcmcli_prop_descr *pd;
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (!(pm & (1ULL << prop)))
+        {
+            continue;
+        }
+        n = bcmolt_string_append(c_api, "\tBCMOLT_%s", extra);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        err = playback_string_write_upper(c_api, apicli_mgt_group_to_str(msg->group));
+        BCMOS_RETURN_IF_ERROR(err);
+        n = bcmolt_string_append(c_api, "_PROP_GET(%s, %s, %s);\n", field, obj, pd->name);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_append_init(
+    bcmolt_string *c_api,
+    void *data,
+    const bcmcli_type_descr *td,
+    const char* name)
+{
+    bcmos_errno rc;
+    bcmcli_session *str;
+
+    rc = bcmcli_session_open_string(&str, c_api);
+    BCMOS_RETURN_IF_ERROR(rc);
+    rc = apicli_dump_dyn_array(str, td, data, name);
+    bcmcli_session_close(str);
+    BCMOS_RETURN_IF_ERROR(rc);
+
+    return BCM_ERR_OK;
+}
+
+
+static bcmos_errno playback_c_api_append_prop(
+    bcmolt_string *c_api,
+    uint32_t size,
+    void *data,
+    const bcmcli_prop_descr *pd)
+{
+    bcmos_errno err;
+    bcmcli_session *str;
+    void *prop_data = (void *)((long)data + pd->offset);
+
+    BCMOS_CHECK_RETURN_ERROR(pd->offset >= size, BCM_ERR_INTERNAL);
+    err = bcmcli_session_open_string(&str, c_api);
+    BCMOS_RETURN_IF_ERROR(err);
+    err = apicli_dump_prop_initializer(str, pd, prop_data);
+    bcmcli_session_close(str);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_make_var_lists(
+    bcmolt_string *c_api,
+    const bcmcli_type_descr *type,
+    void *data,
+    const char *name)
+{
+    int n;
+    bcmos_errno rc;
+
+    if (type == NULL)
+    {
+        return BCM_ERR_OK;
+    }
+
+    if (type->base_type == BCMOLT_BASE_TYPE_ID_ARR_DYN)
+    {
+        n = bcmolt_string_append(c_api, "\t%s %s[] = ", type->x.arr_dyn.elem_type->name, name);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        rc = playback_c_api_append_init(c_api, data, type, name);
+        BCMOS_RETURN_IF_ERROR(rc);
+        n = bcmolt_string_append(c_api, ";\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_ARR_FIXED)
+    {
+        for (uint16_t i = 0; i < type->x.arr_fixed.size; ++i)
+        {
+            char index_name[64];
+
+            sprintf(index_name, "%s%u", name, i);
+            rc = playback_c_api_make_var_lists(c_api, type->x.arr_fixed.elem_type, data, index_name);
+            BCMOS_RETURN_IF_ERROR(rc);
+            data = (void*)((long)data + type->x.arr_fixed.elem_type->size);
+        }
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_STRUCT)
+    {
+        for (uint16_t i = 0; i < type->x.s.num_fields; ++i)
+        {
+            const bcmcli_field_descr *field = &type->x.s.fields[i];
+            rc = playback_c_api_make_var_lists(c_api, field->type, (void*)((long)data + field->offset), field->name);
+            BCMOS_RETURN_IF_ERROR(rc);
+        }
+    }
+    else if (type->base_type == BCMOLT_BASE_TYPE_ID_UNION)
+    {
+        for (uint16_t i = 0; i < type->x.u.num_common_fields; ++i)
+        {
+            const bcmcli_field_descr *field = &type->x.u.common_fields[i];
+            rc = playback_c_api_make_var_lists(c_api, field->type, (void*)((long)data + field->offset), field->name);
+            BCMOS_RETURN_IF_ERROR(rc);
+        }
+        for (uint16_t i = 0; type->x.u.common_fields[type->x.u.classifier_idx].type->x.e[i].name != NULL; ++i)
+        {
+            const bcmcli_field_descr *field = &type->x.u.union_fields[i];
+            rc = playback_c_api_make_var_lists(c_api, field->type, (void*)((long)data + field->offset), field->name);
+            BCMOS_RETURN_IF_ERROR(rc);
+        }
+    }
+    else
+    {
+        /* not a variable sized list */
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_props_write(
+    bcmolt_string *c_api,
+    const bcmolt_msg* msg,
+    const char* object,
+    uint32_t size,
+    uint32_t offset)
+{
+    int n;
+    bcmos_errno err;
+    const char *subgroup;
+    const char *desc;
+    const bcmcli_prop_descr *pd;
+    void *data = (void *)((long)msg + offset);
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (!(msg->presence_mask & (1ULL << prop)))
+        {
+            continue;
+        }
+        playback_c_api_make_var_lists(c_api, pd->type, (void*)((long)data + pd->offset), pd->name);
+    }
+
+    for (uint16_t prop = 0;
+         api_cli_object_property(msg->obj_type, msg->group, msg->subgroup, prop, &pd) == BCM_ERR_OK;
+         ++prop)
+    {
+        if (!(msg->presence_mask & (1ULL << prop)))
+        {
+            continue;
+        }
+        if ((pd->type->base_type == BCMOLT_BASE_TYPE_ID_STRUCT) ||
+            (pd->type->base_type == BCMOLT_BASE_TYPE_ID_UNION) ||
+            (pd->type->base_type == BCMOLT_BASE_TYPE_ID_ARR_DYN) ||
+            (pd->type->base_type == BCMOLT_BASE_TYPE_ID_ARR_FIXED))
+        {
+            n = bcmolt_string_append(c_api, "\t%s "VAL_REF" = ", pd->type->name);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            err = playback_c_api_append_prop(c_api, size, data, pd);
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(c_api, ";\n");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            n = bcmolt_string_append(c_api, "\tBCMOLT_");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            err = playback_string_write_upper(c_api, apicli_mgt_group_to_str(msg->group));
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(c_api, "_PROP_SET(&msg, %s", object);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            if ((msg->group == BCMOLT_MGT_GROUP_OPER) || (msg->group == BCMOLT_MGT_GROUP_PROXY))
+            {
+                err = api_cli_object_subgroup_name(msg->obj_type, msg->group, msg->subgroup, &subgroup, &desc);
+                BCMOS_RETURN_IF_ERROR(err);
+                n = bcmolt_string_append(c_api, ", %s", subgroup);
+                BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            }
+            n = bcmolt_string_append(c_api, ", %s, "VAL_REF");\n", pd->name);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        }
+        else
+        {
+            n = bcmolt_string_append(c_api, "\tBCMOLT_");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            err = playback_string_write_upper(c_api, apicli_mgt_group_to_str(msg->group));
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(c_api, "_PROP_SET(&msg, %s", object);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            if ((msg->group == BCMOLT_MGT_GROUP_OPER) || (msg->group == BCMOLT_MGT_GROUP_PROXY))
+            {
+                err = api_cli_object_subgroup_name(msg->obj_type, msg->group, msg->subgroup, &subgroup, &desc);
+                BCMOS_RETURN_IF_ERROR(err);
+                n = bcmolt_string_append(c_api, ", %s", subgroup);
+                BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            }
+            n = bcmolt_string_append(c_api, ", %s, ", pd->name);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            err = playback_c_api_append_prop(c_api, size, data, pd);
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(c_api, ");\n");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        }
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_code_get(
+    bcmolt_string* c_api,
+    const bcmolt_msg* msg,
+    const char *call,
+    pb_fields field_parms)
+{
+    int n;
+    bcmos_errno err;
+    const char *object;
+    const char *group;
+    const char *subgroup;
+    const char *desc;
+    uint32_t key_size;
+    uint32_t key_offset;
+    uint32_t data_size;
+    uint32_t data_offset;
+    void *data;
+    const bcmcli_prop_descr *pd;
+    bcmos_bool var_list = (BCMOLT_MSG_TYPE_GET == msg->type) && playback_msg_contains_var_list(msg);
+
+    err = api_cli_object_name(msg->obj_type, &object, &desc);
+    BCMOS_RETURN_IF_ERROR(err);
+    err = api_cli_object_subgroup_name(msg->obj_type, msg->group, msg->subgroup, &subgroup, &desc);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    group = apicli_mgt_group_to_str(msg->group);
+
+    /* get message info */
+    err = api_cli_object_struct_size(
+        msg->obj_type,
+        msg->group,
+        msg->subgroup,
+        &key_size,
+        &key_offset,
+        &data_size,
+        &data_offset);
+    BCMOS_RETURN_IF_ERROR(err);
+
+    /* init variables */
+    n = bcmolt_string_append(c_api, "\t{\n");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    if ((msg->group == BCMOLT_MGT_GROUP_OPER) || (msg->group == BCMOLT_MGT_GROUP_PROXY))
+    {
+        n = bcmolt_string_append(c_api, "\tbcmolt_%s_%s "MSG_REF";\n", object, subgroup);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else
+    {
+        n = bcmolt_string_append(c_api, "\tbcmolt_%s_%s "MSG_REF";\n", object, group);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    n = bcmolt_string_append(c_api, "\tbcmolt_%s_key "KEY_REF" = { };\n", object);
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+
+    if (var_list)
+    {
+        n = bcmolt_string_append(c_api, "\tuint8_t* "LIST_MEM_REF";\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+
+    if (msg->type == BCMOLT_MSG_TYPE_GET_MULTI)
+    {
+        n = bcmolt_string_append(c_api, "\tbcmolt_msg_set* "MSG_SET_REF" = NULL;\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\tuint32_t "MAX_MSGS_REF";\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\tbcmos_bool "INVERT_FILTER_REF";\n\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\t"MAX_MSGS_REF" = %d;\n", msg->msg_set->max_instances);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\t"INVERT_FILTER_REF" = %s;\n", BITS_SET(msg->msg_set->filter_flags, BCMOLT_FILTER_FLAGS_INVERT_SELECTION) ? "BCMOS_TRUE" : "BCMOS_FALSE");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\tbcmolt_msg_set_alloc(BCMOLT_OBJ_ID_");
+        err = playback_string_write_upper(c_api, object);
+        BCMOS_RETURN_IF_ERROR(err);
+        n = bcmolt_string_append(c_api, ", BCMOLT_MGT_GROUP_");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        err = playback_string_write_upper(c_api, group);
+        BCMOS_RETURN_IF_ERROR(err);
+        n = bcmolt_string_append(c_api, ", "MAX_MSGS_REF", &"MSG_SET_REF");\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else if (msg->group == BCMOLT_MGT_GROUP_STAT)
+    {
+        n = bcmolt_string_append(c_api, "\tbcmolt_stat_flags "STAT_FLAGS_REF";\n\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        n = bcmolt_string_append(c_api, "\t"STAT_FLAGS_REF" = %s;\n", BITS_SET(msg->type, BCMOLT_MSG_TYPE_CLEAR) ? "BCMOLT_STAT_FLAGS_CLEAR_ON_READ" : "BCMOLT_STAT_FLAGS_NONE");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else
+    {
+        n = bcmolt_string_append(c_api, "\n");
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+
+    /* set key */
+    if (BCMOLT_MGT_GROUP_AUTO_CFG != msg->group)
+    {
+        /* write key */
+        data = (void *)((long)msg + key_offset);
+        for (uint16_t prop = 0;
+             api_cli_object_property(msg->obj_type, BCMOLT_MGT_GROUP_KEY, 0, prop, &pd) == BCM_ERR_OK;
+             ++prop)
+        {
+            n = bcmolt_string_append(c_api, "\t"KEY_REF".%s = ", pd->name);
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            if ((BCMOLT_BASE_TYPE_ID_MAC == pd->type->base_type) ||
+                (BCMOLT_BASE_TYPE_ID_IPV4 == pd->type->base_type) ||
+                (BCMOLT_BASE_TYPE_ID_STRUCT == pd->type->base_type) ||
+                (BCMOLT_BASE_TYPE_ID_UNION == pd->type->base_type))
+            {
+                n = bcmolt_string_append(c_api, "(%s)", pd->type->name);
+                BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            }
+            err = playback_c_api_append_prop(c_api, key_size, data, pd);
+            BCMOS_RETURN_IF_ERROR(err);
+            n = bcmolt_string_append(c_api, ";\n");
+            BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+        }
+    }
+
+    /* set properties */
+    n = bcmolt_string_append(c_api, "\tBCMOLT_");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    err = playback_string_write_upper(c_api, group);
+    BCMOS_RETURN_IF_ERROR(err);
+    n = bcmolt_string_append(c_api, "_INIT(&"MSG_REF", %s", object);
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    if ((msg->group == BCMOLT_MGT_GROUP_STAT_CFG) ||
+        (msg->group == BCMOLT_MGT_GROUP_OPER) ||
+        (msg->group == BCMOLT_MGT_GROUP_PROXY))
+    {
+        n = bcmolt_string_append(c_api, ", %s", subgroup);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    n = bcmolt_string_append(c_api, ", "KEY_REF");\n");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+
+    switch (field_parms)
+    {
+        case PB_FIELDS_GET:
+            /* write presence mask */
+            err = playback_c_api_pm_write(c_api, msg, msg->presence_mask, "", "&"MSG_REF, object);
+            BCMOS_RETURN_IF_ERROR(err);
+            if (var_list)
+            {
+                n = bcmolt_string_append(c_api, "\t"LIST_MEM_REF" = bcmos_calloc("DYN_LIST_SIZE");\n");
+                BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+                n = bcmolt_string_append(
+                    c_api,
+                    "\tBCMOLT_CFG_LIST_BUF_SET(&"MSG_REF", %s, "LIST_MEM_REF", "DYN_LIST_SIZE");\n", object);
+                BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+            }
+            break;
+        case PB_FIELDS_MULTI:
+            /* write filter */
+            err = playback_c_api_props_write(c_api, msg, object, data_size, data_offset);
+            BCMOS_RETURN_IF_ERROR(err);
+            /* write presence mask */
+            err = playback_c_api_pm_write(c_api, msg, msg->presence_mask, "MSGSET_", MSG_SET_REF, object);
+            BCMOS_RETURN_IF_ERROR(err);
+            break;
+        case PB_FIELDS_SET:
+            /* write properties */
+            err = playback_c_api_props_write(c_api, msg, object, data_size, data_offset);
+            BCMOS_RETURN_IF_ERROR(err);
+            break;
+        default:
+            break;
+    }
+
+    /* call API */
+    n = bcmolt_string_append(c_api, "\t"RC_REF" = bcmolt_%s_%s("DEVICE_REF", &"MSG_REF".hdr", group, call);
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    if (msg->type == BCMOLT_MSG_TYPE_GET_MULTI)
+    {
+        n = bcmolt_string_append(
+            c_api,
+            ", ("INVERT_FILTER_REF") ? BCMOLT_FILTER_FLAGS_INVERT_SELECTION : BCMOLT_FILTER_FLAGS_NONE, "MSG_SET_REF);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else if (msg->group == BCMOLT_MGT_GROUP_STAT)
+    {
+        n = bcmolt_string_append(c_api, ", "STAT_FLAGS_REF);
+        BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    }
+    else
+    {
+        /* do nothing */
+    }
+    n = bcmolt_string_append(c_api, ");\n");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    n = bcmolt_string_append(c_api, "\tbcmos_printf(\"bcmolt_%s_%s returned %%s (%%d)\\n\", bcmos_strerror("RC_REF"), "RC_REF");\n", group, call);
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+    n = bcmolt_string_append(c_api, "\t}\n\n");
+    BCMOS_CHECK_RETURN_ERROR(n <= 0, BCM_ERR_INTERNAL);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno playback_c_api_get(bcmolt_string* c_api, const bcmolt_msg* msg)
+{
+    switch (msg->group)
+    {
+        case BCMOLT_MGT_GROUP_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_c_api_code_get(c_api, msg, "get", PB_FIELDS_GET);
+                case BCMOLT_MSG_TYPE_GET_MULTI:
+                    return playback_c_api_code_get(c_api, msg, "get_multi", PB_FIELDS_MULTI);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_c_api_code_get(c_api, msg, "set", PB_FIELDS_SET);
+                case BCMOLT_MSG_TYPE_CLEAR:
+                    return playback_c_api_code_get(c_api, msg, "clear", PB_FIELDS_NONE);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_STAT:
+            return playback_c_api_code_get(c_api, msg, "get", PB_FIELDS_GET);
+        case BCMOLT_MGT_GROUP_STAT_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_c_api_code_get(c_api, msg, "get", PB_FIELDS_NONE);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_c_api_code_get(c_api, msg, "set", PB_FIELDS_SET);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_AUTO_CFG:
+            switch (msg->type)
+            {
+                case BCMOLT_MSG_TYPE_GET:
+                    return playback_c_api_code_get(c_api, msg, "get", PB_FIELDS_GET);
+                case BCMOLT_MSG_TYPE_SET:
+                    return playback_c_api_code_get(c_api, msg, "set", PB_FIELDS_SET);
+                default:
+                    return BCM_ERR_INTERNAL;
+            }
+            break;
+        case BCMOLT_MGT_GROUP_OPER:
+            return playback_c_api_code_get(c_api, msg, "submit", PB_FIELDS_SET);
+        case BCMOLT_MGT_GROUP_PROXY:
+            return playback_c_api_code_get(c_api, msg, "send", PB_FIELDS_SET);
+        default:
+            return BCM_ERR_INTERNAL;
+    }
+}
+
+static bcmos_errno playback_convert_c_api(bcmolt_playback *mpb, FILE *out_file)
+{
+    playback_iterator it;
+    bcmos_errno err;
+    bcmolt_string *str;
+    uint32_t last_time_us = 0;
+    bcmos_bool first = BCMOS_TRUE;
+
+    err = bcmolt_string_create(&str, 16384);
+    BCMOS_RETURN_IF_ERROR(err);
+    fprintf(out_file, "void run_playback(void)\n{\n");
+    fprintf(out_file, "\tbcmolt_devid "DEVICE_REF" = %d;\n", current_device);
+    fprintf(out_file, "\tbcmos_errno "RC_REF" = BCM_ERR_OK;\n\n");
+    FOR_EACH_PLAYBACK_MSG(it, mpb->capture_buffer, mpb->buf_size)
+    {
+        if (playback_should_send(mpb->location, (bcmtr_cld_event_type)it.raw.hdr.event))
+        {
+            if (BCM_ERR_OK == it.err)
+            {
+                if (!first)
+                {
+                    /* approximate original timing; doesn't account for processing time in this code - this could be
+                       improved */
+                    fprintf(out_file, "\n\tbcmos_usleep(%u);\n\n", it.raw.hdr.timestamp - last_time_us);
+                }
+                else
+                {
+                    first = BCMOS_FALSE;
+                }
+                last_time_us = it.raw.hdr.timestamp;
+                bcmolt_string_reset(str);
+                err = playback_c_api_get(str, it.msg);
+                fwrite(bcmolt_string_get(str), sizeof(char), strlen(bcmolt_string_get(str)), out_file);
+            }
+            else
+            {
+                err = it.err;
+                BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Unpacking failed: %s\n", bcmos_strerror(err));
+            }
+        }
+    }
+    fprintf(out_file, "}\n");
+    bcmolt_string_destroy(str);
+
+    return err;
+}
+
+static bcmos_errno playback_convert(bcmolt_playback *mpb, pb_format format, FILE *out_file)
+{
+    bcmos_errno err;
+
+    switch (format)
+    {
+        case PB_FORMAT_API_CLI:
+            err = playback_convert_api_cli(mpb, out_file);
+            break;
+        case PB_FORMAT_C_API:
+            err = playback_convert_c_api(mpb, out_file);
+            break;
+        default:
+            err = BCM_ERR_PARM;
+            BCM_LOG(ERROR, pb_ctxt[current_device].log_id, "Unknown format: %d\n", format);
+            break;
+    }
+
+    return err;
+}
+
+static bcmos_errno playback_cli_conv(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
+{
+    const char *infilename = bcmcli_find_named_parm(session, "infile")->value.string;
+    const char *outfilename = bcmcli_find_named_parm(session, "outfile")->value.string;
+    pb_format format = (pb_format)bcmcli_find_named_parm(session, "format")->value.enum_val;
+    bcmos_bool ignore_ver = BCMOS_FALSE;
+    bcmos_errno err;
+    bcmolt_playback mpb = {};
+    FILE *out_file;
+
+    bcmcli_cmd_parm *iv_parm = bcmcli_find_named_parm(session, "ignore_version");
+    if (iv_parm != NULL)
+    {
+        ignore_ver = iv_parm->value.enum_val == (long)BCMOS_TRUE;
+    }
+
+    err = playback_file_open(infilename, &mpb);
+
+    if (BCM_ERR_OK == err)
+    {
+        if (playback_version_match(&mpb) || ignore_ver)
+        {
+            out_file = fopen(outfilename, "wb");
+            playback_convert(&mpb, format, out_file);
+            fclose(out_file);
+        }
+        else
+        {
+            bcmcli_print(session, "Possible version mismatch; use ignore_version=yes to force conversion\n");
+            err = BCM_ERR_IMAGE_TYPE;
+        }
+        bcmos_free(mpb.capture_buffer);
+    }
+
+    return err;
+}
+
+void bcmolt_user_appl_playback_cli_init(bcmcli_entry *top_dir)
+{
+#ifdef ENABLE_CLI
+    bcmcli_entry *plb_dir = bcmcli_dir_add(top_dir, "playback", "playback", BCMCLI_ACCESS_ADMIN, NULL);
+    BUG_ON(NULL == plb_dir);
+
+    static bcmcli_enum_val format_enum_table[PB_FORMAT__COUNT+1] =
+    {
+        {.name = "api_cli", .val = (long)PB_FORMAT_API_CLI},
+        {.name = "c_api", .val = (long)PB_FORMAT_C_API},
+        BCMCLI_ENUM_LAST
+    };
+
+    BCMCLI_MAKE_CMD_NOPARM(plb_dir, "dump", "dump capture", playback_cli_dump);
+    BCMCLI_MAKE_CMD(plb_dir, "save", "save capture", playback_cli_save,
+        BCMCLI_MAKE_PARM("file", "filename", BCMCLI_PARM_STRING, BCMCLI_PARM_FLAG_NONE));
+    BCMCLI_MAKE_CMD(plb_dir, "replay", "replay capture", playback_cli_replay,
+        BCMCLI_MAKE_PARM("file", "filename", BCMCLI_PARM_STRING, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ENUM("ignore_version", "Ignore version mismatch", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL));
+    BCMCLI_MAKE_CMD(plb_dir, "conv", "Convert capture", playback_cli_conv,
+        BCMCLI_MAKE_PARM("infile", "Input filename", BCMCLI_PARM_STRING, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM("outfile", "Output filename", BCMCLI_PARM_STRING, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ENUM("format", "Output format", format_enum_table, BCMCLI_PARM_FLAG_NONE),
+        BCMCLI_MAKE_PARM_ENUM("ignore_version", "Ignore version mismatch", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL));
+#endif
+}
+
+void bcmolt_user_appl_playback_init(void)
+{
+    for (uint32_t i = 0; i < BCMTR_MAX_OLTS; i++)
+    {
+        char log_name[MAX_DEV_LOG_ID_NAME];
+        snprintf(log_name, sizeof(log_name)-1, "user_appl_playback_%u", i);
+        pb_ctxt[i].log_id = bcm_dev_log_id_register(log_name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+    }
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.h b/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.h
new file mode 100644
index 0000000..cb37c61
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/playback/bcmolt_user_appl_playback.h
@@ -0,0 +1,40 @@
+/*
+<: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.
+
+:>
+ */
+
+#ifndef _BCMOLT_USER_APPL_PLAYBACK_H_
+#define _BCMOLT_USER_APPL_PLAYBACK_H_
+
+#include "bcmcli.h"
+
+void bcmolt_user_appl_playback_cli_init(bcmcli_entry *top_dir);
+
+void bcmolt_user_appl_playback_init(void);
+
+#endif /* _BCMOLT_USER_APPL_PLAYBACK_H_ */
+
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/Makefile b/bcm68620_release/release/host_reference/user_appl/protection_switching/Makefile
new file mode 100644
index 0000000..dff2e55
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/Makefile
@@ -0,0 +1,17 @@
+# protection switching user application
+
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_user_appl_ps
+    MOD_TYPE = lib
+    MOD_DEPS = host_api bcm_board
+    srcs = bcmolt_user_appl_ps.c \
+	   bcmolt_user_appl_ps_cli.c \
+	   bcmolt_user_appl_ps_gpon.c \
+	   bcmolt_user_appl_ps_epon.c \
+	   bcmolt_user_appl_ps_xgpon.c
+
+    ifeq ("$(OS_KERNEL)", "linux")
+	MOD_DEPS += dev_log_linux
+    endif
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.c b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.c
new file mode 100644
index 0000000..3fc8d31
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.c
@@ -0,0 +1,756 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_msg_pack.h>
+#include <bcmolt_model_types.h>
+#include <bcm_dev_log.h>
+#include "bcmolt_user_appl_ps.h"
+#include "bcmolt_user_appl_ps_internal.h"
+
+#ifndef SIMULATION_BUILD
+#include <bcmolt_board.h>
+#endif
+
+#define PS_DEFAULT_MAX_PAIRS 16
+#define PS_TASK_MSG_Q_SIZE 64
+
+typedef struct
+{
+    bcmolt_ps_pair pair;
+    bcmos_bool is_valid;
+} ps_pair_state;
+
+typedef struct
+{
+    bcmos_bool is_running;
+    bcmolt_ps_global_cfg cfg;
+    ps_pair_state *pairs;
+    uint32_t switch_start_time_us;
+} ps_global_state;
+
+typedef struct
+{
+    bcmos_msg os_msg;
+    bcmolt_ps_pon pon;
+    bcmolt_auto *ind;
+} ps_task_msg;
+
+typedef enum
+{
+    PS_PON_MODE_INVALID,
+    PS_PON_MODE_GPON,
+    PS_PON_MODE_EPON,
+    PS_PON_MODE_XGPON,
+} ps_pon_mode;
+
+static ps_global_state ps_state = {};
+static bcmos_mutex ps_mutex;
+static bcmos_task ps_task;
+
+#ifdef ENABLE_LOG
+static dev_log_id ps_log_id;
+dev_log_id ps_get_log_id(void)
+{
+    return ps_log_id;
+}
+#endif
+
+static inline bcmos_bool ps_pon_equal(const bcmolt_ps_pon *a, const bcmolt_ps_pon *b)
+{
+    return a->device_id == b->device_id && a->pon_id == b->pon_id;
+}
+
+static inline bcmos_bool ps_pair_equal(const bcmolt_ps_pair *a, const bcmolt_ps_pair *b)
+{
+    return ps_pon_equal(&a->standby, &b->standby) && ps_pon_equal(&a->working, &b->working);
+}
+
+static bcmos_errno bcmolt_ps_pon_pair_get(const bcmolt_ps_pon *pon, bcmolt_ps_pair **pair)
+{
+    uint16_t i;
+
+    if (!ps_state.is_running)
+    {
+        *pair = NULL;
+        PS_ERR("PS application not running\n");
+        return BCM_ERR_STATE;
+    }
+
+    for (i = 0; i < ps_state.cfg.max_num_pairs; i++)
+    {
+        if (ps_state.pairs[i].is_valid)
+        {
+            if (ps_pon_equal(pon, &ps_state.pairs[i].pair.standby) ||
+                ps_pon_equal(pon, &ps_state.pairs[i].pair.working))
+            {
+                *pair = &ps_state.pairs[i].pair;
+                return BCM_ERR_OK;
+            }
+        }
+    }
+
+    return BCM_ERR_NOENT;
+}
+
+static bcmos_errno ps_init_task(void)
+{
+    bcmos_task_parm task_params =
+    {
+        .name = "user_appl_ps",
+        .priority = TASK_PRIORITY_USER_APPL_PS,
+        .core = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+
+    return bcmos_task_create(&ps_task, &task_params);
+}
+
+static bcmos_errno ps_init_module(void)
+{
+    bcmos_module_parm module_params =
+    {
+        .qparm = { .name = "user_appl_ps", .size = PS_TASK_MSG_Q_SIZE }
+    };
+
+    return bcmos_module_create(BCMOS_MODULE_ID_USER_APPL_PS, &ps_task, &module_params);
+}
+
+static ps_pon_mode ps_pon_mode_get(bcmolt_devid dev)
+{
+    bcmos_errno err;
+    bcmolt_system_mode system_mode;
+
+    err = bcmolt_system_mode_get(dev, &system_mode);
+    if (err != BCM_ERR_OK)
+    {
+        return PS_PON_MODE_INVALID;
+    }
+    switch (system_mode)
+    {
+    case BCMOLT_SYSTEM_MODE_GPON__4_X:
+    case BCMOLT_SYSTEM_MODE_GPON__8_X:
+    case BCMOLT_SYSTEM_MODE_GPON__16_X:
+        return PS_PON_MODE_GPON;
+    case BCMOLT_SYSTEM_MODE_EPON__16_X:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA:
+    case BCMOLT_SYSTEM_MODE_EPON__8_X_10_G:
+    case BCMOLT_SYSTEM_MODE_EPON__4_X_10_G:
+    case BCMOLT_SYSTEM_MODE_EPON__2_X_10_G:
+        return PS_PON_MODE_EPON;
+    case BCMOLT_SYSTEM_MODE_XGPON_1__4_X:
+    case BCMOLT_SYSTEM_MODE_XGPON_1__8_X:
+    case BCMOLT_SYSTEM_MODE_XGS__2_X_10_G:
+    case BCMOLT_SYSTEM_MODE_NGPON2__2_X_10_G:
+        return PS_PON_MODE_XGPON;
+    default:
+        return PS_PON_MODE_INVALID;
+    }
+}
+
+static void ps_handle_msg(bcmos_module_id module_id, bcmos_msg *os_msg)
+{
+    ps_task_msg *msg = (ps_task_msg *)os_msg;
+    bcmos_errno err;
+
+    /* process the indication */
+    if (msg->ind->hdr.obj_type == BCMOLT_OBJ_ID_DEVICE || msg->ind->hdr.obj_type == BCMOLT_OBJ_ID_DEBUG)
+    {
+        /* there's no need to mirror device-level indications */
+        err = BCM_ERR_OK;
+    }
+    else
+    {
+        switch (ps_pon_mode_get(msg->pon.device_id))
+        {
+        case PS_PON_MODE_GPON:
+            err = ps_process_ind_gpon(&msg->pon, msg->ind);
+            break;
+        case PS_PON_MODE_EPON:
+            err = ps_process_ind_epon(&msg->pon, msg->ind);
+            break;
+        case PS_PON_MODE_XGPON:
+            err = ps_process_ind_xgpon(&msg->pon, msg->ind);
+            break;
+        default:
+            err = BCM_ERR_STATE; /* not a supported system mode */
+            break;
+        }
+    }
+
+    if (err != BCM_ERR_OK)
+    {
+        bcmolt_auto *ind = msg->ind;
+
+        PS_ERR("Error processing indication (obj_type=%u, subgroup=%u): %s\n", ind->hdr.obj_type, ind->hdr.subgroup, bcmos_strerror(err));
+    }
+
+    /* free the cloned indication since we're done processing it */
+    bcmolt_msg_free(&msg->ind->hdr);
+
+    /* free the internal OS message handle */
+    bcmos_free(os_msg);
+}
+
+void bcmolt_ps_appl_init(void)
+{
+    bcmos_errno err;
+
+#ifdef ENABLE_LOG
+    ps_log_id = bcm_dev_log_id_register("user_appl_ps", DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
+#endif
+
+    err = bcmos_mutex_create(&ps_mutex, 0, NULL);
+    BUG_ON(err != BCM_ERR_OK);
+
+    err = ps_init_task();
+    BUG_ON(err != BCM_ERR_OK);
+
+    err = ps_init_module();
+    BUG_ON(err != BCM_ERR_OK);
+}
+
+bcmos_errno bcmolt_ps_appl_start(const bcmolt_ps_global_cfg *cfg)
+{
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (ps_state.is_running)
+    {
+        bcmos_mutex_unlock(&ps_mutex);
+        PS_ERR("PS application already running\n");
+        return BCM_ERR_STATE;
+    }
+
+    ps_state.cfg = *cfg;
+    ps_state.cfg.max_num_pairs = (ps_state.cfg.max_num_pairs == 0) ? PS_DEFAULT_MAX_PAIRS : ps_state.cfg.max_num_pairs;
+    ps_state.pairs = bcmos_calloc(sizeof(*ps_state.pairs) * ps_state.cfg.max_num_pairs);
+    if (ps_state.pairs == NULL)
+    {
+        BCM_MEMZERO_STRUCT(&ps_state);
+        bcmos_mutex_unlock(&ps_mutex);
+        PS_ERR("memory allocation failed\n");
+        return BCM_ERR_NOMEM;
+    }
+
+    ps_state.is_running = BCMOS_TRUE;
+
+    bcmos_mutex_unlock(&ps_mutex);
+    PS_INFO("PS application started\n");
+    return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_ps_appl_stop(void)
+{
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        bcmos_mutex_unlock(&ps_mutex);
+        PS_ERR("PS application not running\n");
+        return BCM_ERR_STATE;
+    }
+
+    ps_state.is_running = BCMOS_FALSE;
+
+    bcmos_free(ps_state.pairs);
+    BCM_MEMZERO_STRUCT(&ps_state);
+
+    bcmos_mutex_unlock(&ps_mutex);
+    PS_INFO("PS application stopped\n");
+    return BCM_ERR_OK;
+}
+
+bcmos_bool bcmolt_ps_appl_is_running(void)
+{
+    bcmos_bool ret;
+    bcmos_mutex_lock(&ps_mutex);
+    ret = ps_state.is_running;
+    bcmos_mutex_unlock(&ps_mutex);
+    return ret;
+}
+
+bcmos_errno bcmolt_ps_global_cfg_get(bcmolt_ps_global_cfg *cfg)
+{
+    bcmos_errno err;
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        PS_ERR("PS application not running\n");
+        err = BCM_ERR_STATE;
+    }
+    else
+    {
+        *cfg = ps_state.cfg;
+        err = BCM_ERR_OK;
+    }
+
+    bcmos_mutex_unlock(&ps_mutex);
+    return err;
+}
+
+bcmos_errno bcmolt_ps_global_cfg_update(const bcmolt_ps_global_cfg *cfg)
+{
+    bcmos_errno err;
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        PS_ERR("PS application not running\n");
+        err = BCM_ERR_STATE;
+    }
+    else if (cfg->max_num_pairs != ps_state.cfg.max_num_pairs)
+    {
+        PS_ERR("cannot change max number of protected pairs while running\n");
+        err = BCM_ERR_PARM;
+    }
+    else
+    {
+        ps_state.cfg = *cfg;
+        err = BCM_ERR_OK;
+    }
+
+    bcmos_mutex_unlock(&ps_mutex);
+    return err;
+}
+
+bcmos_errno bcmolt_ps_pairs_get(uint16_t array_len, bcmolt_ps_pair *pairs, uint16_t *num_written)
+{
+    bcmos_errno err;
+    uint16_t i;
+
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        PS_ERR("PS application not running\n");
+        err = BCM_ERR_STATE;
+    }
+    else
+    {
+        *num_written = 0;
+        err = BCM_ERR_OK;
+        for (i = 0; i < ps_state.cfg.max_num_pairs; i++)
+        {
+            if (ps_state.pairs[i].is_valid)
+            {
+                if (*num_written == array_len)
+                {
+                    PS_ERR("array size too small to collect all pairs\n");
+                    err = BCM_ERR_PARM;
+                    break;
+                }
+                pairs[*num_written] = ps_state.pairs[i].pair;
+                (*num_written)++;
+            }
+        }
+    }
+
+    bcmos_mutex_unlock(&ps_mutex);
+    return err;
+}
+
+bcmos_errno bcmolt_ps_pair_add(const bcmolt_ps_pair *pair)
+{
+    uint16_t i;
+    bcmos_errno err;
+
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        PS_ERR("PS application not running\n");
+        err = BCM_ERR_STATE;
+    }
+    else if (ps_pon_equal(&pair->working, &pair->standby))
+    {
+        PS_ERR("A PON cannot protect itself\n");
+        err = BCM_ERR_PARM;
+    }
+    else
+    {
+        err = BCM_ERR_OK;
+        for (i = 0; i < ps_state.cfg.max_num_pairs; i++)
+        {
+            if (ps_state.pairs[i].is_valid &&
+                (ps_pon_equal(&pair->working, &ps_state.pairs[i].pair.working) ||
+                ps_pon_equal(&pair->working, &ps_state.pairs[i].pair.standby) ||
+                ps_pon_equal(&pair->standby, &ps_state.pairs[i].pair.working) ||
+                ps_pon_equal(&pair->standby, &ps_state.pairs[i].pair.standby)))
+            {
+                PS_ERR("One of the PONs was already present in an existing pair\n");
+                err = BCM_ERR_ALREADY;
+                break;
+            }
+        }
+
+        if (err == BCM_ERR_OK)
+        {
+            err = BCM_ERR_NORES;
+            for (i = 0; i < ps_state.cfg.max_num_pairs; i++)
+            {
+                if (!ps_state.pairs[i].is_valid)
+                {
+                    ps_state.pairs[i].pair = *pair;
+                    ps_state.pairs[i].is_valid = BCMOS_TRUE;
+                    err = BCM_ERR_OK;
+                    break;
+                }
+            }
+            if (err == BCM_ERR_NORES)
+            {
+                PS_ERR("Not enough memory available to add pair\n");
+            }
+        }
+    }
+
+    bcmos_mutex_unlock(&ps_mutex);
+    return err;
+}
+
+bcmos_errno bcmolt_ps_pon_remove(const bcmolt_ps_pon *pon)
+{
+    uint16_t i;
+    bcmos_errno err;
+
+    bcmos_mutex_lock(&ps_mutex);
+
+    if (!ps_state.is_running)
+    {
+        PS_ERR("PS application not running\n");
+        err = BCM_ERR_STATE;
+    }
+    else
+    {
+        err = BCM_ERR_NOENT;
+        for (i = 0; i < ps_state.cfg.max_num_pairs; i++)
+        {
+            if (ps_state.pairs[i].is_valid &&
+                (ps_pon_equal(pon, &ps_state.pairs[i].pair.working) ||
+                ps_pon_equal(pon, &ps_state.pairs[i].pair.standby)))
+            {
+                ps_state.pairs[i].is_valid = BCMOS_FALSE;
+                err = BCM_ERR_OK;
+                break;
+            }
+        }
+        if (err == BCM_ERR_NOENT)
+        {
+            PS_ERR("Specified PON not found\n");
+        }
+    }
+
+    bcmos_mutex_unlock(&ps_mutex);
+    return err;
+}
+
+bcmos_errno bcmolt_ps_pon_state_get(const bcmolt_ps_pon *pon, bcmolt_ps_pon_state *state, bcmolt_ps_pon *partner)
+{
+    bcmos_errno err;
+    bcmolt_ps_pair *pair;
+
+    bcmos_mutex_lock(&ps_mutex);
+    err = bcmolt_ps_pon_pair_get(pon, &pair);
+    bcmos_mutex_unlock(&ps_mutex);
+
+    if (err == BCM_ERR_OK)
+    {
+        if (ps_pon_equal(pon, &pair->standby))
+        {
+            *state = BCMOLT_PS_PON_STATE_STANDBY;
+            *partner = pair->working;
+        }
+        else
+        {
+            *state = BCMOLT_PS_PON_STATE_WORKING;
+            *partner = pair->standby;
+        }
+        return BCM_ERR_OK;
+    }
+    else if (err == BCM_ERR_NOENT)
+    {
+        *state = BCMOLT_PS_PON_STATE_UNASSOCIATED;
+        return BCM_ERR_OK;
+    }
+    else
+    {
+        return err;
+    }
+}
+
+bcmos_errno bcmolt_ps_process_ind(const bcmolt_ps_pon *pon, bcmolt_auto *ind)
+{
+    bcmos_errno err;
+    bcmolt_msg *ind_clone = NULL;
+
+    if (!bcmolt_ps_appl_is_running())
+    {
+        return BCM_ERR_OK;
+    }
+
+    /* Make a copy of all indications received so they can be handled later asynchronously. We don't validate PON state
+     * here since it might change before we get around to handling it.
+     *
+     * Note that this could be optimized to filter out indications that aren't relevant for protection switching before
+     * copying them, but this is left out for simplicity. */
+    err = bcmolt_msg_clone(&ind_clone, &ind->hdr);
+    if (err != BCM_ERR_OK)
+    {
+        PS_ERR("Indication clone failed: %s\n", bcmos_strerror(err));
+        return err;
+    }
+
+    /* send this indication to the internal task's message queue to be processed */
+    ps_task_msg *msg = bcmos_calloc(sizeof(*msg));
+    if (msg == NULL)
+    {
+        PS_ERR("Message calloc failed\n");
+        bcmolt_msg_free(ind_clone);
+        return BCM_ERR_NOMEM;
+    }
+
+    msg->os_msg.handler = ps_handle_msg;
+    msg->os_msg.release = bcmolt_os_msg_release_cb;
+    msg->pon = *pon;
+    msg->ind = (bcmolt_auto *)ind_clone;
+
+    err = bcmos_msg_send_to_module(BCMOS_MODULE_ID_USER_APPL_PS, &msg->os_msg, 0);
+    if (err != BCM_ERR_OK)
+    {
+        PS_ERR("Message send failed: %s\n", bcmos_strerror(err));
+        return err;
+    }
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno bcmolt_ps_change_pon_to_standby(const bcmolt_ps_pon *pon)
+{
+    bcmos_errno err;
+
+    PS_INFO("Switching PON <%d:%d> working->standby\n", pon->device_id, pon->pon_id);
+    switch (ps_pon_mode_get(pon->device_id))
+    {
+    case PS_PON_MODE_GPON:
+        err = ps_move_to_standby_gpon(pon);
+        break;
+    case PS_PON_MODE_EPON:
+        err = ps_move_to_standby_epon(pon);
+        break;
+    case PS_PON_MODE_XGPON:
+        err = ps_move_to_standby_xgpon(pon);
+        break;
+    default:
+        err = BCM_ERR_STATE; /* not a supported system mode */
+        break;
+    }
+    if (err != BCM_ERR_OK)
+    {
+        PS_ERR("<%d:%d> working->standby API failed: %s\n", pon->device_id, pon->pon_id, bcmos_strerror(err));
+    }
+    return err;
+}
+
+static bcmos_errno bcmolt_ps_change_pon_to_working(const bcmolt_ps_pon *pon)
+{
+    bcmos_errno err;
+
+    PS_INFO("Switching PON <%d:%d> standby->working\n", pon->device_id, pon->pon_id);
+    switch (ps_pon_mode_get(pon->device_id))
+    {
+    case PS_PON_MODE_GPON:
+        err = ps_move_to_working_gpon(pon);
+        break;
+    case PS_PON_MODE_EPON:
+        err = ps_move_to_working_epon(pon);
+        break;
+    case PS_PON_MODE_XGPON:
+        err = ps_move_to_working_xgpon(pon);
+        break;
+    default:
+        err = BCM_ERR_STATE; /* not a supported system mode */
+        break;
+    }
+    if (err != BCM_ERR_OK)
+    {
+        PS_ERR("<%d:%d> standby->working API failed: %s\n", pon->device_id, pon->pon_id, bcmos_strerror(err));
+    }
+    return err;
+}
+
+static bcmos_errno bcmolt_ps_disable_tx_optics(const bcmolt_ps_pon *pon)
+{
+    PS_INFO("Disabling TRX for PON <%d:%d:%d>\n", pon->device_id, pon->pon_id, pon->transceiver_id);
+
+    /* This disables the TX optical channel on a given PON port on the BCM68620 SVK board.
+       For other boards, this must be changed to match the board design. */
+#ifndef SIMULATION_BUILD
+    return bcm_board_trx_enable(pon->transceiver_id, BCMOS_FALSE);
+#else
+    return BCM_ERR_OK;
+#endif
+}
+
+static bcmos_errno bcmolt_ps_enable_tx_optics(const bcmolt_ps_pon *pon)
+{
+#ifndef SIMULATION_BUILD
+    bcmos_errno ret;
+#endif
+
+    PS_INFO("Enabling TRX for PON <%d:%d:%d>\n", pon->device_id, pon->pon_id, pon->transceiver_id);
+
+    /* This enables the TX optical channel on a given PON port on the BCM68620 SVK board.
+       For other boards, this must be changed to match the board design. */
+#ifndef SIMULATION_BUILD
+    ret = bcm_board_trx_enable(pon->transceiver_id, BCMOS_TRUE);
+    bcmos_usleep(ps_state.cfg.trx_warming_delay);
+    return ret;
+#else
+    return BCM_ERR_OK;
+#endif
+}
+
+bcmos_errno bcmolt_ps_switch_perform(const bcmolt_ps_pon *pon)
+{
+    bcmos_errno err;
+    bcmolt_ps_pair *pair;
+    bcmolt_ps_pon temp_pon;
+
+    bcmos_mutex_lock(&ps_mutex);
+    ps_state.switch_start_time_us = bcmos_timestamp();
+    err = bcmolt_ps_pon_pair_get(pon, &pair);
+    bcmos_mutex_unlock(&ps_mutex);
+
+    if (err != BCM_ERR_OK)
+    {
+        goto exit;
+    }
+
+    if (ps_state.cfg.switch_sequence == BCMOLT_PS_SWITCH_SEQUENCE_TRX_FIRST)
+    {
+        /* step 1: disable optical TX channel of the working PON */
+        err = bcmolt_ps_disable_tx_optics(&pair->working);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 2: enable optical TX channel of the standby PON */
+        err = bcmolt_ps_enable_tx_optics(&pair->standby);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 3: change the standby PON state to working */
+        err = bcmolt_ps_change_pon_to_working(&pair->standby);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 4: change the working PON state to standby */
+        err = bcmolt_ps_change_pon_to_standby(&pair->working);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+    }
+    else /* ps_state.cfg.switch_sequence == BCMOLT_PS_SWITCH_SEQUENCE_STANDARD */
+    {
+        /* step 1: change the working PON state to standby */
+        err = bcmolt_ps_change_pon_to_standby(&pair->working);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 2: disable optical TX channel of the working PON */
+        err = bcmolt_ps_disable_tx_optics(&pair->working);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 3: enable optical TX channel of the standby PON */
+        err = bcmolt_ps_enable_tx_optics(&pair->standby);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+
+        /* step 4: change the standby PON state to working */
+        err = bcmolt_ps_change_pon_to_working(&pair->standby);
+        if (err != BCM_ERR_OK)
+        {
+            goto exit;
+        }
+    }
+
+    /* step 5: update internal database for new PON states */
+    bcmos_mutex_lock(&ps_mutex);
+    temp_pon = pair->working;
+    pair->working = pair->standby;
+    pair->standby = temp_pon;
+    bcmos_mutex_unlock(&ps_mutex);
+
+exit:
+    if (err == BCM_ERR_OK)
+    {
+        PS_INFO(
+            "<%d:%d> switchover started successfully (<%d:%d> is now standby)\n",
+            pair->working.device_id,
+            pair->working.pon_id,
+            pair->standby.device_id,
+            pair->standby.pon_id);
+    }
+    else if (pair)
+    {
+        PS_INFO(
+            "<%d:%d> switchover encountered an error: %s\n",
+            pair->working.device_id,
+            pair->working.pon_id,
+            bcmos_strerror(err));
+    }
+
+    return err;
+}
+
+uint32_t ps_get_last_switch_start_time_us(void)
+{
+    uint32_t ret;
+    bcmos_mutex_lock(&ps_mutex);
+    ret = ps_state.switch_start_time_us;
+    bcmos_mutex_unlock(&ps_mutex);
+    return ret;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.h b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.h
new file mode 100644
index 0000000..50224be
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps.h
@@ -0,0 +1,210 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_PS_H_
+#define _BCMOLT_USER_APPL_PS_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+
+/** Conditions that can cause a protection switch to be performed. */
+typedef enum
+{
+    BCMOLT_PS_SWITCH_CONDITION_LOS,    /**< Switch when LoS alarm is received for a working PON. */
+    BCMOLT_PS_SWITCH_CONDITION_MANUAL, /**< Do not switch automatically. */
+} bcmolt_ps_switch_condition;
+
+/** How provisioning should be mirrored from working to standby. */
+typedef enum
+{
+    BCMOLT_PS_MIRROR_MODE_AUTO, /**< Whenever an indication is received on the working PON, mirror all necessary
+                                     configuration to standby PON. */
+    BCMOLT_PS_MIRROR_MODE_NONE, /**< Do not automatically mirror any provisioning from working to standby. */
+} bcmolt_ps_mirror_mode;
+
+/** What the order of operations should be when performing a switchover. */
+typedef enum
+{
+    BCMOLT_PS_SWITCH_SEQUENCE_STANDARD,  /**< "standard" order:
+                                              1: Change the working PON state to standby.
+                                              2: Disable optical TX channel of working PON.
+                                              3: Enable optical TX channel of standby PON.
+                                              4: Change the standby PON state to working. */
+    BCMOLT_PS_SWITCH_SEQUENCE_TRX_FIRST, /**< Perform transceiver enable/disable first:
+                                              1: Disable optical TX channel of working PON.
+                                              2: Enable optical TX channel of standby PON.
+                                              3: Change the standby PON state to working.
+                                              4: Change the working PON state to standby. */
+} bcmolt_ps_switch_sequence;
+
+/** Global configuration for the protection switching application. */
+typedef struct
+{
+    uint16_t max_num_pairs;                        /**< Maximum number of protected pairs supported. */
+    bcmolt_ps_switch_condition switch_condition;   /**< Condition that should cause a switchover. */
+    bcmolt_ps_switch_sequence switch_sequence;     /**< Order of operations when performing a switchover. */
+
+    /* The following properties are only used in GPON mode. */
+    bcmolt_ps_mirror_mode mirror_mode;             /**< How provisioning should be mirrored from working to standby. */
+    bcmos_bool mirror_mac_entries;                 /**< If TRUE, mirror all MAC learning entries to the standby PON
+                                                        when learning indications are received from the working PON.
+                                                        This will also cause entries to be automatically deleted when
+                                                        aging indications are received and moved when MAC move
+                                                        indications are received. */
+    bcmos_bool static_mac_entries;                 /**< If TRUE, when entries are mirrored to the standby PON, treat
+                                                        them as "static" (not aged). */
+    uint32_t trx_warming_delay;                    /**< Delay (in usec) to wait after TRX enable, in order to
+                                                        guarantee that POPUP PLOAMs are correctly seen by ONUs. */
+} bcmolt_ps_global_cfg;
+
+/** A single PON on an OLT device - half of a protected pair. */
+typedef struct
+{
+    bcmolt_devid device_id;
+    bcmolt_pon_ni pon_id;
+    bcmolt_pon_ni transceiver_id;
+} bcmolt_ps_pon;
+
+/** A protected pair of PONs, on the same OLT device or different devices. */
+typedef struct
+{
+    bcmolt_ps_pon working;
+    bcmolt_ps_pon standby;
+} bcmolt_ps_pair;
+
+/** Possible states for a single PON from a protection switching point-of-view. */
+typedef enum
+{
+    BCMOLT_PS_PON_STATE_UNASSOCIATED, /**< The PON is not part of a protected pair. */
+    BCMOLT_PS_PON_STATE_WORKING,      /**< The PON is designated as working with the TRX enabled while active. */
+    BCMOLT_PS_PON_STATE_STANDBY,      /**< The PON is designated as standby with the TRX disabled while active. */
+} bcmolt_ps_pon_state;
+
+/** Initialize the protection switching application (this should be called as part of application startup). */
+void bcmolt_ps_appl_init(void);
+
+/** Start the protection switching application (this is global for the entire system).
+ *
+ * \param[in] cfg Global protection switching configuration.
+ * \return        BCM_ERR_OK if the application was started successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_appl_start(const bcmolt_ps_global_cfg *cfg);
+
+/** Stop the protection switching application (this is global for the entire system).
+ *
+ * \return        BCM_ERR_OK if the application was stopped successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_appl_stop(void);
+
+/** Query whether the protection switching application is currently running.
+ *
+ * \return        BCMOS_TRUE if the application is running, BCMOS_FALSE otherwise.
+ */
+bcmos_bool bcmolt_ps_appl_is_running(void);
+
+/** Get the configuration of the running protection switching application (this is global for the entire system).
+ *
+ * \param[out] cfg Global protection switching configuration.
+ * \return         BCM_ERR_OK if the configuration was retrieved successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_global_cfg_get(bcmolt_ps_global_cfg *cfg);
+
+/** Change the configuration of the running protection switching application (this is global for the entire system).
+ *
+ * \param[in] cfg Global protection switching configuration.
+ * \return        BCM_ERR_OK if the configuration was updated successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_global_cfg_update(const bcmolt_ps_global_cfg *cfg);
+
+/** Get the list of all protected pairs currently tracked by the protection switching application.
+ *
+ * \param[in]  array_len   The length of the pairs array.
+ * \param[out] pairs       An array of protected pairs to fill.
+ * \param[out] num_written The number of pairs written to the array.
+ * \return                 BCM_ERR_OK if the array was written successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_pairs_get(uint16_t array_len, bcmolt_ps_pair *pairs, uint16_t *num_written);
+
+/** Add a protected pair to the protection switching database.
+ *
+ * After this has been called, provisioning will be mirrored from the working PON to the standby PON according to the
+ * mirror_mode global parameter, and a switch will be performed per the switch_condition global parameter.
+ *
+ * \param[in] pair A pair of PONs to add - they can be on the same OLT device or different devices.
+ * \return         BCM_ERR_OK if the pair was added successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_pair_add(const bcmolt_ps_pair *pair);
+
+/** Remove the protected pair containing the specified PON from the protection switching database.
+*
+* \param[in] pon A PON that is a member of a protection pair.
+* \return        BCM_ERR_OK if the PON was removed successfully, <0 otherwise.
+*/
+bcmos_errno bcmolt_ps_pon_remove(const bcmolt_ps_pon *pon);
+
+/** Query for the current state of a protected PON.
+*
+* \param[in]  pon     The PON to query.
+* \param[out] state   The protection state of the PON (e.g. working/standby).
+* \param[out] partner If this PON is protected, the other half of the protected pair.
+* \return             BCM_ERR_OK if the PON was queried successfully, <0 otherwise.
+*/
+bcmos_errno bcmolt_ps_pon_state_get(const bcmolt_ps_pon *pon, bcmolt_ps_pon_state *state, bcmolt_ps_pon *partner);
+
+/** Process an indication received by the working PON.
+ *
+ * The action taken will depend on the object/indication type and the mirror_mode global parameter.
+ * If the application isn't running, the supplied PON isn't in a protected working state or the indication isn't
+ * relevant for protection switching, the indication will be ignored.
+ *
+ * Note: this function does not free the indication.
+ * The caller must free it using bcmos_msg_free() after calling this function.
+ *
+ * \param[in] pon The PON on which the indication was received.
+ * \param[in] ind The indication that was received.
+ * \return        BCM_ERR_OK if the indication was processed successfully or ignored, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_process_ind(const bcmolt_ps_pon *pon, bcmolt_auto *ind);
+
+/** Immediately perform a protection switch on the given PON, which must belong to a protected pair.
+ *
+ * This function is primarily intended for use with the BCMOLT_PS_SWITCH_CONDITION_MANUAL switch condition, or for
+ * debugging purposes.
+ *
+ * Note that this doesn't wait for the switch to complete - it merely just configures the transceivers and initiates
+ * the PON state transitions.  To wait for completion, the host must wait for the PON NI state change complete or
+ * "protection_switching_traffic_resume" indications to be received.
+ *
+ * \param[in] pair The PON on which to perform the switch (either the working or standby PON of a protected pair).
+ * \return         BCM_ERR_OK if the switch was successful, <0 otherwise.
+ */
+bcmos_errno bcmolt_ps_switch_perform(const bcmolt_ps_pon *pon);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.c b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.c
new file mode 100644
index 0000000..d75bd00
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.c
@@ -0,0 +1,291 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+#include "bcmolt_user_appl_ps_cli.h"
+#include "bcmolt_user_appl_ps.h"
+
+static bcmos_errno ps_cmd_start(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *parm;
+    bcmolt_ps_global_cfg cfg = {};
+
+    parm = bcmcli_find_named_parm(session, "max_pairs");
+    if (parm != NULL)
+    {
+        cfg.max_num_pairs = (uint16_t)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "switch_condition");
+    if (parm != NULL)
+    {
+        cfg.switch_condition = (bcmolt_ps_switch_condition)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "switch_sequence");
+    if (parm != NULL)
+    {
+        cfg.switch_sequence = (bcmolt_ps_switch_sequence)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "mirror_mode");
+    if (parm != NULL)
+    {
+        cfg.mirror_mode = (bcmolt_ps_mirror_mode)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "mirror_mac_entries");
+    if (parm != NULL)
+    {
+        cfg.mirror_mac_entries = (bcmos_bool)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "static_mac_entries");
+    if (parm != NULL)
+    {
+        cfg.static_mac_entries = (bcmos_bool)parm->value.number;
+    }
+
+    parm = bcmcli_find_named_parm(session, "trx_warming_delay");
+    if (parm != NULL)
+    {
+        cfg.trx_warming_delay = parm->value.number;
+    }
+
+    return bcmolt_ps_appl_start(&cfg);
+}
+
+static bcmos_errno ps_cmd_stop(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    return bcmolt_ps_appl_stop();
+}
+
+static bcmos_errno ps_cmd_get_cfg(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmos_errno err;
+    bcmolt_ps_global_cfg cfg;
+    
+    err = bcmolt_ps_global_cfg_get(&cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    bcmcli_session_print(session, "max_pairs=%d\n", cfg.max_num_pairs);
+    bcmcli_session_print(
+        session,
+        "switch_condition=%s\n",
+        cfg.switch_condition == BCMOLT_PS_SWITCH_CONDITION_LOS ? "los" : "manual");
+    bcmcli_session_print(
+        session,
+        "switch_sequence=%s\n",
+        cfg.switch_sequence == BCMOLT_PS_SWITCH_SEQUENCE_STANDARD ? "std" : "trx_first");
+    bcmcli_session_print(
+        session,
+        "mirror_mode=%s\n",
+        cfg.mirror_mode == BCMOLT_PS_MIRROR_MODE_AUTO ? "auto" : "none");
+    bcmcli_session_print(session, "mirror_mac_entries=%s\n", cfg.mirror_mac_entries ? "yes" : "no");
+    bcmcli_session_print(session, "static_mac_entries=%s\n", cfg.static_mac_entries ? "yes" : "no");
+    bcmcli_session_print(session, "trx_warming_delay=%u\n", cfg.trx_warming_delay);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno ps_cmd_show_pairs(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmos_errno err;
+    bcmolt_ps_global_cfg cfg;
+    bcmolt_ps_pair *pairs;
+    uint16_t num_written;
+
+    err = bcmolt_ps_global_cfg_get(&cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    pairs = bcmos_calloc(sizeof(*pairs) * cfg.max_num_pairs);
+
+    err = bcmolt_ps_pairs_get(cfg.max_num_pairs, pairs, &num_written);
+    if (err == BCM_ERR_OK)
+    {
+        uint16_t i;
+
+        bcmcli_session_print(session, "      working  | standby\n");
+        bcmcli_session_print(session, "      =========+=========\n");
+        for (i = 0; i < num_written; i++)
+        {
+            bcmcli_session_print(
+                session,
+                "[%2d]: %2d.%2d.%2d | %2d.%2d.%2d\n",
+                i,
+                pairs[i].working.device_id,
+                pairs[i].working.pon_id,
+                pairs[i].working.transceiver_id,
+                pairs[i].standby.device_id,
+                pairs[i].standby.pon_id,
+                pairs[i].standby.transceiver_id);
+        }
+    }
+
+    bcmos_free(pairs);
+    return err;
+}
+
+static bcmos_errno ps_cmd_add_pair(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *working_device = bcmcli_find_named_parm(session, "working.device");
+    bcmcli_cmd_parm *working_pon = bcmcli_find_named_parm(session, "working.pon");
+    bcmcli_cmd_parm *working_transceiver = bcmcli_find_named_parm(session, "working.transceiver");
+    bcmcli_cmd_parm *standby_device = bcmcli_find_named_parm(session, "standby.device");
+    bcmcli_cmd_parm *standby_pon = bcmcli_find_named_parm(session, "standby.pon");
+    bcmcli_cmd_parm *standby_transceiver = bcmcli_find_named_parm(session, "standby.transceiver");
+
+    bcmolt_ps_pair pair =
+    {
+        .working =
+        {
+            .device_id = (bcmolt_devid)working_device->value.number,
+            .pon_id = (bcmolt_pon_ni)working_pon->value.number,
+            .transceiver_id = (bcmolt_pon_ni)working_transceiver->value.number
+        },
+        .standby =
+        {
+            .device_id = (bcmolt_devid)standby_device->value.number,
+            .pon_id = (bcmolt_pon_ni)standby_pon->value.number,
+            .transceiver_id = (bcmolt_pon_ni)standby_transceiver->value.number
+        },
+    };
+
+    // Set transceiver_id to pon_id if default
+    if (pair.working.transceiver_id == (bcmolt_pon_ni)-1)
+    {
+        pair.working.transceiver_id = pair.working.pon_id;
+    }
+    if (pair.standby.transceiver_id == (bcmolt_pon_ni)-1)
+    {
+        pair.standby.transceiver_id = pair.standby.pon_id;
+    }
+
+    return bcmolt_ps_pair_add(&pair);
+}
+
+static bcmos_errno ps_cmd_remove_pon(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *device = bcmcli_find_named_parm(session, "device");
+    bcmcli_cmd_parm *pon = bcmcli_find_named_parm(session, "pon");
+
+    bcmolt_ps_pon ps_pon =
+    {
+        .device_id = (bcmolt_devid)device->value.number,
+        .pon_id = (bcmolt_pon_ni)pon->value.number
+    };
+
+    return bcmolt_ps_pon_remove(&ps_pon);
+}
+
+static bcmos_errno ps_cmd_perform_switch(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmcli_cmd_parm *device = bcmcli_find_named_parm(session, "device");
+    bcmcli_cmd_parm *pon = bcmcli_find_named_parm(session, "pon");
+
+    bcmolt_ps_pon ps_pon =
+    {
+        .device_id = (bcmolt_devid)device->value.number,
+        .pon_id = (bcmolt_pon_ni)pon->value.number
+    };
+
+    return bcmolt_ps_switch_perform(&ps_pon);
+}
+
+bcmos_errno bcmolt_user_appl_ps_cli_init(bcmcli_entry *top_dir)
+{
+    static bcmcli_enum_val switch_cond_table[] =
+    {
+        { .name = "los",    .val = BCMOLT_PS_SWITCH_CONDITION_LOS },
+        { .name = "manual", .val = BCMOLT_PS_SWITCH_CONDITION_MANUAL },
+        BCMCLI_ENUM_LAST
+    };
+
+    static bcmcli_enum_val switch_seq_table[] =
+    {
+        { .name = "std",       .val = BCMOLT_PS_SWITCH_SEQUENCE_STANDARD },
+        { .name = "trx_first", .val = BCMOLT_PS_SWITCH_SEQUENCE_TRX_FIRST },
+        BCMCLI_ENUM_LAST
+    };
+
+    static bcmcli_enum_val mirror_mode_table[] =
+    {
+        { .name = "auto", .val = BCMOLT_PS_MIRROR_MODE_AUTO },
+        { .name = "none", .val = BCMOLT_PS_MIRROR_MODE_NONE },
+        BCMCLI_ENUM_LAST
+    };
+
+    bcmcli_entry *dir =
+        bcmcli_dir_add(top_dir, "ps", "Protection switching user application", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD(dir, "start", "Start protection switching application", ps_cmd_start,
+        BCMCLI_MAKE_PARM("max_pairs", "Max number of protected pairs", BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM_ENUM("switch_condition", "Switch condition", switch_cond_table, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM_ENUM("switch_sequence", "Switch sequence", switch_seq_table, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM_ENUM("mirror_mode", "Mirror mode", mirror_mode_table, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM_ENUM(
+            "mirror_mac_entries", "Mirror MAC entries", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM_ENUM(
+            "static_mac_entries", "Static MAC entries", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL),
+        BCMCLI_MAKE_PARM("trx_warming_delay", "TRX warming delay (in usec)", BCMCLI_PARM_UDECIMAL, BCMCLI_PARM_FLAG_OPTIONAL));
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "stop", "Stop protection switching application", ps_cmd_stop);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "get_cfg", "Show running configuration parameters", ps_cmd_get_cfg);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "show_pairs", "Show current protected pairs", ps_cmd_show_pairs);
+
+    BCMCLI_MAKE_CMD(dir, "add_pair", "Add protected pair", ps_cmd_add_pair,
+        BCMCLI_MAKE_PARM("working.device", "Device ID of the working PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("working.pon", "PON NI of the working PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM_DEFVAL("working.transceiver", "Transceiver Id of the working PON",
+                                BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_OPTIONAL, -1),
+        BCMCLI_MAKE_PARM("standby.device", "Device ID of the standby PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("standby.pon", "PON NI of the standby PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM_DEFVAL("standby.transceiver", "Transceiver Id of the standby PON",
+                                BCMCLI_PARM_NUMBER, BCMCLI_PARM_FLAG_OPTIONAL, -1));
+
+    BCMCLI_MAKE_CMD(dir, "remove_pon", "Remove protected pair for (either) PON", ps_cmd_remove_pon,
+        BCMCLI_MAKE_PARM("device", "Device ID of the either working/standby PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("pon", "PON NI of the either working/standby PON", BCMCLI_PARM_NUMBER, 0));
+
+    BCMCLI_MAKE_CMD(dir, "perform_switch", "Perform protection switch manually", ps_cmd_perform_switch,
+        BCMCLI_MAKE_PARM("device", "Device ID of the either working/standby PON", BCMCLI_PARM_NUMBER, 0),
+        BCMCLI_MAKE_PARM("pon", "PON NI of the either working/standby PON", BCMCLI_PARM_NUMBER, 0));
+
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.h b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.h
new file mode 100644
index 0000000..db77c09
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_cli.h
@@ -0,0 +1,38 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_PS_CLI_H_
+#define _BCMOLT_USER_APPL_PS_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+
+bcmos_errno bcmolt_user_appl_ps_cli_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_epon.c b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_epon.c
new file mode 100644
index 0000000..dab93cb
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_epon.c
@@ -0,0 +1,188 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include <bcm_dev_log.h>
+#include <bcmos_common.h>
+#include "bcmolt_user_appl_ps.h"
+#include "bcmolt_user_appl_ps_internal.h"
+
+
+static bcmos_errno ps_handle_ni_no_reports(bcmolt_ps_global_cfg * global_cfg, const bcmolt_ps_pon *pon, const bcmolt_epon_ni_no_reports * ind)
+{
+    if (ind->data.alarm_status == BCMOLT_STATUS_OFF)
+    {
+        PS_INFO("<%d:%d> LoS alarm cleared\n", pon->device_id, pon->pon_id);
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("<%d:%d> LoS alarm raised\n", pon->device_id, pon->pon_id);
+
+    if (global_cfg->switch_condition == BCMOLT_PS_SWITCH_CONDITION_MANUAL)
+    {
+        /* don't react to the switch if we're in manual switch mode */
+        return BCM_ERR_OK;
+    }
+    else
+    {
+        return bcmolt_ps_switch_perform(pon);
+    }
+}
+
+static bcmos_errno ps_handle_logical_link_discovered(bcmolt_ps_global_cfg * global_cfg, const bcmolt_ps_pon *pon, const bcmolt_epon_link_mpcp_discovered * ind)
+{
+    bcmolt_ps_pon_state state;
+    bcmolt_ps_pon partner;
+    bcmos_errno result;
+
+    if (global_cfg->mirror_mode != BCMOLT_PS_MIRROR_MODE_AUTO)
+    {
+        return BCM_ERR_OK;
+    }
+    
+    //At this point we know we have a link discovered on a PON that is in the PROTECTED WORKING state.
+    //This means we want to make sure that is is setup on the PROCTECTED STANDBY PON.  It is possible
+    //That this link has been previously configured on the STANDBY PON so we have to handle that case
+    //gracefully as well.
+    result = bcmolt_ps_pon_state_get(pon, &state, &partner);
+    PS_INFO("pon %d, bcmolt_ps_pon_state_get result == %d, state == %d, partner.pon_id == %d\n", pon->pon_id, result, state, partner.pon_id);
+    if ((BCM_ERR_OK == result) && (state == BCMOLT_PS_PON_STATE_WORKING))
+    {
+        bcmolt_epon_ni_add_protected_standby_link add_standby = {};
+        bcmolt_epon_ni_key key = {};
+        PS_INFO("Mirroring received link discovered on working PON\n");
+
+        key.epon_ni = (bcmolt_epon_ni)partner.pon_id;  //Setup the standby PON as the location to add the link.
+        
+        //Setup operation key for api call.
+        BCMOLT_OPER_INIT(&add_standby, epon_ni, add_protected_standby_link, key);
+
+        //Copy over the data from the received messages:
+        BCMOLT_OPER_PROP_SET(&add_standby, epon_ni, add_protected_standby_link, mac_address, ind->key.mac_address);
+        BCMOLT_OPER_PROP_SET(&add_standby, epon_ni, add_protected_standby_link, working_link_info, ind->data.link_info);
+
+        result = bcmolt_oper_submit(partner.device_id, &add_standby.hdr);
+
+        //We have multiple acceptable return codes here:  
+        //  BCM_ERR_OK      - Indicates new entry created.
+        //  BCM_ERR_ALREADY - Indicates the entry already exists (possible after deregistration/reregistration).
+        PS_INFO("Mirroring add protected standby link result %d\n", result);
+
+        if ( (result == BCM_ERR_OK) || (result == BCM_ERR_ALREADY) )
+        {
+            bcmolt_epon_link_static_registration static_reg = {};
+            bcmolt_epon_link_key link_key = {};
+
+            link_key.epon_ni = (bcmolt_epon_ni)partner.pon_id;
+            link_key.mac_address = ind->key.mac_address;
+
+            //Setup operation key for api call.
+            BCMOLT_OPER_INIT(&static_reg, epon_link, static_registration, link_key);
+            BCMOLT_OPER_PROP_SET(&static_reg, epon_link, static_registration, laseron_time_tq, ind->data.link_info.onu_laser_on_time_tq);
+            BCMOLT_OPER_PROP_SET(&static_reg, epon_link, static_registration, laseroff_time_tq, ind->data.link_info.onu_laser_off_time_tq);
+            BCMOLT_OPER_PROP_SET(&static_reg, epon_link, static_registration, range_value_tq, ind->data.link_info.range_value_tq);
+            BCMOLT_OPER_PROP_SET(&static_reg, epon_link, static_registration, pending_grants, ind->data.link_info.pending_grants);
+            result = bcmolt_oper_submit(partner.device_id, &static_reg.hdr);
+            if (result != BCM_ERR_OK)
+            {
+                PS_ERR("Static registration failed %d\n", result);
+            }
+            else
+            {
+                PS_INFO("Static registration success\n");
+            }
+        }
+        else
+        {
+            PS_ERR("Add protected standby failed %d\n", result);
+        }
+    }
+    return BCM_ERR_OK;
+}
+
+bcmos_errno ps_process_ind_epon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind)
+{
+    bcmos_errno err;
+    bcmolt_ps_global_cfg global_cfg;
+
+    err = bcmolt_ps_global_cfg_get(&global_cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    PS_INFO("ps_process_ind_epon Type: %d CFG_ID: %d\n", ind->hdr.obj_type, ind->hdr.subgroup);
+    switch (ind->hdr.obj_type)
+    {
+    case BCMOLT_OBJ_ID_EPON_NI:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_EPON_NI_AUTO_CFG_ID_NO_REPORTS:
+            return ps_handle_ni_no_reports(&global_cfg, pon, ((const bcmolt_epon_ni_no_reports *)ind));
+        default:
+            return BCM_ERR_OK;
+        }
+    case BCMOLT_OBJ_ID_EPON_LINK:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_EPON_LINK_AUTO_CFG_ID_MPCP_DISCOVERED:
+            return ps_handle_logical_link_discovered(&global_cfg, pon, ((const bcmolt_epon_link_mpcp_discovered *)ind));
+        default:
+            return BCM_ERR_OK;
+        }
+    default:
+        return BCM_ERR_OK;
+    }
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno ps_move_to_standby_epon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_epon_ni_set_epon_ni_en_state set_pon_state; 
+    bcmolt_epon_ni_key key = {};
+    key.epon_ni = pon->pon_id;
+
+    BCMOLT_OPER_INIT(&set_pon_state, epon_ni, set_epon_ni_en_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, epon_ni, set_epon_ni_en_state, new_state, BCMOLT_EPON_NI_EN_STATE_PROTECTED_STANDBY);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
+
+bcmos_errno ps_move_to_working_epon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_epon_ni_set_epon_ni_en_state set_pon_state;
+    bcmolt_epon_ni_key key = {};
+    key.epon_ni = pon->pon_id;
+
+    BCMOLT_OPER_INIT(&set_pon_state, epon_ni, set_epon_ni_en_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, epon_ni, set_epon_ni_en_state, new_state, BCMOLT_EPON_NI_EN_STATE_PROTECTED_WORKING);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
\ No newline at end of file
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_gpon.c b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_gpon.c
new file mode 100644
index 0000000..f43e7fb
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_gpon.c
@@ -0,0 +1,878 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmolt_host_api.h>
+#include "bcmolt_user_appl_ps.h"
+#include "bcmolt_user_appl_ps_internal.h"
+
+static bcmos_errno ps_handle_ni_los(
+    const bcmolt_ps_global_cfg *global_cfg,
+    const bcmolt_ps_pon *pon,
+    const bcmolt_gpon_ni_los *ind)
+{
+    if (ind->data.status == BCMOLT_STATUS_OFF)
+    {
+        PS_INFO("<%d:%d> LoS alarm cleared\n", pon->device_id, pon->pon_id);
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("<%d:%d> LoS alarm raised\n", pon->device_id, pon->pon_id);
+
+    if (global_cfg->switch_condition == BCMOLT_PS_SWITCH_CONDITION_MANUAL)
+    {
+        /* don't react to the switch if we're in manual switch mode */
+        return BCM_ERR_OK;
+    }
+    else
+    {
+        return bcmolt_ps_switch_perform(pon);
+    }
+}
+
+static bcmos_errno ps_handle_ni_state_changed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_ni_state_change_completed *ind)
+{
+    bcmolt_gpon_ni_key key = { partner->pon_id };
+    bcmolt_gpon_ni_set_pon_state oper;
+
+    BCMOLT_OPER_INIT(&oper, gpon_ni, set_pon_state, key);
+
+    if (ind->data.previous_state == BCMOLT_PON_STATE_INACTIVE &&
+        ind->data.new_state == BCMOLT_PON_STATE_ACTIVE_WORKING)
+    {
+        PS_INFO("%s: activating PON <%d:%d>\n", __FUNCTION__, partner->device_id, key.pon_ni);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_STANDBY);
+    }
+    else if (ind->data.previous_state == BCMOLT_PON_STATE_ACTIVE_WORKING &&
+        ind->data.new_state == BCMOLT_PON_STATE_INACTIVE)
+    {
+        PS_INFO("%s: deactivating PON <%d:%d>\n", __FUNCTION__, partner->device_id, key.pon_ni);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_INACTIVE);
+    }
+    else
+    {
+        /* only change the standby PON's state if we're pre-provisioning the working PON
+          (the switchover is handled separately) */
+        return BCM_ERR_OK;
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_ni_traffic_resume(
+    const bcmolt_ps_pon *pon,
+    const bcmolt_gpon_ni_protection_switching_traffic_resume *ind)
+{
+    uint32_t after_us  = bcmos_timestamp();
+    uint32_t before_us = ps_get_last_switch_start_time_us();
+    uint32_t time_taken_us = (after_us > before_us) ? after_us - before_us : before_us - after_us;
+    const char *traffic_resume_result_str = NULL;
+
+    switch (ind->data.result)
+    {
+    case BCMOLT_TRAFFIC_RESUME_RESULT_SUCCESS:
+        traffic_resume_result_str = "success";
+        break;
+    case BCMOLT_TRAFFIC_RESUME_RESULT_FAILURE:
+        traffic_resume_result_str = "failure";
+        break;
+    case BCMOLT_TRAFFIC_RESUME_RESULT_SUSPECTED_LOS:
+        traffic_resume_result_str = "suspected_los";
+        break;
+    default:
+        break;
+    }
+
+    PS_INFO(
+        "<%d:%d> Traffic resume %s. Time since start of switch: %d ms.\n",
+        pon->device_id,
+        pon->pon_id,
+        traffic_resume_result_str,
+        time_taken_us / 1000);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno ps_handle_ni_onus_ranged(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_ni_protection_switching_onus_ranged *ind)
+{
+    uint32_t i;
+    bcmos_errno last_err = BCM_ERR_OK;
+
+    /* apply all of the new ONU EQDs to the standby PON */
+    for (i = 0; i < ind->data.onus.len; i++)
+    {
+        bcmolt_gpon_onu_key key = { partner->pon_id, ind->data.onus.val[i].onu_id };
+        bcmolt_gpon_onu_cfg cfg;
+        bcmos_errno err;
+
+        PS_INFO(
+            "%s: updating EQD <%d:%d> ONU %d -> %d\n",
+            __FUNCTION__,
+            partner->device_id,
+            key.pon_ni,
+            key.onu_id,
+            ind->data.onus.val[i].eqd);
+
+        BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+        BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, ranging_time, ind->data.onus.val[i].eqd);
+        err = bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+        if (err != BCM_ERR_OK)
+        {
+            PS_ERR("EQD update failed: %s\n", bcmos_strerror(err));
+            last_err = err;
+        }
+    }
+
+    return last_err;
+}
+
+static bcmos_errno ps_handle_onu_ranging_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_ranging_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+
+    /* only react to success indications */
+    if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO(
+        "%s: updating EQD <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.eqd);
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, ranging_time, ind->data.eqd);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_key_exchange_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_key_exchange_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+
+    PS_INFO("%s: updating AES key <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, aes_encryption_key, ind->data.new_key);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_password_authentication_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_password_authentication_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+
+    /* only react to success indications */
+    if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: updating password <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, password, ind->data.password);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_activation_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_onu_activation_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_set_onu_state oper;
+
+    /* only react to success indications */
+    if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: activating <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, gpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ACTIVE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_deactivation_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_onu_deactivation_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_set_onu_state oper;
+
+    /* Note: we react to this indication whether or not the deactivation was successful.  Failure indicates that the
+     * ONU didn't respond to the deactivation, but we still remove the ONU from OLT hardware so it must be mirrored. */
+
+    PS_INFO("%s: deactivating <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, gpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_INACTIVE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_enable_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_onu_enable_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_set_onu_state oper;
+
+    /* don't try to mirror broadcast enable/disable */
+    if (key.onu_id == BCMOLT_GPON_ONU_ID_INVALID)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: enabling <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, gpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ENABLE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_disable_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_onu_disable_completed *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_set_onu_state oper;
+
+    /* don't try to mirror broadcast enable/disable */
+    if (key.onu_id == BCMOLT_GPON_ONU_ID_INVALID)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: disabling <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, gpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, gpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_DISABLE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static void ps_alarm_state_init(bcmolt_gpon_onu_alarm_state *alarm_state)
+{
+    alarm_state->losi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->lofi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->loami = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dgi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->tiwi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dowi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sufi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sfi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sdi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dfi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->loai = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->loki = BCMOLT_STATUS_NO_CHANGE;
+}
+
+static bcmos_errno ps_handle_onu_alarm(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_onu_alarm *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.losi = ind->data.onu_alarm.losi;
+    alarm_state.lofi = ind->data.onu_alarm.lofi;
+    alarm_state.loami = ind->data.onu_alarm.loami;
+    
+    PS_INFO(
+        "%s: updating ONU alarms <%d:%d> ONU %d -> %d/%d/%d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        alarm_state.losi,
+        alarm_state.lofi,
+        alarm_state.loami);
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_dowi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_dowi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating DOWi <%d:%d> ONU %d -> %d (EQD %d)\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status,
+        ind->data.new_eqd);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.dowi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+
+    if (ind->data.alarm_status == BCMOLT_STATUS_ON)
+    {
+        BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, ranging_time, ind->data.new_eqd);
+    }
+
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sfi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_sfi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SFI <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sfi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sdi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_sdi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SDi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sdi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_dfi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_dfi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating DFi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.dfi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sufi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_sufi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SUFi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sufi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_loai(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_loai *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating LOAi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.loai = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_dgi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_dgi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating DGi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.dgi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_tiwi(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_tiwi *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating TIWi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.tiwi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_loki(const bcmolt_ps_pon *partner, const bcmolt_gpon_onu_loki *ind)
+{
+    bcmolt_gpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_gpon_onu_cfg cfg;
+    bcmolt_gpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating LOKi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.loki = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, gpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_omci_port_configuration_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_onu_omci_port_id_configuration_completed *ind)
+{
+    bcmolt_gpon_gem_port_key key = { partner->pon_id, ind->data.gem_port };
+    bcmolt_gpon_gem_port_set_state oper;
+
+    BCMOLT_OPER_INIT(&oper, gpon_gem_port, set_state, key);
+
+    if (ind->data.operation == BCMOLT_OMCI_PORT_ID_OPERATION_CONFIGURE)
+    {
+        /* don't react to unsuccessful activations */
+        if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+        {
+            return BCM_ERR_OK;
+        }
+
+        PS_INFO("%s: activating <%d:%d> GEM port ID %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.gem_port_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_gem_port, set_state, state, BCMOLT_GEM_PORT_OPERATION_ACTIVATE);
+    }
+    else
+    {
+        PS_INFO(
+            "%s: deactivating <%d:%d> GEM port ID %d\n",
+            __FUNCTION__,
+            partner->device_id,
+            key.pon_ni,
+            key.gem_port_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_gem_port, set_state, state, BCMOLT_GEM_PORT_OPERATION_DEACTIVATE);
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_mac_table_new_mac(
+    const bcmolt_ps_global_cfg *global_cfg,
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_iwf_mac_table_new_mac *ind)
+{
+    bcmolt_gpon_iwf_mac_table_key key = { partner->pon_id, ind->key.mac_address, ind->key.vlan };
+    bcmolt_gpon_iwf_mac_table_cfg cfg;
+
+    if (!global_cfg->mirror_mac_entries)
+    {
+        return BCM_ERR_OK; /* we are not configured to mirror MAC entries */
+    }
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_mac_table, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_mac_table, flow_id, ind->data.flow_id);
+
+    if (global_cfg->static_mac_entries)
+    {
+        BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_mac_table, stat, BCMOS_TRUE);
+    }
+    else
+    {
+        BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_mac_table, stat, BCMOS_FALSE);
+    }
+
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_mac_table_mac_aged(
+    const bcmolt_ps_global_cfg *global_cfg,
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_iwf_mac_table_mac_aged *ind)
+{
+    bcmolt_gpon_iwf_mac_table_key key = { partner->pon_id, ind->key.mac_address, ind->key.vlan };
+    bcmolt_gpon_iwf_mac_table_cfg cfg;
+
+    if (!global_cfg->mirror_mac_entries)
+    {
+        return BCM_ERR_OK; /* we are not configured to mirror MAC entries */
+    }
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_mac_table, key);
+    return bcmolt_cfg_clear(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_mac_table_mac_move(
+    const bcmolt_ps_global_cfg *global_cfg,
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_iwf_mac_table_mac_move *ind)
+{
+    bcmolt_gpon_iwf_mac_table_key key = { partner->pon_id, ind->key.mac_address, ind->key.vlan };
+    bcmolt_gpon_iwf_mac_table_cfg cfg;
+
+    if (!global_cfg->mirror_mac_entries)
+    {
+        return BCM_ERR_OK; /* we are not configured to mirror MAC entries */
+    }
+
+    BCMOLT_CFG_INIT(&cfg, gpon_iwf_mac_table, key);
+    BCMOLT_CFG_PROP_SET(&cfg, gpon_iwf_mac_table, flow_id, ind->data.new_flow_id);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_alloc_id_configuration_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_alloc_configuration_completed *ind)
+{
+    bcmolt_gpon_alloc_key key = { partner->pon_id, ind->key.alloc_id };
+    bcmolt_gpon_alloc_set_state oper;
+
+    BCMOLT_OPER_INIT(&oper, gpon_alloc, set_state, key);
+
+    if (ind->data.new_state == BCMOLT_ALLOC_STATE_ACTIVE)
+    {
+        /* don't react to unsuccessful activations */
+        if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+        {
+            return BCM_ERR_OK;
+        }
+
+        PS_INFO("%s: activating <%d:%d> alloc %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.alloc_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_alloc, set_state, state, BCMOLT_ALLOC_OPERATION_ACTIVATE);
+    }
+    else if (ind->data.new_state == BCMOLT_ALLOC_STATE_INACTIVE)
+    {
+        PS_INFO("%s: deactivating <%d:%d> alloc %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.alloc_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_alloc, set_state, state, BCMOLT_ALLOC_OPERATION_DEACTIVATE);
+    }
+    else /* ind->data.new_state is something else (e.g. unprovisioned, if the alloc was cleared) */
+    {
+        return BCM_ERR_OK;
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_gem_port_configuration_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_gpon_gem_port_configuration_completed *ind)
+{
+    bcmolt_gpon_gem_port_key key = { partner->pon_id, ind->key.gem_port_id };
+    bcmolt_gpon_gem_port_set_state oper;
+
+    BCMOLT_OPER_INIT(&oper, gpon_gem_port, set_state, key);
+
+    if (ind->data.new_state == BCMOLT_GPON_GEM_PORT_STATE_ACTIVE)
+    {
+        /* don't react to unsuccessful activations */
+        if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+        {
+            return BCM_ERR_OK;
+        }
+
+        PS_INFO("%s: activating <%d:%d> GEM port ID %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.gem_port_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_gem_port, set_state, state, BCMOLT_GEM_PORT_OPERATION_ACTIVATE);
+    }
+    else if (ind->data.new_state == BCMOLT_GPON_GEM_PORT_STATE_INACTIVE)
+    {
+        PS_INFO(
+            "%s: deactivating <%d:%d> GEM port ID %d\n",
+            __FUNCTION__,
+            partner->device_id,
+            key.pon_ni,
+            key.gem_port_id);
+        BCMOLT_OPER_PROP_SET(&oper, gpon_gem_port, set_state, state, BCMOLT_GEM_PORT_OPERATION_DEACTIVATE);
+    }
+    else /* ind->data.new_state is something else (e.g. unprovisioned, if the GEM port was cleared) */
+    {
+        return BCM_ERR_OK;
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+bcmos_errno ps_process_ind_gpon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind)
+{
+    bcmos_errno err;
+    bcmolt_ps_pon_state state;
+    bcmolt_ps_pon partner;
+    bcmolt_ps_global_cfg global_cfg;
+
+    err = bcmolt_ps_global_cfg_get(&global_cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    if (global_cfg.mirror_mode != BCMOLT_PS_MIRROR_MODE_AUTO &&
+        ind->hdr.subgroup != BCMOLT_GPON_NI_AUTO_ID_LOS)
+    {
+        /* only handle most indications while in mirror mode 'auto', but perform a switchover on LoS in any case  */
+        return BCM_ERR_OK;
+    }
+
+    err = bcmolt_ps_pon_state_get(pon, &state, &partner);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    if (state != BCMOLT_PS_PON_STATE_WORKING)
+    {
+        return BCM_ERR_OK; /* ignore indications from PONs that aren't protected working */
+    }
+
+    switch (ind->hdr.obj_type)
+    {
+    case BCMOLT_OBJ_ID_GPON_NI:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_GPON_NI_AUTO_ID_LOS:
+            return ps_handle_ni_los(&global_cfg, pon, ((const bcmolt_gpon_ni_los *)ind));
+        case BCMOLT_GPON_NI_AUTO_ID_STATE_CHANGE_COMPLETED:
+            return ps_handle_ni_state_changed(&partner, ((const bcmolt_gpon_ni_state_change_completed *)ind));
+        case BCMOLT_GPON_NI_AUTO_ID_PROTECTION_SWITCHING_TRAFFIC_RESUME:
+            return ps_handle_ni_traffic_resume(pon, ((const bcmolt_gpon_ni_protection_switching_traffic_resume *)ind));
+        case BCMOLT_GPON_NI_AUTO_ID_PROTECTION_SWITCHING_ONUS_RANGED:
+            return ps_handle_ni_onus_ranged(&partner, ((const bcmolt_gpon_ni_protection_switching_onus_ranged *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other NI indications */
+        }
+
+    case BCMOLT_OBJ_ID_GPON_ONU:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_GPON_ONU_AUTO_ID_RANGING_COMPLETED:
+            return ps_handle_onu_ranging_completed(&partner, ((const bcmolt_gpon_onu_ranging_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_KEY_EXCHANGE_COMPLETED:
+            return ps_handle_onu_key_exchange_completed(
+                &partner,
+                ((const bcmolt_gpon_onu_key_exchange_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_PASSWORD_AUTHENTICATION_COMPLETED:
+            return ps_handle_onu_password_authentication_completed(
+                &partner,
+                ((const bcmolt_gpon_onu_password_authentication_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_ONU_ACTIVATION_COMPLETED:
+            return ps_handle_onu_activation_completed(
+                &partner,
+                ((const bcmolt_gpon_onu_onu_activation_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_ONU_DEACTIVATION_COMPLETED:
+            return ps_handle_onu_deactivation_completed(
+                &partner,
+                ((const bcmolt_gpon_onu_onu_deactivation_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_ONU_ENABLE_COMPLETED:
+            return ps_handle_onu_enable_completed(&partner, ((const bcmolt_gpon_onu_onu_enable_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_ONU_DISABLE_COMPLETED:
+            return ps_handle_onu_disable_completed(&partner, ((const bcmolt_gpon_onu_onu_disable_completed *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_ONU_ALARM:
+            return ps_handle_onu_alarm(&partner, ((const bcmolt_gpon_onu_onu_alarm *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_DOWI:
+            return ps_handle_onu_dowi(&partner, ((const bcmolt_gpon_onu_dowi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_SFI:
+            return ps_handle_onu_sfi(&partner, ((const bcmolt_gpon_onu_sfi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_SDI:
+            return ps_handle_onu_sdi(&partner, ((const bcmolt_gpon_onu_sdi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_DFI:
+            return ps_handle_onu_dfi(&partner, ((const bcmolt_gpon_onu_dfi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_SUFI:
+            return ps_handle_onu_sufi(&partner, ((const bcmolt_gpon_onu_sufi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_LOAI:
+            return ps_handle_onu_loai(&partner, ((const bcmolt_gpon_onu_loai *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_DGI:
+            return ps_handle_onu_dgi(&partner, ((const bcmolt_gpon_onu_dgi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_TIWI:
+            return ps_handle_onu_tiwi(&partner, ((const bcmolt_gpon_onu_tiwi *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_LOKI:
+            return ps_handle_onu_loki(&partner, ((const bcmolt_gpon_onu_loki *)ind));
+        case BCMOLT_GPON_ONU_AUTO_ID_OMCI_PORT_ID_CONFIGURATION_COMPLETED:
+            return ps_handle_onu_omci_port_configuration_completed(
+                &partner,
+                ((const bcmolt_gpon_onu_omci_port_id_configuration_completed *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other ONU indications */
+        }
+
+    case BCMOLT_OBJ_ID_GPON_ALLOC:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_GPON_ALLOC_AUTO_ID_CONFIGURATION_COMPLETED:
+            return ps_handle_alloc_id_configuration_completed(
+                &partner,
+                ((const bcmolt_gpon_alloc_configuration_completed *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other alloc indications */
+        }
+
+    case BCMOLT_OBJ_ID_GPON_GEM_PORT:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_GPON_GEM_PORT_AUTO_ID_CONFIGURATION_COMPLETED:
+            return ps_handle_gem_port_configuration_completed(
+                &partner,
+                ((const bcmolt_gpon_gem_port_configuration_completed *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other GEM port indications */
+        }
+
+    case BCMOLT_OBJ_ID_GPON_IWF_MAC_TABLE:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_GPON_IWF_MAC_TABLE_AUTO_CFG_ID_NEW_MAC:
+            return ps_handle_mac_table_new_mac(
+                &global_cfg,
+                &partner,
+                (const bcmolt_gpon_iwf_mac_table_new_mac *)ind);
+        case BCMOLT_GPON_IWF_MAC_TABLE_AUTO_CFG_ID_MAC_AGED:
+            return ps_handle_mac_table_mac_aged(
+                &global_cfg,
+                &partner,
+                (const bcmolt_gpon_iwf_mac_table_mac_aged *)ind);
+        case BCMOLT_GPON_IWF_MAC_TABLE_AUTO_CFG_ID_MAC_MOVE:
+            return ps_handle_mac_table_mac_move(
+                &global_cfg, 
+                &partner,
+                (const bcmolt_gpon_iwf_mac_table_mac_move *)ind);
+        default:
+            return BCM_ERR_OK; /* silently ignore all other mac table indications */
+        }
+
+    default:
+        return BCM_ERR_NOT_SUPPORTED; /* someone else needs to handle this indication */
+    }
+}
+
+bcmos_errno ps_move_to_standby_gpon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_gpon_ni_set_pon_state set_pon_state;
+    bcmolt_gpon_ni_key key = { (bcmolt_gpon_ni)pon->pon_id };
+
+    BCMOLT_OPER_INIT(&set_pon_state, gpon_ni, set_pon_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_STANDBY);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
+
+bcmos_errno ps_move_to_working_gpon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_gpon_ni_set_pon_state set_pon_state;
+    bcmolt_gpon_ni_key key = { (bcmolt_gpon_ni)pon->pon_id };
+
+    BCMOLT_OPER_INIT(&set_pon_state, gpon_ni, set_pon_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, gpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_WORKING);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_internal.h b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_internal.h
new file mode 100644
index 0000000..7c3f7d1
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_internal.h
@@ -0,0 +1,66 @@
+/*
+<: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 is for internal use within the protection switching user application -
+   no functions here should be called from anywhere else! */
+
+#ifndef _BCMOLT_USER_APPL_PS_INTERNAL_H_
+#define _BCMOLT_USER_APPL_PS_INTERNAL_H_
+
+#include <bcm_dev_log.h>
+#include "bcmolt_user_appl_ps.h"
+
+#ifdef ENABLE_LOG
+dev_log_id ps_get_log_id(void);
+#define PS_ERR(...) BCM_LOG(ERROR, ps_get_log_id(), __VA_ARGS__)
+#define PS_INFO(...) BCM_LOG(INFO, ps_get_log_id(), __VA_ARGS__)
+#else
+#define PS_ERR(...) bcmos_printf("[PS ERROR] " __VA_ARGS__)
+#define PS_INFO(...) bcmos_printf("[PS INFO] " __VA_ARGS__)
+#endif
+
+/* Helper functions */
+uint32_t ps_get_last_switch_start_time_us(void);
+
+/* GPON-specific functionality */
+bcmos_errno ps_process_ind_gpon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind);
+bcmos_errno ps_move_to_standby_gpon(const bcmolt_ps_pon *pon);
+bcmos_errno ps_move_to_working_gpon(const bcmolt_ps_pon *pon);
+
+/* EPON-specific functionality */
+bcmos_errno ps_process_ind_epon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind);
+bcmos_errno ps_move_to_standby_epon(const bcmolt_ps_pon *pon);
+bcmos_errno ps_move_to_working_epon(const bcmolt_ps_pon *pon);
+
+/* XGPON-specific functionality */
+bcmos_errno ps_process_ind_xgpon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind);
+bcmos_errno ps_move_to_standby_xgpon(const bcmolt_ps_pon *pon);
+bcmos_errno ps_move_to_working_xgpon(const bcmolt_ps_pon *pon);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_xgpon.c b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_xgpon.c
new file mode 100644
index 0000000..faa167a
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/protection_switching/bcmolt_user_appl_ps_xgpon.c
@@ -0,0 +1,655 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmolt_host_api.h>
+#include "bcmolt_user_appl_ps.h"
+#include "bcmolt_user_appl_ps_internal.h"
+
+static bcmos_errno ps_handle_ni_los(
+    const bcmolt_ps_global_cfg *global_cfg,
+    const bcmolt_ps_pon *pon,
+    const bcmolt_xgpon_ni_los *ind)
+{
+    if (ind->data.status == BCMOLT_STATUS_OFF)
+    {
+        PS_INFO("<%d:%d> LoS alarm cleared\n", pon->device_id, pon->pon_id);
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("<%d:%d> LoS alarm raised\n", pon->device_id, pon->pon_id);
+
+    if (global_cfg->switch_condition == BCMOLT_PS_SWITCH_CONDITION_MANUAL)
+    {
+        /* don't react to the switch if we're in manual switch mode */
+        return BCM_ERR_OK;
+    }
+    else
+    {
+        return bcmolt_ps_switch_perform(pon);
+    }
+}
+
+static bcmos_errno ps_handle_ni_state_changed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_ni_state_change_completed *ind)
+{
+    bcmolt_xgpon_ni_key key = { partner->pon_id };
+    bcmolt_xgpon_ni_set_pon_state oper;
+
+    BCMOLT_OPER_INIT(&oper, xgpon_ni, set_pon_state, key);
+
+    if (ind->data.previous_state == BCMOLT_PON_STATE_INACTIVE &&
+        ind->data.new_state == BCMOLT_PON_STATE_ACTIVE_WORKING)
+    {
+        PS_INFO("%s: activating PON <%d:%d>\n", __FUNCTION__, partner->device_id, key.pon_ni);
+        BCMOLT_OPER_PROP_SET(&oper, xgpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_STANDBY);
+    }
+    else if (ind->data.previous_state == BCMOLT_PON_STATE_ACTIVE_WORKING &&
+        ind->data.new_state == BCMOLT_PON_STATE_INACTIVE)
+    {
+        PS_INFO("%s: deactivating PON <%d:%d>\n", __FUNCTION__, partner->device_id, key.pon_ni);
+        BCMOLT_OPER_PROP_SET(&oper, xgpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_INACTIVE);
+    }
+    else
+    {
+        /* only change the standby PON's state if we're pre-provisioning the working PON
+          (the switchover is handled separately) */
+        return BCM_ERR_OK;
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_ni_traffic_resume(
+    const bcmolt_ps_pon *pon,
+    const bcmolt_xgpon_ni_protection_switching_traffic_resume *ind)
+{
+    uint32_t after_us  = bcmos_timestamp();
+    uint32_t before_us = ps_get_last_switch_start_time_us();
+    uint32_t time_taken_us = (after_us > before_us) ? after_us - before_us : before_us - after_us;
+    const char *traffic_resume_result_str = NULL;
+
+    switch (ind->data.result)
+    {
+    case BCMOLT_TRAFFIC_RESUME_RESULT_SUCCESS:
+        traffic_resume_result_str = "success";
+        break;
+    case BCMOLT_TRAFFIC_RESUME_RESULT_FAILURE:
+        traffic_resume_result_str = "failure";
+        break;
+    case BCMOLT_TRAFFIC_RESUME_RESULT_SUSPECTED_LOS:
+        traffic_resume_result_str = "suspected_los";
+        break;
+    default:
+        break;
+    }
+
+    PS_INFO(
+        "<%d:%d> Traffic resume %s. Time since start of switch: %d ms.\n",
+        pon->device_id,
+        pon->pon_id,
+        traffic_resume_result_str,
+        time_taken_us / 1000);
+
+    return BCM_ERR_OK;
+}
+
+static bcmos_errno ps_handle_ni_onus_ranged(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_ni_protection_switching_onus_ranged *ind)
+{
+    uint32_t i;
+    bcmos_errno last_err = BCM_ERR_OK;
+
+    /* apply all of the new ONU EQDs to the standby PON */
+    for (i = 0; i < ind->data.onus.len; i++)
+    {
+        bcmolt_xgpon_onu_key key = { partner->pon_id, ind->data.onus.val[i].onu_id };
+        bcmolt_xgpon_onu_cfg cfg;
+        bcmos_errno err;
+
+        PS_INFO(
+            "%s: updating EQD <%d:%d> ONU %d -> %d\n",
+            __FUNCTION__,
+            partner->device_id,
+            key.pon_ni,
+            key.onu_id,
+            ind->data.onus.val[i].eqd);
+
+        BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+        BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, ranging_time, ind->data.onus.val[i].eqd);
+        err = bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+        if (err != BCM_ERR_OK)
+        {
+            PS_ERR("EQD update failed: %s\n", bcmos_strerror(err));
+            last_err = err;
+        }
+    }
+
+    return last_err;
+}
+
+static bcmos_errno ps_handle_onu_ranging_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_ranging_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+
+    /* only react to success indications */
+    if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO(
+        "%s: updating EQD <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.eqd);
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, ranging_time, ind->data.eqd);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_key_exchange_completed(
+    const bcmolt_ps_pon *pon,
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_key_exchange_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+
+    PS_INFO("%s: updating AES key <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, current_encryption_key, ind->data.new_key);
+    return bcmolt_cfg_set(pon->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_activation_completed(
+    const bcmolt_ps_pon *pon,
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_onu_activation_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_set_onu_state oper;
+    bcmos_errno err;
+
+    /* only react to success indications */
+    if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO(
+        "%s: provisioning registration ID / keys for newly-activated ONU <%d:%d> ONU %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id);
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, registration_id, ind->data.registration_id);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, registration_encryption_keys, ind->data.registration_encryption_keys);
+    err = bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    PS_INFO("%s: activating <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, xgpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, xgpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ACTIVE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_deactivation_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_onu_deactivation_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_set_onu_state oper;
+
+    /* Note: we react to this indication whether or not the deactivation was successful.  Failure indicates that the
+     * ONU didn't respond to the deactivation, but we still remove the ONU from OLT hardware so it must be mirrored. */
+
+    PS_INFO("%s: deactivating <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, xgpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, xgpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_INACTIVE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_enable_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_onu_enable_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_set_onu_state oper;
+
+    /* don't try to mirror broadcast enable/disable */
+    if (key.onu_id == BCMOLT_XGPON_ONU_ID_INVALID)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: enabling <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, xgpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, xgpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_ENABLE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static bcmos_errno ps_handle_onu_disable_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_onu_onu_disable_completed *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_set_onu_state oper;
+
+    /* don't try to mirror broadcast enable/disable */
+    if (key.onu_id == BCMOLT_XGPON_ONU_ID_INVALID)
+    {
+        return BCM_ERR_OK;
+    }
+
+    PS_INFO("%s: disabling <%d:%d> ONU %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.onu_id);
+
+    BCMOLT_OPER_INIT(&oper, xgpon_onu, set_onu_state, key);
+    BCMOLT_OPER_PROP_SET(&oper, xgpon_onu, set_onu_state, onu_state, BCMOLT_ONU_OPERATION_DISABLE);
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+static void ps_alarm_state_init(bcmolt_xgpon_onu_alarm_state *alarm_state)
+{
+    alarm_state->losi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->lobi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->lopci = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->lopci_mic_error = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->looci = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->tiwi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dowi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sufi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sfi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->sdi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dfi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->dgi = BCMOLT_STATUS_NO_CHANGE;
+    alarm_state->pqsi = BCMOLT_STATUS_NO_CHANGE;
+}
+
+static bcmos_errno ps_handle_onu_alarm(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_onu_alarm *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.losi = ind->data.onu_alarm.losi;
+    alarm_state.lobi = ind->data.onu_alarm.lobi;
+    alarm_state.lopci = ind->data.onu_alarm.lopci_miss;
+    alarm_state.lopci_mic_error = ind->data.onu_alarm.lopci_mic_error;
+
+    PS_INFO(
+        "%s: updating ONU alarms <%d:%d> ONU %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id);
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_dowi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_dowi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating DOWi <%d:%d> ONU %d -> %d (EQD %d)\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status,
+        ind->data.new_eqd);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.dowi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+
+    if (ind->data.alarm_status == BCMOLT_STATUS_ON)
+    {
+        BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, ranging_time, ind->data.new_eqd);
+    }
+
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sfi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_sfi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SFI <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sfi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sdi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_sdi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SDi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sdi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_dfi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_dfi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating DFi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.dfi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_sufi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_sufi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating SUFi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.sufi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_tiwi(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_tiwi *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating TIWi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.tiwi = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_onu_looci(const bcmolt_ps_pon *partner, const bcmolt_xgpon_onu_looci *ind)
+{
+    bcmolt_xgpon_onu_key key = { partner->pon_id, ind->key.onu_id };
+    bcmolt_xgpon_onu_cfg cfg;
+    bcmolt_xgpon_onu_alarm_state alarm_state;
+
+    PS_INFO(
+        "%s: updating LOOCi <%d:%d> ONU %d -> %d\n",
+        __FUNCTION__,
+        partner->device_id,
+        key.pon_ni,
+        key.onu_id,
+        ind->data.alarm_status);
+
+    ps_alarm_state_init(&alarm_state);
+    alarm_state.looci = ind->data.alarm_status;
+
+    BCMOLT_CFG_INIT(&cfg, xgpon_onu, key);
+    BCMOLT_CFG_PROP_SET(&cfg, xgpon_onu, alarm_state, alarm_state);
+    return bcmolt_cfg_set(partner->device_id, &cfg.hdr);
+}
+
+static bcmos_errno ps_handle_alloc_id_configuration_completed(
+    const bcmolt_ps_pon *partner,
+    const bcmolt_xgpon_alloc_configuration_completed *ind)
+{
+    bcmolt_xgpon_alloc_key key = { partner->pon_id, ind->key.alloc_id };
+    bcmolt_xgpon_alloc_set_state oper;
+
+    BCMOLT_OPER_INIT(&oper, xgpon_alloc, set_state, key);
+
+    if (ind->data.new_state == BCMOLT_ALLOC_STATE_ACTIVE)
+    {
+        /* only react to success indications */
+        if (ind->data.status != BCMOLT_RESULT_SUCCESS)
+        {
+            return BCM_ERR_OK;
+        }
+
+        PS_INFO("%s: activating <%d:%d> alloc %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.alloc_id);
+        BCMOLT_OPER_PROP_SET(&oper, xgpon_alloc, set_state, state, BCMOLT_ALLOC_OPERATION_ACTIVATE);
+    }
+    else if (ind->data.new_state == BCMOLT_ALLOC_STATE_INACTIVE)
+    {
+        PS_INFO("%s: deactivating <%d:%d> alloc %d\n", __FUNCTION__, partner->device_id, key.pon_ni, key.alloc_id);
+        BCMOLT_OPER_PROP_SET(&oper, xgpon_alloc, set_state, state, BCMOLT_ALLOC_OPERATION_DEACTIVATE);
+    }
+    else /* ind->data.new_state is something else (e.g. unprovisioned, if the alloc was cleared) */
+    {
+        return BCM_ERR_OK;
+    }
+
+    return bcmolt_oper_submit(partner->device_id, &oper.hdr);
+}
+
+bcmos_errno ps_process_ind_xgpon(const bcmolt_ps_pon *pon, const bcmolt_auto *ind)
+{
+    bcmos_errno err;
+    bcmolt_ps_pon_state state;
+    bcmolt_ps_pon partner;
+    bcmolt_ps_global_cfg global_cfg;
+
+    err = bcmolt_ps_global_cfg_get(&global_cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    if (global_cfg.mirror_mode != BCMOLT_PS_MIRROR_MODE_AUTO &&
+        ind->hdr.subgroup != BCMOLT_XGPON_NI_AUTO_ID_LOS)
+    {
+        /* only handle most indications while in mirror mode 'auto', but perform a switchover on LoS in any case  */
+        return BCM_ERR_OK;
+    }
+
+    err = bcmolt_ps_pon_state_get(pon, &state, &partner);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    if (state != BCMOLT_PS_PON_STATE_WORKING)
+    {
+        return BCM_ERR_OK; /* ignore indications from PONs that aren't protected working */
+    }
+
+    switch (ind->hdr.obj_type)
+    {
+    case BCMOLT_OBJ_ID_XGPON_NI:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_XGPON_NI_AUTO_ID_LOS:
+            return ps_handle_ni_los(&global_cfg, pon, ((const bcmolt_xgpon_ni_los *)ind));
+        case BCMOLT_XGPON_NI_AUTO_ID_STATE_CHANGE_COMPLETED:
+            return ps_handle_ni_state_changed(&partner, ((const bcmolt_xgpon_ni_state_change_completed *)ind));
+        case BCMOLT_XGPON_NI_AUTO_ID_PROTECTION_SWITCHING_TRAFFIC_RESUME:
+            return ps_handle_ni_traffic_resume(pon, ((const bcmolt_xgpon_ni_protection_switching_traffic_resume *)ind));
+        case BCMOLT_XGPON_NI_AUTO_ID_PROTECTION_SWITCHING_ONUS_RANGED:
+            return ps_handle_ni_onus_ranged(&partner, ((const bcmolt_xgpon_ni_protection_switching_onus_ranged *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other NI indications */
+        }
+
+    case BCMOLT_OBJ_ID_XGPON_ONU:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_XGPON_ONU_AUTO_ID_RANGING_COMPLETED:
+            return ps_handle_onu_ranging_completed(&partner, ((const bcmolt_xgpon_onu_ranging_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_KEY_EXCHANGE_COMPLETED:
+            return ps_handle_onu_key_exchange_completed(
+                pon,
+                &partner,
+                ((const bcmolt_xgpon_onu_key_exchange_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_ACTIVATION_COMPLETED:
+            return ps_handle_onu_activation_completed(
+                pon,
+                &partner,
+                ((const bcmolt_xgpon_onu_onu_activation_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_DEACTIVATION_COMPLETED:
+            return ps_handle_onu_deactivation_completed(
+                &partner,
+                ((const bcmolt_xgpon_onu_onu_deactivation_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_ENABLE_COMPLETED:
+            return ps_handle_onu_enable_completed(&partner, ((const bcmolt_xgpon_onu_onu_enable_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_DISABLE_COMPLETED:
+            return ps_handle_onu_disable_completed(&partner, ((const bcmolt_xgpon_onu_onu_disable_completed *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_ONU_ALARM:
+            return ps_handle_onu_alarm(&partner, ((const bcmolt_xgpon_onu_onu_alarm *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_DOWI:
+            return ps_handle_onu_dowi(&partner, ((const bcmolt_xgpon_onu_dowi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_SFI:
+            return ps_handle_onu_sfi(&partner, ((const bcmolt_xgpon_onu_sfi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_SDI:
+            return ps_handle_onu_sdi(&partner, ((const bcmolt_xgpon_onu_sdi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_DFI:
+            return ps_handle_onu_dfi(&partner, ((const bcmolt_xgpon_onu_dfi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_SUFI:
+            return ps_handle_onu_sufi(&partner, ((const bcmolt_xgpon_onu_sufi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_TIWI:
+            return ps_handle_onu_tiwi(&partner, ((const bcmolt_xgpon_onu_tiwi *)ind));
+        case BCMOLT_XGPON_ONU_AUTO_ID_LOOCI:
+            return ps_handle_onu_looci(&partner, ((const bcmolt_xgpon_onu_looci *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other ONU indications */
+        }
+
+    case BCMOLT_OBJ_ID_XGPON_ALLOC:
+        switch (ind->hdr.subgroup)
+        {
+        case BCMOLT_XGPON_ALLOC_AUTO_ID_CONFIGURATION_COMPLETED:
+            return ps_handle_alloc_id_configuration_completed(
+                &partner,
+                ((const bcmolt_xgpon_alloc_configuration_completed *)ind));
+        default:
+            return BCM_ERR_OK; /* silently ignore all other alloc indications */
+        }
+
+    default:
+        return BCM_ERR_NOT_SUPPORTED; /* someone else needs to handle this indication */
+    }
+}
+
+bcmos_errno ps_move_to_standby_xgpon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_xgpon_ni_set_pon_state set_pon_state;
+    bcmolt_xgpon_ni_key key = { (bcmolt_xgpon_ni)pon->pon_id };
+
+    BCMOLT_OPER_INIT(&set_pon_state, xgpon_ni, set_pon_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, xgpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_STANDBY);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
+
+bcmos_errno ps_move_to_working_xgpon(const bcmolt_ps_pon *pon)
+{
+    bcmolt_xgpon_ni_set_pon_state set_pon_state;
+    bcmolt_xgpon_ni_key key = { (bcmolt_xgpon_ni)pon->pon_id };
+
+    BCMOLT_OPER_INIT(&set_pon_state, xgpon_ni, set_pon_state, key);
+    BCMOLT_OPER_PROP_SET(&set_pon_state, xgpon_ni, set_pon_state, pon_state, BCMOLT_PON_OPERATION_ACTIVE_WORKING);
+    return bcmolt_oper_submit(pon->device_id, &set_pon_state.hdr);
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/remote_logger/Makefile b/bcm68620_release/release/host_reference/user_appl/remote_logger/Makefile
new file mode 100644
index 0000000..61e7df6
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/remote_logger/Makefile
@@ -0,0 +1,15 @@
+# User application to copy log prints from the device to a local file on the host
+
+ifeq ("$(ENABLE_LOG)", "y")
+    ifeq ("$(ENABLE_CLI)", "y")
+	MOD_NAME = bcm_user_appl_remote_logger
+	MOD_TYPE = lib
+	MOD_DEPS = host_api dev_log
+	ifeq ("$(OS_KERNEL)", "linux")
+	    MOD_DEPS += dev_log_linux
+	endif
+	srcs = bcmolt_user_appl_remote_logger.c \
+	       bcmolt_user_appl_remote_logger_cli.c
+    endif
+endif
+
diff --git a/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.c b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.c
new file mode 100644
index 0000000..8cbeddf
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.c
@@ -0,0 +1,232 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcm_dev_log.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include "bcmolt_user_appl_remote_logger.h"
+
+static struct
+{
+    FILE *file_handle;
+    uint64_t file_size;
+    bcmolt_remote_logger_cfg cfg;
+    bcmos_task task;
+    bcmos_timer timer;
+    dev_log_id log_id;
+} remote_logger[BCMTR_MAX_OLTS] = {};
+
+static bcmos_timer_rc remote_logger_timer_handler(bcmos_timer *timer, long data)
+{
+    bcmolt_devid device = (bcmolt_devid)data;
+    int written;
+    bcmos_errno err;
+    bcmolt_logger_cfg cfg;
+    bcmolt_logger_key key = { .file_id = BCMOLT_LOG_FILE_ID_DDR };
+
+    BCMOLT_CFG_INIT(&cfg, logger, key);
+    BCMOLT_CFG_PROP_GET(&cfg, logger, buffer);
+    err = bcmolt_cfg_get(device, &cfg.hdr);
+    if (err != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "Logger get API returned '%s' (%d)\n", bcmos_strerror(err), err);
+        bcmolt_remote_logger_appl_stop(device);
+        return BCMOS_TIMER_OK;
+    }
+
+    written = fprintf(
+        remote_logger[device].file_handle,
+        "%.*s",
+        (int)sizeof(cfg.data.buffer.buff),
+        cfg.data.buffer.buff);
+    if (written < 0)
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "File write returned %d\n", written);
+        bcmolt_remote_logger_appl_stop(device);
+        return BCMOS_TIMER_OK;
+    }
+
+    fflush(remote_logger[device].file_handle);
+
+    /* Check to see if we will exceed the max file size next time we write a chunk. */
+    remote_logger[device].file_size += written;
+    if (remote_logger[device].file_size + sizeof(cfg.data.buffer.buff) > remote_logger[device].cfg.max_file_size)
+    {
+        switch (remote_logger[device].cfg.max_file_size_reached_behavior)
+        {
+            case BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_STOP:
+                BCM_LOG(INFO, remote_logger[device].log_id, "Max file size reached - remote logger stopped\n");
+                bcmolt_remote_logger_appl_stop(device);
+                return BCMOS_TIMER_OK;
+            case BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_CLEAR:
+                BCM_LOG(INFO, remote_logger[device].log_id, "Max file size reached - log file cleared\n");
+                /* the easiest way to clear the file is to close/re-open it */
+                fclose(remote_logger[device].file_handle);
+                remote_logger[device].file_handle = fopen(remote_logger[device].cfg.filename, "w");
+                BUG_ON(remote_logger[device].file_handle == NULL);
+                break;
+            default:
+                break;
+        }
+    }
+
+    if (cfg.data.buffer.msg_to_read == 0)
+    {
+        /* There are no more messages to read right now, wait for the entire polling interval. */
+        bcmos_timer_start(
+            &remote_logger[device].timer,
+            remote_logger[device].cfg.polling_period_ms * 1000); /* ms to us */
+    }
+    else
+    {
+        /* There are more messages to read - wait for only the 'subsequent delay' time. */
+        bcmos_timer_start(
+            &remote_logger[device].timer,
+            remote_logger[device].cfg.subsequent_delay_ms * 1000); /* ms to us */
+    }
+
+    return BCMOS_TIMER_OK;
+}
+
+void bcmolt_remote_logger_appl_init()
+{
+    static bcmos_task_parm task_params =
+    {
+        .priority = TASK_PRIORITY_USER_APPL_REMOTE_LOGGER,
+        .core = BCMOS_CPU_CORE_ANY, /* No CPU affinity */
+        .init_handler = NULL
+    };
+    static bcmos_module_parm module_params =
+    {
+        .qparm = { .size = 1 } /* just the timer message */
+    };
+    static bcmos_timer_parm timer_params =
+    {
+        .periodic = BCMOS_FALSE, /* we will re-enable the timer after each tick */
+        .handler = remote_logger_timer_handler
+    };
+
+    bcmos_errno err;
+    bcmolt_devid device;
+
+    for (device = 0; device < BCMTR_MAX_OLTS; device++)
+    {
+        bcmos_module_id module_id = BCMOS_MODULE_ID_USER_APPL_REMOTE_LOGGER_DEV0 + device;
+        snprintf(
+            remote_logger[device].task.name,
+            sizeof(remote_logger[device].task.name),
+            "user_appl_remote_logger%u",
+            device);
+
+        task_params.name = remote_logger[device].task.name;
+        module_params.qparm.name = remote_logger[device].task.name;
+        timer_params.owner = module_id;
+        timer_params.name = remote_logger[device].task.name;
+        timer_params.data = (long)device;
+
+        err = bcmos_task_create(&remote_logger[device].task, &task_params);
+        BUG_ON(err != BCM_ERR_OK);
+
+        err = bcmos_module_create(module_id, &remote_logger[device].task, &module_params);
+        BUG_ON(err != BCM_ERR_OK);
+
+        err = bcmos_timer_create(&remote_logger[device].timer, &timer_params);
+        BUG_ON(err != BCM_ERR_OK);
+
+        remote_logger[device].log_id =
+            bcm_dev_log_id_register(remote_logger[device].task.name, DEV_LOG_LEVEL_WARNING, DEV_LOG_ID_TYPE_BOTH);
+    }
+}
+
+bcmos_errno bcmolt_remote_logger_appl_start(bcmolt_devid device)
+{
+    bcmos_errno err;
+    bcmolt_logger_cfg cfg;
+    bcmolt_logger_key key = { .file_id = BCMOLT_LOG_FILE_ID_DDR };
+
+    if (bcmolt_remote_logger_appl_is_running(device))
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "Remote logger application is already running.\n");
+        return BCM_ERR_ALREADY;
+    }
+
+    /* configure DDR log file to clear on read so we can always stay up-to-date */
+    BCMOLT_CFG_INIT(&cfg, logger, key);
+    BCMOLT_CFG_PROP_SET(&cfg, logger, enable_log, BCMOS_TRUE);
+    BCMOLT_CFG_PROP_SET(&cfg, logger, clear_after_read, BCMOS_TRUE);
+    err = bcmolt_cfg_set(0, &cfg.hdr);
+    if (err != BCM_ERR_OK)
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "Logger set API returned '%s' (%d)\n", bcmos_strerror(err), err);
+        return err;
+    }
+
+    remote_logger[device].file_handle = fopen(remote_logger[device].cfg.filename, "w");
+    if (remote_logger[device].file_handle == NULL)
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "Could not open file '%s'\n", remote_logger[device].cfg.filename);
+        return BCM_ERR_IO;
+    }
+    remote_logger[device].file_size = 0;
+
+    bcmos_timer_start(&remote_logger[device].timer, remote_logger[device].cfg.polling_period_ms * 1000); /* ms to us */
+    return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_remote_logger_appl_stop(bcmolt_devid device)
+{
+    if (!bcmolt_remote_logger_appl_is_running(device))
+    {
+        BCM_LOG(ERROR, remote_logger[device].log_id, "Remote logger application is not running.\n");
+        return BCM_ERR_ALREADY;
+    }
+
+    bcmos_timer_stop(&remote_logger[device].timer);
+    fclose(remote_logger[device].file_handle);
+    remote_logger[device].file_handle = NULL;
+    return BCM_ERR_OK;
+}
+
+bcmos_bool bcmolt_remote_logger_appl_is_running(bcmolt_devid device)
+{
+    return bcmos_timer_is_running(&remote_logger[device].timer);
+}
+
+bcmos_errno bcmolt_remote_logger_appl_cfg_get(bcmolt_devid device, bcmolt_remote_logger_cfg *cfg)
+{
+    *cfg = remote_logger[device].cfg;
+    return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_remote_logger_appl_cfg_update(bcmolt_devid device, const bcmolt_remote_logger_cfg *cfg)
+{
+    remote_logger[device].cfg = *cfg;
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.h b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.h
new file mode 100644
index 0000000..75a22fd
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger.h
@@ -0,0 +1,89 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_REMOTE_LOGGER_H_
+#define _BCMOLT_USER_APPL_REMOTE_LOGGER_H_
+
+#include <bcmos_system.h>
+#include <bcmolt_msg.h>
+
+#define BCMOLT_REMOTE_LOGGER_FILENAME_MAX_LEN 128
+
+/** Possible actions to take when the max log file size is reached. */
+typedef enum
+{
+    BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_STOP,  /**< Stop the remote logger application. */
+    BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_CLEAR, /**< Clear all content from the file and continue. */
+} bcmolt_remote_logger_file_size_reached_behavior;
+
+/** Configuration for the remote logger application. */
+typedef struct
+{
+    uint32_t polling_period_ms;   /**< Time to wait in between polling API calls. */
+    uint32_t subsequent_delay_ms; /**< Minumum required time to wait in between susequent API calls. */
+    uint32_t max_file_size;       /**< Maximum size for the output file in bytes. */
+
+    /** Output file full path. */
+    char filename[BCMOLT_REMOTE_LOGGER_FILENAME_MAX_LEN];
+
+    /** Behavior when the max file size is reached. */
+    bcmolt_remote_logger_file_size_reached_behavior max_file_size_reached_behavior;
+} bcmolt_remote_logger_cfg;
+
+/** Initialize the remote logger application (this should be called as part of application startup). */
+void bcmolt_remote_logger_appl_init(void);
+
+/** Start the remote logger application.
+ * \return        BCM_ERR_OK if the application was started successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_remote_logger_appl_start(bcmolt_devid device);
+
+/** Stop the remote logger application.
+ * \return        BCM_ERR_OK if the application was stopped successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_remote_logger_appl_stop(bcmolt_devid device);
+
+/** Query whether the remote logger application is currently running.
+ * \return        BCMOS_TRUE if the application is running, BCMOS_FALSE otherwise.
+ */
+bcmos_bool bcmolt_remote_logger_appl_is_running(bcmolt_devid device);
+
+/** Get the configuration of the remote logger application.
+ * \param[out] cfg Remote logger configuration.
+ * \return         BCM_ERR_OK if the configuration was retrieved successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_remote_logger_appl_cfg_get(bcmolt_devid device, bcmolt_remote_logger_cfg *cfg);
+
+/** Change the configuration of the remote logger application.
+ * \param[in] cfg Remote logger configuration.
+ * \return        BCM_ERR_OK if the configuration was updated successfully, <0 otherwise.
+ */
+bcmos_errno bcmolt_remote_logger_appl_cfg_update(bcmolt_devid device, const bcmolt_remote_logger_cfg *cfg);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.c b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.c
new file mode 100644
index 0000000..86c4fd5
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.c
@@ -0,0 +1,257 @@
+/*
+<: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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+#include <bcmolt_api.h>
+#include <bcmolt_model_types.h>
+#include "bcmolt_user_appl_remote_logger_cli.h"
+#include "bcmolt_user_appl_remote_logger.h"
+#include "bcmolt_conv.h"
+
+#define BCMOLT_REMOTE_LOGGER_DEFAULT_POLLING_PERIOD_MS     5000
+#define BCMOLT_REMOTE_LOGGER_DEFAULT_SUBSEQUENT_DELAY_MS   5
+#define BCMOLT_REMOTE_LOGGER_DEFAULT_MAX_FILE_SIZE         (200 * 1000000) 
+#define BCMOLT_REMOTE_LOGGER_DEFAULT_FILE_REACHED_BEHAVIOR "stop"
+#define BCMOLT_REMOTE_LOGGER_DEFAULT_FILENAME              "remote_log.txt"
+
+static bcmos_errno remote_logger_configure_log(
+    bcmolt_devid device,
+    const bcmolt_log_entry_key *key,
+    bcmolt_log_level log_level_save)
+{
+    bcmolt_log_entry_cfg cfg;
+    BCMOLT_CFG_INIT(&cfg, log_entry, *key);
+    BCMOLT_CFG_PROP_SET(&cfg, log_entry, log_level_save, log_level_save);
+    BCMOLT_CFG_PROP_SET(&cfg, log_entry, log_level_print, BCMOLT_LOG_LEVEL_WARNING);
+    return bcmolt_cfg_set(device, &cfg.hdr);
+}
+
+static bcmos_errno remote_logger_configure_logs(bcmolt_devid device)
+{
+    bcmos_errno err;
+    bcmolt_msg_set *msg_set;
+    bcmolt_log_entry_cfg multi_cfg;
+    bcmolt_log_entry_key multi_key = { .log_id = 0 }; /* start from the first log ID */
+    uint16_t i;
+
+    /* allocate space for multi-get return */
+    err = bcmolt_msg_set_alloc(BCMOLT_OBJ_ID_LOG_ENTRY, BCMOLT_MGT_GROUP_CFG, 50, &msg_set);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    /* initialize the multi-get config structure */
+    BCMOLT_CFG_INIT(&multi_cfg, log_entry, multi_key);
+    BCMOLT_MSGSET_CFG_PROP_GET(msg_set, log_entry, log_level_save);
+
+    do
+    {
+        /* call multi-get API */
+        err = bcmolt_cfg_get_multi(device, &multi_cfg.hdr, BCMOLT_FILTER_FLAGS_NONE, msg_set);
+        if (err != BCM_ERR_OK)
+        {
+            break;
+        }
+
+        /* for each log entry, reconfigure it to print only warning level and above to the screen (keeping current
+         * behavior for printing to RAM files) */
+        for (i = 0; i < msg_set->num_instances; i++)
+        {
+            bcmolt_log_entry_cfg *cfg = (bcmolt_log_entry_cfg *)msg_set->msg[i];
+            err = remote_logger_configure_log(device, &cfg->key, cfg->data.log_level_save);
+            if (err != BCM_ERR_OK)
+            {
+                break;
+            }
+        }
+
+        /* update the key for next call */
+        multi_cfg.key = *((bcmolt_log_entry_key *)msg_set->next_key);
+
+    /* keep calling the function until we have retrieved all entries */
+    } while (msg_set->more);
+
+    bcmolt_msg_set_free(msg_set);
+    return err;
+}
+
+static bcmos_errno remote_logger_cmd_start(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmos_errno err;
+
+    /* these parameters will always be non-NULL since they have default values */
+    bcmolt_remote_logger_cfg cfg = 
+    {
+        .max_file_size = bcmcli_find_named_parm(session, "max_file_size")->value.unumber,
+        .max_file_size_reached_behavior = (bcmolt_remote_logger_file_size_reached_behavior)
+            bcmcli_find_named_parm(session, "file_size_reached")->value.number,
+        .polling_period_ms = bcmcli_find_named_parm(session, "polling_period")->value.unumber,
+        .subsequent_delay_ms = bcmcli_find_named_parm(session, "subsequent_delay")->value.unumber,
+    };
+    strncpy(
+        cfg.filename,
+        bcmcli_find_named_parm(session, "filename")->value.string,
+        BCMOLT_REMOTE_LOGGER_FILENAME_MAX_LEN - 1); /* leave room for terminator */
+
+    /* if the user requested, set all logs on the device to print warnings/errors only */
+    if ((bcmos_bool)bcmcli_find_named_parm(session, "configure_logs")->value.number)
+    {
+        err = remote_logger_configure_logs(current_device);
+        if (err != BCM_ERR_OK)
+        {
+            return err;
+        }
+        bcmcli_session_print(session, "Reconfigured all log entries to print warning/error messages only\n");
+    }
+
+    err = bcmolt_remote_logger_appl_cfg_update(current_device, &cfg);
+    if (err != BCM_ERR_OK)
+    {
+        return err;
+    }
+
+    err = bcmolt_remote_logger_appl_start(current_device);
+    if (err == BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "Remote logger application started\n");
+    }
+    return err;
+}
+
+static bcmos_errno remote_logger_cmd_stop(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmos_errno err = bcmolt_remote_logger_appl_stop(current_device);
+    if (err == BCM_ERR_OK)
+    {
+        bcmcli_session_print(session, "Remote logger application stopped\n");
+    }
+    return err;
+}
+
+static bcmos_errno remote_logger_cmd_status(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    static int2str_t file_size_reached2str[] =
+    {
+        { BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_STOP, "stop" },
+        { BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_CLEAR, "clear" },
+        { -1 }
+    };
+
+    bcmos_errno err;
+    bcmolt_remote_logger_cfg cfg;
+
+    if (bcmolt_remote_logger_appl_is_running(current_device))
+    {
+        bcmcli_session_print(session, "Remote logger application is running\n");
+        err = bcmolt_remote_logger_appl_cfg_get(current_device, &cfg);
+        if (err != BCM_ERR_OK)
+        {
+            return err;
+        }
+
+        bcmcli_session_print(session, "filename=%s\n", cfg.filename);
+        bcmcli_session_print(session, "max_file_size=%u\n", cfg.max_file_size);
+        bcmcli_session_print(
+            session,
+            "file_size_reached=%s\n",
+            int2str(file_size_reached2str, cfg.max_file_size_reached_behavior));
+        bcmcli_session_print(session, "polling_period=%u\n", cfg.polling_period_ms);
+        bcmcli_session_print(session, "subsequent_delay=%u\n", cfg.subsequent_delay_ms);
+    }
+    else
+    {
+        bcmcli_session_print(session, "Remote logger application is not running\n");
+    }
+
+    return BCM_ERR_OK;
+}
+
+bcmos_errno bcmolt_remote_logger_appl_cli_init(bcmcli_entry *top_dir)
+{
+    static bcmcli_enum_val file_size_reached_behavior_table[] =
+    {
+        { "stop", BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_STOP },
+        { "clear", BCMOLT_REMOTE_LOGGER_FILE_SIZE_REACHED_BEHAVIOR_CLEAR },
+        BCMCLI_ENUM_LAST
+    };
+
+    bcmcli_entry *dir = bcmcli_dir_add(
+        top_dir,
+        "remote_logger",
+        "Periodically copy device log to local file",
+        BCMCLI_ACCESS_ADMIN,
+        NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD(dir, "start", "Start periodically copying device log to host", remote_logger_cmd_start,
+        BCMCLI_MAKE_PARM_DEFVAL(
+            "filename",
+            "Output file path",
+            BCMCLI_PARM_STRING,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            (long)BCMOLT_REMOTE_LOGGER_DEFAULT_FILENAME),
+        BCMCLI_MAKE_PARM_DEFVAL(
+            "max_file_size",
+            "Maximum output file size in bytes",
+            BCMCLI_PARM_UNUMBER,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            BCMOLT_REMOTE_LOGGER_DEFAULT_MAX_FILE_SIZE),
+        BCMCLI_MAKE_PARM_ENUM_DEFVAL(
+            "file_size_reached",
+            "Behavior when max file size is reached",
+            file_size_reached_behavior_table,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            BCMOLT_REMOTE_LOGGER_DEFAULT_FILE_REACHED_BEHAVIOR),
+        BCMCLI_MAKE_PARM_DEFVAL(
+            "polling_period",
+            "Polling period in ms",
+            BCMCLI_PARM_UNUMBER,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            BCMOLT_REMOTE_LOGGER_DEFAULT_POLLING_PERIOD_MS),
+        BCMCLI_MAKE_PARM_DEFVAL(
+            "subsequent_delay",
+            "Delay between repeated API calls in ms",
+            BCMCLI_PARM_UNUMBER,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            BCMOLT_REMOTE_LOGGER_DEFAULT_SUBSEQUENT_DELAY_MS),
+        BCMCLI_MAKE_PARM_ENUM_DEFVAL(
+            "configure_logs",
+            "Configure all device logs to print error/warning only",
+            bcmcli_enum_bool_table,
+            BCMCLI_PARM_FLAG_OPTIONAL,
+            "yes"));
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "stop", "Stop periodically copying device log to host", remote_logger_cmd_stop);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "status", "Show status / configuration parameters", remote_logger_cmd_status);
+
+    return BCM_ERR_OK;
+}
diff --git a/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.h b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.h
new file mode 100644
index 0000000..191ef8b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/remote_logger/bcmolt_user_appl_remote_logger_cli.h
@@ -0,0 +1,38 @@
+/*
+<: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.
+
+:>
+*/
+
+#ifndef _BCMOLT_USER_APPL_REMOTE_LEARNING_CLI_H_
+#define _BCMOLT_USER_APPL_REMOTE_LEARNING_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+
+bcmos_errno bcmolt_remote_logger_appl_cli_init(bcmcli_entry *top_dir);
+
+#endif
diff --git a/bcm68620_release/release/host_reference/user_appl/sw_upgrade/Makefile b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/Makefile
new file mode 100644
index 0000000..1641a6b
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/Makefile
@@ -0,0 +1,12 @@
+# SW upgrade application
+
+ifeq ("$(ENABLE_CLI)", "y")
+    MOD_NAME = bcm_sw_upgrade
+    MOD_TYPE = lib
+    MOD_DEPS = host_api
+    srcs = bcmolt_sw_upgrade_cli.c
+    
+    ifeq ("$(OS_KERNEL)", "linux")
+        MOD_DEPS += dev_log_linux
+    endif
+endif
diff --git a/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.c b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.c
new file mode 100644
index 0000000..c9b03b4
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.c
@@ -0,0 +1,70 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+*/
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+#include <bcmolt_model_types.h>
+#include <bcmolt_msg.h>
+#include <bcmolt_api.h>
+#include <bcm_api_cli_helpers.h>
+#include "bcmolt_sw_upgrade_cli.h"
+
+static bcmos_errno bcmolt_user_appl_cli_sw_upgrade_activate(bcmcli_session *session, const bcmcli_cmd_parm parms[], uint16_t n_parms)
+{
+    bcmolt_device_key key = {};
+    bcmolt_device_sw_upgrade_activate oper_sw_upgrade_activate;
+    bcmolt_device_disconnect oper_disconnect;
+    bcmolt_device_connect oper_connect;
+    bcmos_errno err;
+
+    BCMOLT_OPER_INIT(&oper_sw_upgrade_activate, device, sw_upgrade_activate, key);
+    err = bcmolt_oper_submit(0, &oper_sw_upgrade_activate.hdr);
+    if (err)
+        return err;
+
+    BCMOLT_OPER_INIT(&oper_disconnect, device, disconnect, key);
+    err = bcmolt_oper_submit(0, &oper_disconnect.hdr);
+    if (err)
+        return err;
+
+    BCMOLT_OPER_INIT(&oper_connect, device, connect, key);
+    return bcmolt_oper_submit(0, &oper_connect.hdr);
+}
+
+bcmos_errno bcmolt_user_appl_cli_sw_upgrade_init(bcmcli_entry *top_dir)
+{
+    bcmcli_entry *dir = bcmcli_dir_add(top_dir, "sw_upgrade",
+        "Software upgrade user application", BCMCLI_ACCESS_ADMIN, NULL);
+    BCMOS_CHECK_RETURN_ERROR(!dir, BCM_ERR_NOMEM);
+
+    BCMCLI_MAKE_CMD_NOPARM(dir, "activate", "SW upgrade image activation", bcmolt_user_appl_cli_sw_upgrade_activate);
+
+    return BCM_ERR_OK;
+}
+
diff --git a/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.h b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.h
new file mode 100644
index 0000000..9915a95
--- /dev/null
+++ b/bcm68620_release/release/host_reference/user_appl/sw_upgrade/bcmolt_sw_upgrade_cli.h
@@ -0,0 +1,38 @@
+/*
+<:copyright-BRCM:2014:DUAL/GPL:standard
+
+   Copyright (c) 2014 Broadcom Corporation
+   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.
+
+:>
+*/
+
+#ifndef _BCMOLT_SW_UPGRADE_CLI_H_
+#define _BCMOLT_SW_UPGRADE_CLI_H_
+
+#include <bcmos_system.h>
+#include <bcmcli.h>
+
+bcmos_errno bcmolt_user_appl_cli_sw_upgrade_init(bcmcli_entry *top_dir);
+
+#endif