blob: 05894fdf6646f2897e590ef4b14dc23b396047b2 [file] [log] [blame]
/******************************************************************************
*
* <:copyright-BRCM:2016:DUAL/GPL:standard
*
* Copyright (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 bal_dpp_group.c
* @brief BAL Switch util helper functions that handle group requests
* @addtogroup sw_util
*/
/*@{*/
#include <bal_common.h>
#include <bcm_dev_log.h>
#include <bal_msg.h>
#include <bal_utils_msg.h>
#include "bcmos_errno.h"
#ifndef TEST_SW_UTIL_LOOPBACK
#include <bcm/types.h>
#include <sal/core/libc.h>
#ifndef sal_memset
#define sal_memset memset
#endif
#include <bcm/port.h>
#include <bcm/vlan.h>
#include <bcm/error.h>
#include <bcm/multicast.h>
#include "bal_dpp_group.h"
#include "bal_switch_acc_term.h"
#include "bal_dpp_vswitch.h"
/* A local link list to keep track of group list */
TAILQ_HEAD(bal_sw_group_list_head, bal_sw_group_list) g_swutil_group_list;
/**
* @brief The group list init function prepare a link list to keep track of
* multicast group in the switch util
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_list_init(void)
{
TAILQ_INIT(&g_swutil_group_list);
return BCM_ERR_OK;
}
/**
* @brief The group list finish function release all resources allocated in the list
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_list_finish(void)
{
bal_sw_group_list *current_entry, *p_temp_entry;
/* Free all the entries in the list */
TAILQ_FOREACH_SAFE(current_entry,
&g_swutil_group_list,
next_grp,
p_temp_entry)
{
/* Remove it from the list */
TAILQ_REMOVE(&g_swutil_group_list, current_entry, next_grp);
bcmos_free(current_entry);
}
return BCM_ERR_OK;
}
/**
* @brief The group list search function by BAL group id
*
* @param grp_id the group id that need to match the entry in the list
* @return pointer to an element in the list, NULL if search failed
*/
bal_sw_group_list *bal_sw_util_dpp_group_list_get_by_id(uint32_t grp_id)
{
bal_sw_group_list *p_entry, *p_temp;
TAILQ_FOREACH_SAFE(p_entry, &g_swutil_group_list, next_grp, p_temp)
{
if( p_entry->bal_grp_id == grp_id)
{
break;
}
}
/* if reach the end of the list, TAILQ_FOREACH_SAFE set the p_entry to NULL */
return p_entry;
}
/*
* @brief The group list insert function
*
* @param entry the group element to be added in the link list
* @return pointer to the newly inserted group in the list, NULL if operation failed
*/
static bal_sw_group_list *bal_sw_util_group_list_insert(bal_sw_group_list entry)
{
bal_sw_group_list *p_new_entry;
p_new_entry = bcmos_calloc(sizeof(bal_sw_group_list));
if(NULL == p_new_entry)
{
BCM_LOG(ERROR, log_id_sw_util, "GROUP list insert out of memory\n");
return NULL;
}
*p_new_entry = entry;
TAILQ_INSERT_TAIL(&g_swutil_group_list, p_new_entry, next_grp);
return p_new_entry;
}
/*
* @brief The group list remove function
*
* @param p_entry Pointer to the group element in the link list result from the search functions
* @return error code
*/
static bcmos_errno bal_sw_util_group_list_remove(bal_sw_group_list *p_entry)
{
TAILQ_REMOVE(&g_swutil_group_list, p_entry, next_grp);
bcmos_free(p_entry);
return BCM_ERR_OK;
}
/**
* @brief The group list membership check function by port
* Validate if interface/svervice_port is already a member
*
* @param p_grp_port the interface that need to be matched in the list
* @param p_entry pointer to an entry in the group list
* @return index to the port member, -1 if not found
*/
static int bal_sw_util_dpp_group_list_membership_check(bal_sw_group_list *p_entry, bal_sw_group_port *p_grp_port)
{
int i;
if(p_entry == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group membership check with NULL parameter\n");
return BCMOS_FALSE;
}
for(i=0; i<p_entry->num_port; i++)
{
if(p_entry->port[i].port == p_grp_port->port &&
p_entry->port[i].svc_port == p_grp_port->svc_port)
{
p_grp_port->gport = p_entry->port[i].gport;
return i;
}
}
return -1;
}
/**
* @brief The group list membership remove function by port
* remove interface/svervice_port from the group membership
*
* @param grp_port the interface that need to be matched in the list
* @param p_entry pointer to an entry in the group list
* @return boolen TRUE or FALSE
*/
static bcmos_bool bal_sw_util_dpp_group_list_membership_rem(bal_sw_group_list *p_entry, bal_sw_group_port grp_port)
{
int i, rv;
if(p_entry == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group membership remove with NULL parameter\n");
return BCMOS_FALSE;
}
for(i=0; i<p_entry->num_port; i++)
{
if(p_entry->port[i].port == grp_port.port &&
p_entry->port[i].svc_port == grp_port.svc_port)
{
/* destroy the vlan port (i.e., PON LIF) */
if(p_entry->port[i].gport)
{
rv = bcm_vlan_port_destroy(p_entry->device, p_entry->port[i].gport);
if (rv != BCM_E_NONE)
{
BCM_LOG(WARNING, log_id_sw_util, "Warning, GROUP:bcm_vlan_port_destroy pon %d failed %d\n", p_entry->port[i].gport, rv);
/* Likely a bug in the 6.5.4 release, ignore for now
return BCMOS_FALSE;
*/
}
p_entry->port[i].gport = 0;
}
break;
}
}
/* if can't find the port, just return OK */
if(i != p_entry->num_port)
{
/* pack the list */
bal_sw_group_port null_port={0};
for(; i<p_entry->num_port-1; i++)
{
p_entry->port[i] = p_entry->port[i+1];
}
p_entry->port[i] = null_port;
p_entry->num_port--;
}
return BCMOS_TRUE;
}
/**
* @brief The group create function create a multicast group that contains egress LIF.
* This group can later connects to virtual switch in multiple FLOWs.
*
* The pointer of the created group will be returned
*
* @param unit switch device id
* @param p_grp a pointer to the multicast group definition
*
* @return pointer to the group list entry, NULL if operation failed
*/
bal_sw_group_list *bal_sw_util_dpp_group_create(int unit, bcmbal_group_cfg *p_grp)
{
bal_sw_group_list *p_grp_list, grp_list_elm;
int32_t multicast_id;
uint32_t svc_indx;
int rv, flags;
bal_sw_vsi_service *p_vsi_service;
if(p_grp == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group create with NULL parameter\n");
return NULL;
}
/* check group list */
p_grp_list = bal_sw_util_dpp_group_list_get_by_id(p_grp->key.group_id);
if(p_grp_list)
{
BCM_LOG(ERROR, log_id_sw_util, "group create with group id %d already existed\n", p_grp->key.group_id);
return NULL;
}
/* alloc an entry in the group list*/
memset(&grp_list_elm, 0, sizeof(bal_sw_group_list));
grp_list_elm.device = unit;
grp_list_elm.bal_grp_id = p_grp->key.group_id;
p_grp_list = bal_sw_util_group_list_insert(grp_list_elm);
if(p_grp_list == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group create failed insert to group list\n");
return NULL;
}
/* create a vswitch with empty service, we need vsi to create the MC flooding group for egress */
p_vsi_service = bal_sw_util_dpp_vsi_service_create(unit, NULL, &svc_indx);
if(NULL == p_vsi_service)
{
BCM_LOG(ERROR, log_id_sw_util, "create vsi service for group failed \n");
bal_sw_util_group_list_remove(p_grp_list);
return NULL;
}
p_grp_list->p_vsi = p_vsi_service;
multicast_id = p_vsi_service->vswitch + BAL_DPP_MC_OFFSET;
rv = bcm_multicast_group_is_free(unit, multicast_id);
if (rv == BCM_E_EXISTS)
{
rv = bcm_multicast_destroy(unit, multicast_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "MC: bcm_multicast_destroy 0x%x failed %d \n", multicast_id, rv);
bal_sw_util_group_list_remove(p_grp_list);
return NULL;
}
}
flags = BCM_MULTICAST_INGRESS_GROUP | BCM_MULTICAST_WITH_ID | BCM_MULTICAST_TYPE_L2;
rv = bcm_multicast_create(unit, flags, &multicast_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "MC: in bcm_multicast_create 0x%x w ingress failed %d \n", multicast_id, rv);
bal_sw_util_group_list_remove(p_grp_list);
return NULL;
}
else
{
BCM_LOG(INFO, log_id_sw_util, "MC: vswitch flood group 0x%x created\n", multicast_id);
}
/* update the group list info */
p_grp_list->l2_grp_id = multicast_id;
return p_grp_list;
}
/**
* @brief The group add function add members to the multicast group.
* In this function, gourp members are PON interfaces
*
* @param unit switch device id
* @param p_grp a pointer to the multicast group definition
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_add(int unit, bcmbal_group_cfg *p_grp)
{
bal_sw_group_list *p_grp_list;
int i, pon, rv, indx;
bcm_gport_t pon_gport;
bcmos_errno ret;
int pon_encap_id;
bcm_vlan_port_t vp;
uint32_t flood_grp;
if(p_grp == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group add with NULL parameter\n");
return BCM_ERR_PARM;
}
/* check if L2 group has been created */
p_grp_list = bal_sw_util_dpp_group_list_get_by_id(p_grp->key.group_id);
if(!p_grp_list)
{
BCM_LOG(INFO, log_id_sw_util, "group add will alloc a L2 MC group, group id %d \n", p_grp->key.group_id);
p_grp_list = bal_sw_util_dpp_group_create(unit, p_grp);
if(p_grp_list == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group add failed create L2 Mc group\n");
return BCM_ERR_NORES;
}
}
ret = BCM_ERR_OK;
for(i=0; i<p_grp->data.members.len; i++)
{
bcmbal_group_member_info *p_member = &p_grp->data.members.val[i];
bal_sw_group_port port_member;
/* find the L2 logical interface number */
pon = bal_bcm_pon_inf_pbm_get(p_member->intf_id);
/* check if interface is already a member in the group list */
port_member.port = pon;
port_member.svc_port = p_member->svc_port_id;
if( -1 != bal_sw_util_dpp_group_list_membership_check(p_grp_list, &port_member))
{
BCM_LOG(INFO, log_id_sw_util, "pon interface %d with service port %d already a member\n",p_member->intf_id, p_member->svc_port_id);
continue;
}
/* check if interface already has an entry in the group list */
port_member.port = pon;
port_member.svc_port = 0;
indx = bal_sw_util_dpp_group_list_membership_check(p_grp_list, &port_member);
if( -1 == indx)
{
/* make sure there is still room in the group list for new member */
if(p_grp_list->num_port >= MAX_PON_PORT)
{
BCM_LOG(ERROR, log_id_sw_util,
"Error, GROUP: Reach maximum number of membership in the group list\n");
ret = BCM_ERR_NORES;
break;
}
}
/* if group has a owner, create a LIF for each member */
if(p_grp->data.owner != BCMBAL_GROUP_OWNER_NONE)
{
/* Map the tunnel ID to a PON channel OTM port */
rv = bcm_port_pon_tunnel_map_set(unit,
pon,
p_member->svc_port_id,
pon);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util,
"Error, GROUP:bcm_port_pon_tunnel_map_set on pon %d failed %d"
" (have you chosen the correct intf_maptable?)\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
/* Create the pon LIF to be DS only */
bcm_vlan_port_t_init(&vp);
/* preserve any incoming packet vlan tags, if vlan actions are required, do it using egress translation */
vp.flags = BCM_VLAN_PORT_OUTER_VLAN_PRESERVE | BCM_VLAN_PORT_INNER_VLAN_PRESERVE;
/* It is required to set the FEC flag so that the egress redirection action from the DS classification can
select the correct PON LIF to forward the packet */
vp.flags |= BCM_VLAN_PORT_FORWARD_GROUP;
vp.criteria = BCM_VLAN_PORT_MATCH_PORT_TUNNEL;
vp.port = pon;
vp.match_tunnel_value = p_member->svc_port_id;
vp.egress_tunnel_value = p_member->svc_port_id;
vp.vsi = 0; /* will be populated when the gport is added to service, using vswitch_port_add */
/* Create the vlan port (i.e., PON LIF) */
rv = bcm_vlan_port_create(unit, &vp);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_vlan_port_create pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
else
{
BCM_LOG(INFO, log_id_sw_util, "Info, GROUP:bcm_vlan_port_create pon %d with tunnel %d success\n", pon, p_member->svc_port_id);
}
pon_gport = vp.vlan_port_id;
rv = bcm_port_discard_set(unit, pon_gport, BCM_PORT_DISCARD_INGRESS);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_port_discard_set on pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
/* select the flooding group */
if(p_grp->data.owner == BCMBAL_GROUP_OWNER_MULTICAST)
{
flood_grp = p_grp_list->l2_grp_id;
}
else
{
flood_grp = (p_grp_list->p_vsi)->ds_flood_grp_id;
}
/* join the L2 multicast group */
rv = bcm_multicast_vlan_encap_get(unit, flood_grp, pon, pon_gport, &pon_encap_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_vlan_encap_get on pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
rv = bcm_multicast_ingress_add(unit, flood_grp, pon, pon_encap_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_multicast_ingress_add on pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
BCM_LOG(INFO, log_id_sw_util, "Info, GROUP:pon %d join l2 mc group 0x%x success\n", pon, flood_grp);
/* update the group list membership */
port_member.gport = pon_gport;
}
else
{
port_member.gport = 0;
}
p_grp_list->port[p_grp_list->num_port] = port_member;
p_grp_list->num_port++;
}
return ret;
}
/**
* @brief The group remove function remove members from the multicast group.
* In this function, group members are PON interfaces
*
* @param unit switch device id
* @param p_grp a pointer to the multicast group definition
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_rem(int unit, bcmbal_group_cfg *p_grp)
{
bal_sw_group_list *p_grp_list;
int i, pon, rv;
bcmos_errno ret;
int pon_encap_id;
if(p_grp == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group remove with NULL parameter\n");
return BCM_ERR_PARM;
}
/* check if L2 group has been created */
p_grp_list = bal_sw_util_dpp_group_list_get_by_id(p_grp->key.group_id);
if(!p_grp_list)
{
BCM_LOG(ERROR, log_id_sw_util, "group remove can not find L2 Mc group %d\n", p_grp->key.group_id);
return BCM_ERR_INTERNAL;
}
/* remove LIF for each member */
ret = BCM_ERR_OK;
for(i=0; i<p_grp->data.members.len; i++)
{
bcmbal_group_member_info *p_member = &p_grp->data.members.val[i];
bal_sw_group_port port_member;
/* find the L2 logical interface number */
pon = bal_bcm_pon_inf_pbm_get(p_member->intf_id);
/* check if interface is already removed from the group list */
port_member.port = pon;
port_member.svc_port = p_member->svc_port_id;
if( -1 == bal_sw_util_dpp_group_list_membership_check(p_grp_list, &port_member))
{
BCM_LOG(INFO, log_id_sw_util, "pon interface %d with service port %d is not a member\n",p_member->intf_id, p_member->svc_port_id);
continue;
}
/* if the member has a LIF assigned, remove it from the switch multicast group */
if(port_member.gport)
{
/* leave the L2 multicast group */
rv = bcm_multicast_vlan_encap_get(unit, p_grp_list->l2_grp_id, pon, port_member.gport, &pon_encap_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_vlan_encap_get on pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
rv = bcm_multicast_ingress_delete(unit, p_grp_list->l2_grp_id, pon, pon_encap_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_multicast_ingress_delete on pon %d failed %d\n", pon, rv);
ret = BCM_ERR_INTERNAL;
break;
}
}
if( BCMOS_FALSE == bal_sw_util_dpp_group_list_membership_rem(p_grp_list, port_member))
{
BCM_LOG(INFO, log_id_sw_util, "pon interface %d with service port %d membership remove failed\n",p_member->intf_id, p_member->svc_port_id);
ret = BCM_ERR_INTERNAL;
break;
}
}
return ret;
}
/**
* @brief The group set function replace members from the multicast group.
* In this function, group members are PON interfaces
*
* @param unit switch device id
* @param p_grp a pointer to the multicast group definition
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_set(int unit, bcmbal_group_cfg *p_grp)
{
bal_sw_group_list *p_grp_list;
bcmos_errno ret = BCM_ERR_OK;
int pon_encap_id;
if(p_grp == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group replace with NULL parameter\n");
return BCM_ERR_PARM;
}
/* check if L2 group has been created */
p_grp_list = bal_sw_util_dpp_group_list_get_by_id(p_grp->key.group_id);
if(!p_grp_list)
{
/* mark it OK to create group at the end */
ret = BCM_ERR_OK;
}
else if(p_grp_list->num_port)
{
/* remove all members from the group */
do
{
if( p_grp_list->port[0].gport)
{
/* leave the L2 multicast group */
if (BCM_E_NONE != bcm_multicast_vlan_encap_get(unit, p_grp_list->l2_grp_id, p_grp_list->port[0].port, p_grp_list->port[0].gport, &pon_encap_id))
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_vlan_encap_get on pon %d failed\n", p_grp_list->port[0].port);
ret = BCM_ERR_INTERNAL;
break;
}
if (BCM_E_NONE != bcm_multicast_ingress_delete(unit, p_grp_list->l2_grp_id, p_grp_list->port[0].port, pon_encap_id))
{
BCM_LOG(ERROR, log_id_sw_util, "Error, GROUP:bcm_multicast_ingress_delete on pon %d failed\n", p_grp_list->port[0].port);
ret = BCM_ERR_INTERNAL;
break;
}
}
if( BCMOS_FALSE == bal_sw_util_dpp_group_list_membership_rem(p_grp_list, p_grp_list->port[0]))
{
BCM_LOG(ERROR, log_id_sw_util, "group set fail to remove existing member interface %d\n",p_grp_list->port[0].port );
ret = BCM_ERR_INTERNAL;
break;
}
}while(p_grp_list->num_port);
}
if(ret == BCM_ERR_OK)
{
/* create/add the group */
return bal_sw_util_dpp_group_add(unit, p_grp);
}
return ret;
}
/**
* @brief The group destroy function free up a multicast group.
* The group will be removed from the group list.
* All L2 resources accociated with the group will be free.
*
* @param unit switch device id
* @param p_grp a pointer to the multicast group definition
*
* @return error code
*/
bcmos_errno bal_sw_util_dpp_group_destroy(int unit, bcmbal_group_cfg *p_grp)
{
bal_sw_group_list *p_grp_list;
int rv;
bcmos_errno ret;
if(p_grp == NULL)
{
BCM_LOG(ERROR, log_id_sw_util, "group destroy with NULL parameter\n");
return BCM_ERR_PARM;
}
/* check group list */
p_grp_list = bal_sw_util_dpp_group_list_get_by_id(p_grp->key.group_id);
if(!p_grp_list)
{
BCM_LOG(INFO, log_id_sw_util, "group destroy with group id %d does not existed\n", p_grp->key.group_id);
return BCM_ERR_OK;
}
/* can't destroy if any flow is still reference it */
if(p_grp_list->use_count)
{
BCM_LOG(WARNING, log_id_sw_util, "group destroy with group id %d is busy\n", p_grp->key.group_id);
return BCM_ERR_INVALID_OP;
}
/* Do our best to clean up */
/* free resources used by all members */
ret = bal_sw_util_dpp_group_rem(unit, p_grp);
if (ret != BCM_ERR_OK)
{
BCM_LOG(ERROR, log_id_sw_util, "MC: destroy group 0x%x failed to free members \n", p_grp_list->l2_grp_id);
}
/* free the L2 multicast group */
rv = bcm_multicast_destroy(unit, p_grp_list->l2_grp_id);
if (rv != BCM_E_NONE)
{
BCM_LOG(ERROR, log_id_sw_util, "MC: in bcm_multicast_destroy 0x%x failed %d \n", p_grp_list->l2_grp_id, rv);
}
/* clean up the vsi */
bal_sw_util_dpp_vsi_service_destroy(unit, p_grp_list->p_vsi);
/* remove from the list */
bal_sw_util_group_list_remove(p_grp_list);
return BCM_ERR_OK;
}
#endif /* LOOPBACK */
/*@}*/