blob: a6ee38b0d3b046bceb12aef80e4a37121686d3c8 [file] [log] [blame]
/*
* Copyright (c) 2009 Mindspeed Technologies, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
*/
#include "module_mc6.h"
#include "types.h"
#include "system.h"
#include "fpp.h"
#include "module_ipv6.h"
#include "module_mc4.h"
#include "module_hidrv.h"
#include "module_timer.h"
#include "module_tx.h"
#include "module_Rx.h"
#include "gemac.h"
#include "fe.h"
#include "layer2.h"
#include "module_expt.h"
#include "alt_conf.h"
#include "module_rtp_relay.h"
#include "module_qm.h"
#include "module_wifi.h"
#if defined(COMCERTO_2000)
#include "control_common.h"
struct dma_pool *mc6_dma_pool;
extern TIMER_ENTRY mc6_delayed_remove_timer;
extern TIMER_ENTRY mc6_timer;
struct dlist_head hw_mc6_active_list[MC6_NUM_HASH_ENTRIES];
struct dlist_head hw_mc6_removal_list;
#endif
#if !defined(COMCERTO_2000)
PVOID MC6_entry_alloc(void)
{
return Heap_Alloc(sizeof(struct _tMC6Entry));
}
void MC6_entry_free(PMC6Entry this_entry)
{
Heap_Free((PVOID) this_entry);
}
static int MC6_entry_add(PMC6Entry this_entry, U32 hash)
{
/* Add software entry to local hash */
slist_add(&mc6_table_memory[hash], &this_entry->list);
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_mc6_link_stats_entry_by_tuple(this_entry, this_entry->src_addr, this_entry->dst_addr);
return NO_ERR;
}
static void MC6_entry_remove(PMC6Entry this_entry, U32 hash)
{
slist_remove(&mc6_table_memory[hash], &this_entry->list);
MC6_entry_free(this_entry);
}
void MC6_entry_update(PMC6Entry this_entry)
{
}
void MC6_mode_update(void)
{
}
#else
static void MC6_entry_add_to_pe(U32 dma_addr, U32 hash)
{
int id;
U32 *pe_addr;
pe_addr = (U32*)&mc6_table_memory[hash];
for (id=CLASS0_ID; id <= CLASS_MAX_ID; id++)
pe_dmem_write(id, dma_addr, virt_to_class_dmem(pe_addr), 4);
}
void MC6_mode_update(void)
{
int id;
U32 *pe_addr;
pe_addr = &CLASS_VARNAME2(g_mc6_mode);
for (id=CLASS0_ID; id <= CLASS_MAX_ID; id++)
pe_dmem_write(id, cpu_to_be32(g_mc6_mode), virt_to_class_dmem(pe_addr), 4);
}
void MC6_entry_update(PMC6Entry this_entry)
{
int i;
Phw_MC6Entry hw_entry = this_entry->hw_entry;
hw_entry_set_flags(&this_entry->hw_entry->flags, HASH_ENTRY_UPDATING | HASH_ENTRY_VALID);
/* FIXME: could be enhanced with a reworked hw_entry structure layout + memcpy */
SFL_memcpy(hw_entry->src_addr, this_entry->src_addr, IPV6_ADDRESS_LENGTH);
SFL_memcpy(hw_entry->dst_addr, this_entry->dst_addr, IPV6_ADDRESS_LENGTH);
hw_entry->status = cpu_to_be16(this_entry->status);
memcpy(&hw_entry->mcdest, &this_entry->mcdest, sizeof(MCxEntry));
hw_entry->mcdest.wifi_listener_timer = cpu_to_be32(this_entry->mcdest.wifi_listener_timer);
for (i=0; i < this_entry->mcdest.num_listeners; i++) {
hw_entry->mcdest.listeners[i].itf = (struct itf *) cpu_to_be32(virt_to_class_dmem(this_entry->mcdest.listeners[i].itf));
hw_entry->mcdest.listeners[i].timer = cpu_to_be32(this_entry->mcdest.listeners[i].timer);
}
hw_entry_set_flags(&this_entry->hw_entry->flags, HASH_ENTRY_VALID);
}
static void hw_MC6_entry_schedule_remove(struct _thw_MC6Entry *hw_entry)
{
hw_entry->removal_time = jiffies + 2;
dlist_add(&hw_mc6_removal_list, &hw_entry->list);
}
/** Processes hardware socket delayed removal list.
* Free all hardware socket in the removal list that have reached their removal time.
*
*
*/
static void hw_MC6_entry_delayed_remove(void)
{
struct dlist_head *entry;
struct _thw_MC6Entry *hw_entry;
dlist_for_each_safe(hw_entry, entry, &hw_mc6_removal_list, list)
{
if (!time_after(jiffies, hw_entry->removal_time))
continue;
dlist_remove(&hw_entry->list);
dma_pool_free(mc6_dma_pool, hw_entry, be32_to_cpu(hw_entry->dma_addr));
}
}
static PVOID MC6_entry_alloc(void)
{
return pfe_kzalloc(sizeof(struct _tMC6Entry), GFP_KERNEL);
}
static void MC6_entry_free(PMC6Entry this_entry)
{
pfe_kfree(this_entry);
}
static int MC6_entry_add(PMC6Entry this_entry, U32 hash)
{
struct _thw_MC6Entry *hw_entry;
struct _thw_MC6Entry *hw_entry_first;
dma_addr_t dma_addr;
int rc = NO_ERR;
/* Allocate hardware entry */
hw_entry = dma_pool_alloc(mc6_dma_pool, GFP_ATOMIC, &dma_addr);
if (!hw_entry)
{
rc = ERR_NOT_ENOUGH_MEMORY;
goto err;
}
hw_entry->dma_addr = cpu_to_be32(dma_addr);
/* Link software conntrack to hardware conntrack */
this_entry->hw_entry = hw_entry;
hw_entry->sw_entry = this_entry;
/* add hw entry to active list and update next pointer */
if(!dlist_empty(&hw_mc6_active_list[hash]))
{
/* list is not empty, and we'll be added at head, so current first will become our next pointer */
hw_entry_first = container_of(dlist_first(&hw_mc6_active_list[hash]), typeof(struct _thw_MC6Entry), list);
hw_entry_set_field(&hw_entry->next, hw_entry_get_field(&hw_entry_first->dma_addr));
}
else
{
/* entry is empty, so we'll be the first and only one entry */
hw_entry_set_field(&hw_entry->next, 0);
}
dlist_add(&hw_mc6_active_list[hash], &hw_entry->list);
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_mc6_link_stats_entry_by_tuple(this_entry, this_entry->src_addr, this_entry->dst_addr);
/* reflect changes to hardware entry */
MC6_entry_update(this_entry);
/* Update PE's internal memory socket cache tables with the HW entry's DDR address */
MC6_entry_add_to_pe(hw_entry->dma_addr, hash);
/* Add software entry to local hash */
slist_add(&mc6_table_memory[hash], &this_entry->list);
return NO_ERR;
err:
MC6_entry_free(this_entry);
return rc;
}
static void MC6_entry_remove(PMC6Entry this_entry, U32 hash)
{
struct _thw_MC6Entry *hw_entry;
struct _thw_MC6Entry *hw_entry_prev;
/* Check if there is a hardware entry */
if ((hw_entry = this_entry->hw_entry))
{
/* Detach from software socket */
this_entry->hw_entry = NULL;
/* if the removed entry is first in hash slot then only PE dmem hash need to be updated */
if (&hw_entry->list == dlist_first(&hw_mc6_active_list[hash]))
{
MC6_entry_add_to_pe(hw_entry_get_field(&hw_entry->next), hash);
}
else
{
hw_entry_prev = container_of(hw_entry->list.prev, typeof(struct _thw_MC6Entry), list);
hw_entry_set_field(&hw_entry_prev->next, hw_entry_get_field(&hw_entry->next));
}
dlist_remove(&hw_entry->list);
hw_MC6_entry_schedule_remove(hw_entry);
}
slist_remove(&mc6_table_memory[hash], &this_entry->list);
MC6_entry_free(this_entry);
}
#endif
int MC6_Get_Next_Hash_Entry(PMC6Command pMC6Cmd, int reset_action);
extern TIMER_ENTRY mc6_timer;
/**
* MC6_timeout
*
*
*/
static void MC6_timeout(void)
{
int i, j, k;
PMC6Entry this_entry;
BOOL entry_found = FALSE;
struct slist_entry *entry;
for(i=0;i<MC6_NUM_HASH_ENTRIES;i++)
{
slist_for_each_safe(this_entry, entry, &mc6_table_memory[i], list)
{
if ((this_entry->mcdest.flags & MC_ACP_LISTENER) && this_entry->mcdest.wifi_listener_timer > 0)
{
if ((this_entry->mcdest.wifi_listener_timer -= 10) <= 0)
this_entry->mcdest.flags &= ~MC_ACP_LISTENER;
else
entry_found = TRUE;
}
for (j = 0; j < this_entry->mcdest.num_listeners; j++)
{
if (this_entry->mcdest.listeners[j].timer <= 0)
continue;
entry_found = TRUE;
if ((this_entry->mcdest.listeners[j].timer -= 10) <= 0)
{
if(this_entry->mcdest.listeners[j].uc_bit)
this_entry->mcdest.num_mc_uc_listeners--;
#ifdef CFG_WIFI_OFFLOAD
else if(IS_WIFI_PORT(this_entry->mcdest.listeners[j].output_port))
this_entry->mcdest.num_wifi_mc_listeners--;
#endif
/* remove listener, and move following entries up */
for (k = j + 1; k < this_entry->mcdest.num_listeners; k++)
{
this_entry->mcdest.listeners[k - 1] = this_entry->mcdest.listeners[k];
}
this_entry->mcdest.num_listeners--;
j -= 1; // need index to remain in place after increment
}
}
if (this_entry->mcdest.num_listeners == 0 && ((this_entry->mcdest.flags & MC_ACP_LISTENER) == 0))
MC6_entry_remove(this_entry, i);
else
MC6_entry_update(this_entry);
}
}
/* turn off timer handler if no MC entries */
if (!entry_found)
timer_del(&mc6_timer);
}
static int MC6_is_listener(PMC6Entry this_group, POnifDesc onif_desc, U8 * UC_Mac, U16 UC_Bit)
{
int i;
U16 onif_index = get_onif_index(onif_desc);
/* check all listeners */
for(i = 0; i <this_group->mcdest.num_listeners; i++)
{
if (this_group->mcdest.listeners[i].output_index == onif_index)
{
if(UC_Bit){
if(TESTEQ_MACADDR(UC_Mac, this_group->mcdest.listeners[i].dstmac))
return i;
}
else if(this_group->mcdest.listeners[i].dstmac[0] & 0x01)
return i;
}
}
/* unknown interface name */
return -1;
}
/**
* MC6_check_overlapping_pair
* searches forwarding table for overlapping pairs based on source, destination ipv6 addresses, output interface & mask
*/
static MC6Entry *MC6_check_overlapping_pair(PMC6Command cmd)
{
PMC6Entry this_entry;
struct slist_entry *entry;
U32 hash_key;
U32 mask_len;
POnifDesc onif_desc = NULL;
U32 output_index;
int i,j;
#define _l_src ((PACKED_U32 *) (U8*)cmd->src_addr)
#define _l_dst ((PACKED_U32 *) (U8*)cmd->dst_addr)
hash_key = HASH_MC6(_l_dst);
slist_for_each(this_entry, entry, &mc6_table_memory[hash_key], list)
{
if (!IPV6_CMP(_l_dst,this_entry->dst_addr))
{
for (i = 0; i < cmd->num_output; i++)
{
onif_desc = get_onif_by_name(cmd->output_list[i].output_device_str);
if (onif_desc)
{
output_index = get_onif_index(onif_desc);
for (j = 0; j < this_entry->mcdest.num_listeners; j++)
{
/* Check overlapping pairs on the output indexes that are being programmed */
if(output_index == this_entry->mcdest.listeners[j].output_index)
{
if(cmd->src_mask_len < this_entry->mcdest.src_mask_len)
mask_len = cmd->src_mask_len;
else
mask_len = this_entry->mcdest.src_mask_len;
if (ipv6_cmp_mask((U32*) _l_src, (U32*)this_entry->src_addr, mask_len)) {
return this_entry;
}
}
}
}
else
{
if (this_entry->mcdest.flags & MC_ACP_LISTENER)
{
if(cmd->src_mask_len < this_entry->mcdest.src_mask_len)
mask_len = cmd->src_mask_len;
else
mask_len = this_entry->mcdest.src_mask_len;
if (ipv6_cmp_mask((U32*) _l_src, (U32*)this_entry->src_addr, mask_len)) {
return this_entry;
}
}
}
}
}
}
#undef _l_src
#undef _l_dst
return NULL;
}
/**
* MC6_command_add()
*
*
*/
static int MC6_command_add(PMC6Command cmd)
{
PMC6Entry this_entry = NULL;
struct slist_entry *entry;
PMC6Entry tmpEntry;
int i, rc = NO_ERR;
U32 hash_key;
POnifDesc onif_desc = NULL;
PMC6Listener new_listener;
U8 num_conf_mc_listeners = 0;
/* some errors parsing on the command*/
if(cmd->num_output > MC6_MAX_LISTENERS_PER_GROUP)
return ERR_MC_MAX_LISTENERS;
hash_key = HASH_MC6(cmd->dst_addr);
/* check if group entry already exist in the table */
slist_for_each(this_entry, entry, &mc6_table_memory[hash_key], list)
{
if (!IPV6_CMP(cmd->src_addr, this_entry->src_addr) && !IPV6_CMP(cmd->dst_addr, this_entry->dst_addr))
{
/* group already registered. just add listeners list the the corresponding entry */
/* numbers of listeners specified in the API command may be larger than the max supported listeners
a MC6 entry can handle. For now make it simple and just nack the command if all listeners can't be added */
if((this_entry->mcdest.num_listeners + cmd->num_output) > MC6_MAX_LISTENERS_PER_GROUP)
return ERR_MC_MAX_LISTENERS;
for (i = 0; i < cmd->num_output; i++) {
if(!cmd->output_list[i].uc_bit)
num_conf_mc_listeners++;
}
/* If the number of MC Listeners per group is more than MCx_MAX_MC_LISTENERS_PER_GROUP (4), throw the error */
if(this_entry->mcdest.num_listeners - this_entry->mcdest.num_mc_uc_listeners + num_conf_mc_listeners > MCx_MAX_MC_LISTENERS_PER_GROUP)
return ERR_MC_MAX_LISTENERS;
break;
}
}
/* If group doesn't exist, create and initialize the entry */
if(entry == NULL)
{
/* IF entry not already present look for overlapping pair.
Place it here so that we allow addition of Listeners to group that already exists */
tmpEntry = MC6_check_overlapping_pair(cmd);
if(tmpEntry != NULL)
return ERR_MC_ENTRY_OVERLAP;
if ((this_entry = MC6_entry_alloc()) == NULL)
return ERR_NOT_ENOUGH_MEMORY;
memset(this_entry, 0, sizeof (MC6Entry));
SFL_memcpy(this_entry->src_addr, cmd->src_addr, IPV6_ADDRESS_LENGTH);
this_entry->mcdest.src_mask_len = cmd->src_mask_len;
SFL_memcpy(this_entry->dst_addr, cmd->dst_addr, IPV6_ADDRESS_LENGTH);
this_entry->mcdest.flags = cmd->mode;
this_entry->mcdest.queue_base = cmd->queue;
}
for (i = 0; i < cmd->num_output; i++)
{
/* We have the linux output interface name. Let's find corresponding output
in the FPP world and pre-computed L2 headers that will be used within
all the packets belonging to a same flow */
onif_desc = get_onif_by_name(cmd->output_list[i].output_device_str);
if (onif_desc)
{
if (!(onif_desc->itf->type & IF_TYPE_TUNNEL) && (MC6_is_listener(this_entry, onif_desc, cmd->output_list[i].uc_mac,cmd->output_list[i].uc_bit) < 0)) // ignore if listener already exists or is a tunnel interface
{
new_listener = &this_entry->mcdest.listeners[this_entry->mcdest.num_listeners++];
new_listener->timer = cmd->output_list[i].timer;
new_listener->shaper_mask = cmd->output_list[i].shaper_mask;
new_listener->output_index = get_onif_index(onif_desc);
new_listener->output_port = itf_get_phys_port(onif_desc->itf);
#ifdef COMCERTO_100
U8 dstmac[ETHER_ADDR_LEN];
/* Set MAC destination address */
*(U16 *)dstmac = MC6_MAC_DEST_MARKER;
*(U32 *)(dstmac + 2) = this_entry->dst_addr[IP6_LO_ADDR];
new_listener->L2_header_size = l2_precalculate_header(onif_desc->itf, new_listener->L2_header, ETHERTYPE_IPV6_END, ETH_HDR_SIZE + VLAN_HDR_SIZE, &new_listener->output_port, dstmac);
L1_dc_clean(new_listener->L2_header,new_listener->L2_header+sizeof(new_listener->L2_header)-1);
#else
if(cmd->output_list[i].uc_bit) { // copy unicast Mac as destination address */
new_listener->uc_bit = 1;
COPY_MACADDR(new_listener->dstmac, cmd->output_list[i].uc_mac);
this_entry->mcdest.num_mc_uc_listeners++;
}
else
{
new_listener->uc_bit = 0;
/* Set MAC destination address */
*(U16 *)(new_listener->dstmac) = MC6_MAC_DEST_MARKER;
*(U32 *)(new_listener->dstmac + 2) = this_entry->dst_addr[IP6_LO_ADDR];
#ifdef CFG_WIFI_OFFLOAD
if(IS_WIFI_PORT(new_listener->output_port))
this_entry->mcdest.num_wifi_mc_listeners++;
#endif
}
new_listener->q_bit = cmd->output_list[i].q_bit;
new_listener->queue_base = (cmd->output_list[i].q_bit) ? cmd->output_list[i].queue: this_entry->mcdest.queue_base;
new_listener->itf = onif_desc->itf;
new_listener->family = PROTO_IPV6;
#endif
}
}
else
{
if(cmd->output_list[i].uc_bit) // Unicasting is unsupported when Wi-Fi is not offloaded.
return ERR_MC_INTERFACE_NOT_ALLOWED;
/* default path is to ACP. Don't need to pre-compute L2 header */
this_entry->mcdest.flags |= MC_ACP_LISTENER;
this_entry->mcdest.wifi_listener_timer = cmd->output_list[i].timer;
}
if ( (cmd->output_list[i].timer != 0xFFFFFFFF) && (cmd->output_list[i].timer > 0))
timer_add(&mc6_timer, TIMER_TICKS_PER_SEC * 10);
}
/* insert new entry in the multicast table */
/* If group doesn't exist, add it to the table with the listeners list */
if (entry == NULL)
rc = MC6_entry_add(this_entry, hash_key);
else
MC6_entry_update(this_entry);
return rc;
}
/**
* MC6_command_remove()
*
*
*/
static int MC6_command_remove(PMC6Command cmd)
{
PMC6Entry this_entry;
struct slist_entry *entry;
int i, j;
U32 hash_key;
int listener_index;
POnifDesc onif_desc;
hash_key = HASH_MC6(cmd->dst_addr);
slist_for_each(this_entry, entry, &mc6_table_memory[hash_key], list)
{
if (!IPV6_CMP(this_entry->src_addr, cmd->src_addr) && !IPV6_CMP(this_entry->dst_addr, cmd->dst_addr))
goto found;
}
return ERR_MC_ENTRY_NOT_FOUND;
found:
/* check all listeners to be removed */
for(i = 0; i < cmd->num_output; i++)
{
onif_desc = get_onif_by_name(cmd->output_list[i].output_device_str);
if(onif_desc)
{
if((listener_index = MC6_is_listener(this_entry, onif_desc,cmd->output_list[i].uc_mac,cmd->output_list[i].uc_bit)) >= 0)
{
if(this_entry->mcdest.listeners[listener_index].uc_bit)
this_entry->mcdest.num_mc_uc_listeners--;
#ifdef CFG_WIFI_OFFLOAD
else if(IS_WIFI_PORT(this_entry->mcdest.listeners[listener_index].output_port))
this_entry->mcdest.num_wifi_mc_listeners--;
#endif
/* remove listener, and move following entries up */
for (j = listener_index + 1; j < this_entry->mcdest.num_listeners; j++)
{
this_entry->mcdest.listeners[j - 1] = this_entry->mcdest.listeners[j];
#if !defined(COMCERTO_2000_CONTROL)
L1_dc_clean(&this_entry->mcdest.listeners[j - 1], ((U8*) &this_entry->mcdest.listeners[j])-1);
#endif
}
this_entry->mcdest.num_listeners--;
}
/* if listener does not belong to the group, just silently continue processing */
}
else
{
this_entry->mcdest.flags &= ~MC_ACP_LISTENER;
}
}
/* no more listerners attached to the group - remove entry */
if(this_entry->mcdest.num_listeners == 0 && ((this_entry->mcdest.flags & MC_ACP_LISTENER) == 0))
MC6_entry_remove(this_entry, hash_key);
else
MC6_entry_update(this_entry);
return NO_ERR;
}
// MC6_interface_purge -- remove any listeners referencing an output interface
void MC6_interface_purge(U32 if_index)
{
int i, j, k;
PMC6Entry this_entry;
struct slist_entry *entry;
for (i = 0; i < MC6_NUM_HASH_ENTRIES; i++)
{
slist_for_each_safe(this_entry, entry, &mc6_table_memory[i], list)
{
for (j = 0; j < this_entry->mcdest.num_listeners; j++)
{
if (if_index == this_entry->mcdest.listeners[j].output_index)
{
if(this_entry->mcdest.listeners[j].uc_bit)
this_entry->mcdest.num_mc_uc_listeners--;
#ifdef CFG_WIFI_OFFLOAD
else if(IS_WIFI_PORT(this_entry->mcdest.listeners[j].output_port))
this_entry->mcdest.num_wifi_mc_listeners--;
#endif
/* remove listener, and move following entries up */
for (k = j + 1; k < this_entry->mcdest.num_listeners; k++)
{
this_entry->mcdest.listeners[k - 1] = this_entry->mcdest.listeners[k];
}
this_entry->mcdest.num_listeners--;
j -= 1; // need index to remain in place after increment
}
}
if (this_entry->mcdest.num_listeners == 0 && ((this_entry->mcdest.flags & MC_ACP_LISTENER) == 0))
MC6_entry_remove(this_entry, i);
else
MC6_entry_update(this_entry);
}
}
}
/**
* MC6_command_refresh()
*
*
*/
static int MC6_command_refresh(PMC6Command cmd)
{
PMC6Entry this_entry;
POnifDesc onif_desc, new_onif_desc;
int listener_refreshed, listener_index;
int group_qb_refreshed = 0;
PMC6Listener update_listener;
this_entry = MC6_rule_search(cmd->src_addr, cmd->dst_addr);
if(this_entry != NULL)
{
this_entry->mcdest.queue_base = cmd->queue;
for(group_qb_refreshed = 0; group_qb_refreshed < this_entry->mcdest.num_listeners; group_qb_refreshed++)
if(!this_entry->mcdest.listeners[group_qb_refreshed].q_bit)
this_entry->mcdest.listeners[group_qb_refreshed].queue_base = cmd->queue;
/* now find listeners to refresh */
for(listener_refreshed = 0; listener_refreshed < cmd->num_output; listener_refreshed++)
{
onif_desc = get_onif_by_name(cmd->output_list[listener_refreshed].output_device_str);
if (onif_desc)
{
if ((listener_index = MC6_is_listener(this_entry, onif_desc, cmd->output_list[listener_refreshed].uc_mac, cmd->output_list[listener_refreshed].uc_bit)) >=0)
{
update_listener = &this_entry->mcdest.listeners[listener_index];
if(cmd->output_list[listener_refreshed].if_bit)
{
if( (new_onif_desc = (get_onif_by_name(cmd->output_list[listener_refreshed].new_output_device_str))) != NULL)
{
if(MC6_is_listener(this_entry, new_onif_desc, cmd->output_list[listener_refreshed].uc_mac, cmd->output_list[listener_refreshed].uc_bit) < 0)
{
update_listener->output_index = get_onif_index(new_onif_desc);
update_listener->output_port = itf_get_phys_port(new_onif_desc->itf);
#ifdef COMCERTO_100
{
U8 dstmac[ETHER_ADDR_LEN];
/* Set MAC destination address */
*(U16 *)dstmac = MC6_MAC_DEST_MARKER;
*(U32 *)(dstmac + 2) = this_entry->dst_addr[IP6_LO_ADDR];
update_listener->L2_header_size = l2_precalculate_header(new_onif_desc->itf, update_listener->L2_header, ETHERTYPE_IPV6_END, ETH_HDR_SIZE + VLAN_HDR_SIZE, &update_listener->output_port, dstmac);
L1_dc_clean(update_listener->L2_header, update_listener->L2_header+sizeof(update_listener->L2_header)-1);
}
#else
if(cmd->output_list[listener_refreshed].uc_bit) { // copy unicast Mac as destination address
update_listener->uc_bit = 1;
COPY_MACADDR(update_listener->dstmac, cmd->output_list[listener_refreshed].uc_mac);
}
else
{
update_listener->uc_bit = 0;
*(U16 *)(update_listener->dstmac) = MC6_MAC_DEST_MARKER;
*(U32 *)(update_listener->dstmac + 2) = this_entry->dst_addr[IP6_LO_ADDR];
}
update_listener->itf = new_onif_desc->itf;
#endif
}
else
return ERR_MC_DUP_LISTENER;
}
else
return ERR_UNKNOWN_INTERFACE;
}
update_listener->timer = cmd->output_list[listener_refreshed].timer;
update_listener->shaper_mask = cmd->output_list[listener_refreshed].shaper_mask;
if(cmd->output_list[listener_refreshed].q_bit)
update_listener->queue_base = cmd->output_list[listener_refreshed].queue;
}
}
else
{
this_entry->mcdest.wifi_listener_timer = cmd->output_list[listener_refreshed].timer;
}
if ((cmd->output_list[listener_refreshed].timer != 0xFFFFFFFF) && (cmd->output_list[listener_refreshed].timer > 0))
timer_add(&mc6_timer, TIMER_TICKS_PER_SEC * 10);
}
MC6_entry_update(this_entry);
}
else
return ERR_MC_ENTRY_NOT_FOUND;
return NO_ERR;
}
/**
* MC6_handle_MULTICAST
*
*
*/
static int MC6_handle_MULTICAST(U16 *p, U16 Length)
{
MC6Command cmd;
U16 rc = NO_ERR;
int reset_action = 0;
/* Check length */
if ((Length > sizeof(MC6Command)) || (Length < MC6_MIN_COMMAND_SIZE))
return ERR_WRONG_COMMAND_SIZE;
memset(&cmd, 0, sizeof(MC6Command));
SFL_memcpy((U8*)&cmd, (U8*)p, Length);
switch(cmd.action)
{
case MC_ACTION_REMOVE:
rc = MC6_command_remove(&cmd);
break;
case MC_ACTION_ADD:
rc = MC6_command_add(&cmd);
break;
case MC_ACTION_REFRESH:
rc = MC6_command_refresh(&cmd);
break;
case ACTION_QUERY:
reset_action = 1;
case ACTION_QUERY_CONT:
rc = MC6_Get_Next_Hash_Entry((MC6Command*)p, reset_action);
break;
default :
rc = ERR_UNKNOWN_ACTION;
break;
}
return rc;
}
static void MC6_Flush_Entries(void)
{
PMC6Entry this_entry;
struct slist_entry *entry;
int i;
for (i = 0; i < MC6_NUM_HASH_ENTRIES; i++)
{
slist_for_each_safe(this_entry, entry, &mc6_table_memory[i], list)
{
MC6_entry_remove(this_entry, i);
}
}
}
static int MC6_handle_MODE(U16 *p , U16 Length)
{
U16 mode = *p;
int rc = NO_ERR;
if (Length != 2)
return ERR_WRONG_COMMAND_SIZE;
switch (mode)
{
case MC_MODE_BRIDGED:
case MC_MODE_ROUTED:
g_mc6_mode = mode;
MC6_mode_update();
break;
default:
rc = ERR_UNKNOWN_ACTION;
break;
}
return rc;
}
/**
* MC6_handle_RESET
*
*
*/
static int MC6_handle_RESET(void)
{
MC6_Flush_Entries();
return NO_ERR;
}
/**
* M_mc6_cmdproc
*
*
*
*/
static U16 M_mc6_cmdproc(U16 cmd_code, U16 cmd_len, U16 *pcmd)
{
U16 rc;
U16 action;
U16 querySize = 0;
switch (cmd_code)
{
case CMD_MC6_MULTICAST:
action = *pcmd;
rc = MC6_handle_MULTICAST(pcmd, cmd_len);
if (rc == NO_ERR && (action == ACTION_QUERY || action == ACTION_QUERY_CONT))
querySize = sizeof(MC6Command);
break;
case CMD_MC6_RESET:
rc = MC6_handle_RESET();
break;
case CMD_MC6_MODE:
rc = MC6_handle_MODE(pcmd, cmd_len);
break;
case CMD_MC4_MULTICAST:
action = *pcmd;
rc = MC4_handle_MULTICAST(pcmd, cmd_len);
if (rc == NO_ERR && (action == ACTION_QUERY || action == ACTION_QUERY_CONT))
querySize = sizeof(MC4Command);
break;
case CMD_MC4_RESET:
rc = MC4_handle_RESET();
break;
default:
rc = ERR_UNKNOWN_COMMAND;
break;
}
*pcmd = rc;
return 2 + querySize;
}
/**
* M_mc6_init
*
*
*
*/
BOOL mc6_init(void)
{
int i;
#if defined(COMCERTO_2000)
struct pfe_ctrl *ctrl = &pfe->ctrl;
#endif
#if defined(COMCERTO_1000)
if (multicast_init())
return 1;
#endif
/* module entry point and channels registration */
#if !defined(COMCERTO_2000)
set_event_handler(EVENT_MC6, M_mc6_entry);
#endif
set_cmd_handler(EVENT_MC6, M_mc6_cmdproc);
for (i = 0; i < MC6_NUM_HASH_ENTRIES; i++)
{
slist_head_init(&mc6_table_memory[i]);
}
#if defined(COMCERTO_2000)
mc6_dma_pool = ctrl->dma_pool_512;
for (i = 0; i < MC6_NUM_HASH_ENTRIES; i++)
dlist_head_init(&hw_mc6_active_list[i]);
dlist_head_init(&hw_mc6_removal_list);
timer_init(&mc6_delayed_remove_timer, hw_MC6_entry_delayed_remove);
timer_add(&mc6_delayed_remove_timer, CT_TIMER_INTERVAL);
#endif
timer_init(&mc6_timer, MC6_timeout);
g_mc6_mode = MC_MODE_BRIDGED;
MC6_mode_update();
return 0;
}
void mc6_exit(void)
{
#if defined(COMCERTO_2000)
struct dlist_head *entry;
struct _thw_MC6Entry *hw_entry;
timer_del(&mc6_timer);
timer_del(&mc6_delayed_remove_timer);
MC6_handle_RESET();
dlist_for_each_safe(hw_entry, entry, &hw_mc6_removal_list, list)
{
dlist_remove(&hw_entry->list);
dma_pool_free(mc6_dma_pool, hw_entry, be32_to_cpu(hw_entry->dma_addr));
}
#endif
}