blob: 3fc8d31b051124bfdf417e877e27d12369ba1498 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#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;
}