| /* |
| * 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 |
| } |