blob: f533272d6d83d974dbee0cbdc9336926fb8f9662 [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 "types.h"
#include "system.h"
#include "fpp.h"
#include "multicast.h"
#ifdef COMCERTO_2000_CLASS
/** Prepend L2 headers, excluding the outermost one in case of QinQ.
* This behavior is needed on C2k because the hardware will take care of modifying
* the last header, as long as it only means changing the src MAC address and/or VLAN tag.
* @param mtd
* @param pmcl
*/
U32 mc_prepend_upper_header(PMetadata mtd, PMCListener pmcl, U16 **srcMac, real_vlan_hdr_t *vlan_tag, U8 flags)
{
U16 ethertype = l2_get_tid(pmcl->family);
U16 vlanid = 0;
U16 srcMac_orig[3];
U16 *pkt_src_mac;
struct itf *itf = pmcl->itf;
int res = 0;
pkt_src_mac = (U16 *) (mtd->data + BaseOffset + ETHER_ADDR_LEN);
COPY_MACADDR(srcMac_orig, pkt_src_mac);
if (itf->type & IF_TYPE_PPPOE)
{
M_pppoe_encapsulate(mtd, (pPPPoE_Info)itf, ethertype, 1);
ethertype = ETHERTYPE_PPPOE_END;
itf = itf->phys;
}
#ifdef CFG_MACVLAN
if (itf->type & IF_TYPE_MACVLAN)
{
*srcMac = ((PMacvlanEntry)itf)->MACaddr;
itf = itf->phys;
}
#endif
/* We can add but not remove VLANs in hardware, so:
* . if we must add a VLAN tag, we store its value to program the HW later (so we'll be able to handle listeners
* with or without VLAN tags).
* . if we must add 2 VLAN tags (QinQ), we add the inner one and store the value of the second to program the HW later
* (so we'll be able to handle listeners with one or 2 tags).
* => This means we cannot handle regular ethernet and QinQ listeners in hardware for the same packet.
*/
if (itf->type & IF_TYPE_VLAN)
{
vlanid = ((PVlanEntry) itf)->VlanId;
/* QinQ */
if (itf->phys->type & IF_TYPE_VLAN)
{
res = 1;
M_vlan_encapsulate(mtd, (PVlanEntry)itf, ethertype, 1);
ethertype = ETHERTYPE_VLAN_END;
itf = itf->phys;
vlanid = ((PVlanEntry) itf)->VlanId;
}
itf = itf->phys;
}
if (vlanid)
{
fill_real_vlan_header(vlan_tag, ETHERTYPE_VLAN_END, vlanid, mtd->vlan_pbits);
}
mtd->length += ETH_HEADER_SIZE;
mtd->offset -= ETH_HEADER_SIZE;
#ifdef CFG_MACVLAN
if (itf->type & IF_TYPE_MACVLAN)
{
*srcMac = ((PMacvlanEntry)itf)->MACaddr;
itf = itf->phys;
}
else
#endif
if (!*srcMac)
*srcMac = (U16 *) ((struct physical_port *)itf)->mac_addr;
mtd->output_port = ((struct physical_port *)itf)->id;
if((IS_WIFI_PORT(mtd->output_port)) && ((flags & MC_MODE_MASK) == MC_MODE_ROUTED) )
COPY_MACHEADER(mtd->data + mtd->offset, pmcl->dstmac, *srcMac, ethertype);
else
COPY_MACHEADER(mtd->data + mtd->offset, pmcl->dstmac, srcMac_orig, ethertype);
return res;
}
/** Clone and send a packet.
* Device-specific (C2000), should only be called by mc_clone_and_send.
* @param[in] mtd Metadata for the packet to be sent
* @param[in] mcdest Multicast destination entry to send the packet to
* @param[in] first_listener First output destination
*/
void __mc_clone_and_send(PMetadata mtd, PMCxEntry mcdest, PMCListener first_listener)
{
U32 n = mcdest->num_listeners;
U8 no_of_mc_uc_lis = mcdest->num_mc_uc_listeners;
#ifdef CFG_WIFI_OFFLOAD
U8 no_of_wifi_mc_lis = mcdest->num_wifi_mc_listeners;
#else
U8 no_of_wifi_mc_lis = 0;
#endif
U32 packet_start;
U16 *src_mac = NULL;
int i=0;
int j=0;
int first_is_vlan = 0;
real_vlan_hdr_t vlan_tag;
struct itf *itf;
class_tx_hdr_mc_t *tx_hdr;
u16 hif_hdr_len = 0;
U32 queue_mod = DSCP_to_Qmod[mtd->priv_mcast_dscp];
void *ddr_ptr=NULL;
void *ddr_start=NULL;
void *dmem_addr;
U16 orig_mtd_offset;
U16 orig_mtd_length;
int mc_uc_or_wifi_mc_lisidx=0;
U8 send_last_mc_uc_or_wifi_pkt_by_ro=FALSE;
U8 get_first_mc_lis_idx=FALSE;
u32 phy;
vlan_tag.tag = 0;
// TODO We need enough headroom for additional descriptors, otherwise send to exception path.
/* Assume all listeners will have the same L2 headers after first ETH/VLAN fields.
* This assumption is no longer required on the C1k with the use of itf structs, but still is on the C2k.
*/
if (mcdest->flags & MC_ACP_LISTENER)
{
mtd->priv_mcast_refcnt = n+1; // For C2k, priv_mcast_refcnt will be the total number of clones, i.e. one more than the C1k value.
} else
{
mtd->priv_mcast_refcnt = n; // For C2k, priv_mcast_refcnt will be the total number of clones, i.e. one more than the C1k value.
}
/* Packet is sent by RO for
1. MC listeners that are not WiFi listeners or
2. (MC-UC listener Or WiFi MC Listener when n==1) or
3. (Last MC-UC listener or WiFi MC listener when no MC non-WiFi listeners are present) */
if(no_of_mc_uc_lis + no_of_wifi_mc_lis) {
/* Condition: IF all listeners are MC-UC listeners or WiFi MC Listeners, send last one by RO if ACP is not programmed
Otherwise point to first MC listener that is not WiFi listener to send by RO */
if((no_of_mc_uc_lis + no_of_wifi_mc_lis) == n) {
if(!(mcdest->flags & MC_ACP_LISTENER)) {
if((no_of_mc_uc_lis + no_of_wifi_mc_lis) ==1)
{
goto send_by_ro;
}
i = no_of_mc_uc_lis + no_of_wifi_mc_lis -1;
send_last_mc_uc_or_wifi_pkt_by_ro = TRUE;
}
else
i=n;
}
else
get_first_mc_lis_idx = TRUE;
orig_mtd_offset = mtd->offset;
orig_mtd_length = mtd->length;
for(j=0; j < n; j++) {
/* Only process MC-UC Listeners and WiFi MC Listeners*/
if(!mcdest->listeners[j].uc_bit && !(IS_WIFI_PORT(mcdest->listeners[j].output_port))) {
if(get_first_mc_lis_idx) {
i = j;
get_first_mc_lis_idx = FALSE;
}
continue;
}
__l2_prepend_header(mtd, mcdest->listeners[j].itf, mcdest->listeners[j].dstmac, l2_get_tid(mcdest->listeners[j].family), 1);
#ifdef CFG_WIFI_OFFLOAD
if (IS_WIFI_PORT(mtd->output_port))
{
mtd->queue = queue_mod;
}
else
#endif
mtd->queue = mcdest->listeners[j].queue_base + queue_mod;
// Allocate BMU2 buffer sepcific to each listener
do
{
ddr_ptr = (void *)readl(BMU2_BASE_ADDR + BMU_ALLOC_CTRL);
} while (ddr_ptr == NULL);
dmem_addr = mtd->data + mtd->offset;
ddr_start = ddr_ptr + ddr_offset(mtd, dmem_addr);
class_mtd_copy_to_ddr(mtd, ddr_start, mtd->length, CLASS_GP_DMEM_BUF,
CLASS_GP_DMEM_BUF_SIZE);
send_to_tx_direct(mtd->output_port, mtd->queue, dmem_addr, ddr_start, 0, mtd->length, 0, mtd->flags);
/* Now let the mtd->offset & mtd->length point back to IP */
mtd->offset = orig_mtd_offset;
mtd->length = orig_mtd_length;
mtd->priv_mcast_refcnt--;
mc_uc_or_wifi_mc_lisidx++;
if(send_last_mc_uc_or_wifi_pkt_by_ro && (mc_uc_or_wifi_mc_lisidx == (no_of_mc_uc_lis + no_of_wifi_mc_lis -1)))
goto send_by_ro;
}
}
send_by_ro:
/* Restart of processing for RO Block */
if (mcdest->flags & MC_ACP_LISTENER)
{
// We need to keep the packet unmodified to send to the host, so we only update pointers without touching data.
mtd->length += mtd->offset - BaseOffset;
mtd->offset = BaseOffset;
mtd->queue = queue_mod;
mtd->output_port = get_expt_tx_output_port(mtd->input_port);
if (is_vlan(mtd)) //the hwparse structure should not be corrupt yet at this stage
first_is_vlan = 1;
hif_hdr_len = hif_tx_prepare(mtd);
packet_start = (U32)mtd->data + mtd->offset - hif_hdr_len;
}
#ifdef CFG_WIFI_OFFLOAD
else if((i < n) && (IS_WIFI_PORT(mcdest->listeners[i].output_port)))
{
mtd->queue = queue_mod;
first_is_vlan = mc_prepend_upper_header(mtd, &mcdest->listeners[i], &src_mac, &vlan_tag, mcdest->flags);
hif_hdr_len = hif_tx_prepare(mtd);
packet_start = (U32)mtd->data + mtd->offset - hif_hdr_len;
}
#endif
else
{
/* Assign queue & repl_mask for first_listener */
mtd->queue = mcdest->listeners[i].queue_base + queue_mod;
mtd->repl_msk = mcdest->listeners[i].shaper_mask;
first_is_vlan = mc_prepend_upper_header(mtd, &mcdest->listeners[i], &src_mac, &vlan_tag, mcdest->flags);
packet_start = (U32)mtd->data + mtd->offset;
}
phy = get_tx_phy(mtd->output_port);
// First clone
tx_hdr = (class_tx_hdr_mc_t *)(ALIGN64(packet_start) - sizeof(class_tx_hdr_mc_t));
tx_hdr->start_data_off = packet_start - ((U32) tx_hdr);
/* FIXME this only works if lmem_hdr_size == ddr_hdr_size, otherwise we need to add (ddr_hdr_size - lmem_hdr_size) */
/* (src_addr & 0xff) is the offset from the 256 byte aligned packet slot */
tx_hdr->start_buf_off = ((U32) tx_hdr) & 0xff;
tx_hdr->queueno = mtd->queue;
tx_hdr->act_phyno = (0 << 4) | (phy & 0xfU);
if ((mcdest->flags & MC_ACP_LISTENER)
#ifdef CFG_WIFI_OFFLOAD
|| ((i < n) && (IS_WIFI_PORT(mcdest->listeners[i].output_port)))
#endif
)
{
tx_hdr->pkt_length = mtd->length + hif_hdr_len;
/* Now that we created the first clone, correct offset for the following clones that
* do not need the ACP metadata.
*/
packet_start += hif_hdr_len;
/* If WiFi Listener is processed as first clone, point to next listener */
if(!(mcdest->flags & MC_ACP_LISTENER))
i++;
}
else
{
tx_hdr->pkt_length = mtd->length;
if ((mcdest->flags & MC_MODE_MASK) == MC_MODE_ROUTED) {
tx_hdr->act_phyno |= ACT_SRC_MAC_REPLACE;
//src_mac already updated by mc_prepend_upper_header
tx_hdr->src_mac_msb = src_mac[0];
tx_hdr->src_mac_lsb = (src_mac[1] << 16) + src_mac[2];
}
i++;
}
tx_hdr->vlanid = vlan_tag.tag;
if (vlan_tag.tag)
{
tx_hdr->act_phyno |= ACT_VLAN_ADD;
}
/*Skip this tx_hdr if physical port tx is not enabled */
if((mtd->output_port < GEM_PORTS) && (!IS_TX_ENABLED(mtd->output_port)) &&
(!(mcdest->flags & MC_ACP_LISTENER))) {
mtd->priv_mcast_refcnt--;
PESTATUS_INCR_DROP();
drop_counter[CLASS_DROP_TX_DISABLE]++;
tx_hdr++;
}
for(;i < n;i++)
{
if(mcdest->listeners[i].uc_bit || (IS_WIFI_PORT(mcdest->listeners[i].output_port)))
continue;
tx_hdr--;
tx_hdr->start_data_off = packet_start - (U32) tx_hdr;
tx_hdr->start_buf_off = ((U32) tx_hdr) & 0xff;
tx_hdr->pkt_length = mtd->length;
tx_hdr->act_phyno = 0;
// Shaper mask can be specified per listener
//mtd->repl_msk = mcdest->listeners[i].shaper_mask; /* FIXME. Where is repl_msk used in PFE */
// Now get to the bottom of the itf list to retrieve MAC, output port, and VLAN (if present)
itf = mcdest->listeners[i].itf;
vlan_tag.tag = 0;
src_mac = NULL;
while (itf->phys)
{
#ifdef CFG_MACVLAN
if (itf->type & IF_TYPE_MACVLAN)
src_mac = ((PMacvlanEntry)itf)->MACaddr;
else
#endif
if (itf->type == IF_TYPE_VLAN)
{
if (vlan_tag.tag) // This means we're in QinQ mode, so only solution is to add the 2nd tag
tx_hdr->act_phyno |= ACT_VLAN_ADD;
fill_real_vlan_header(&vlan_tag, ETHERTYPE_VLAN_END, ((VlanEntry *)itf)->VlanId, mtd->vlan_pbits);
}
itf = itf->phys;
}
tx_hdr->vlanid = vlan_tag.tag;
if (vlan_tag.tag)
{
if ((tx_hdr->act_phyno & ACT_VLAN_ADD) == 0) // We're not in QinQ mode
{
if (first_is_vlan)
tx_hdr->act_phyno |= ACT_VLAN_REPLACE;
else
tx_hdr->act_phyno |= ACT_VLAN_ADD;
}
}
phy = (((struct physical_port *)itf)->id & 0xfU);
tx_hdr->act_phyno |= __get_tx_phy(phy);
tx_hdr->queueno = mcdest->listeners[i].queue_base + queue_mod; // the queue number can be specified per listener
if (!src_mac)
src_mac = (U16 *) ((struct physical_port *)itf)->mac_addr;
if ((mcdest->flags & MC_MODE_MASK) == MC_MODE_ROUTED) {
tx_hdr->act_phyno |= ACT_SRC_MAC_REPLACE;
tx_hdr->src_mac_msb = src_mac[0];
tx_hdr->src_mac_lsb = (src_mac[1] << 16) + src_mac[2];
}
if (first_is_vlan && !vlan_tag.tag) {
PRINTF("MCAST entry not supported, skipping\n");
tx_hdr++;
mtd->priv_mcast_refcnt--;
}
/*Skip this tx_hdr if physical port tx is not enabled */
if((phy < GEM_PORTS) && (!IS_TX_ENABLED(phy))) {
tx_hdr++;
mtd->priv_mcast_refcnt--;
PESTATUS_INCR_DROP();
drop_counter[CLASS_DROP_TX_DISABLE]++;
}
}
// mtd->priv_mcast_refcnt now holds number of copies to be made by RO.
if( mtd->priv_mcast_refcnt ) {
send_to_tx_mcast(mtd, tx_hdr);
}else {
__free_packet(mtd);
}
}
#endif /* COMCERTO_2000_CLASS */
#if defined(COMCERTO_2000_CLASS)||defined(COMCERTO_100)||defined(COMCERTO_1000)
/** Clone a packet and send copies to the relevant output interfaces.
* The actual cloning operation is heavily device-dependent, so that function simply calls
* __mc_clone_and_send after setting up common fields in the metadata structure.
* @param[in] mtd Metadata for the packet to be sent
* @param[in] mcdest Multicast destination entry to send the packet to
*/
void mc_clone_and_send(PMetadata mtd, PMCxEntry mcdest)
{
PMCListener first_listener;
mtd->priv_mcast_flags = 0;
first_listener = &mcdest->listeners[0];
if ((mcdest->flags & MC_MODE_BRIDGED) != 0)
mtd->priv_mcast_flags |= MC_BRIDGED;
__mc_clone_and_send(mtd, mcdest, first_listener);
}
#endif
#ifdef COMCERTO_1000
extern unsigned char Image$$aram_mdma0$$Length[];
extern unsigned char Image$$aram_mdma0$$ZI$$Length[];
extern unsigned char Image$$aram_mdma0$$Base[];
#define MDMA0_DEV_MEMSIZE ((U32)Image$$aram_mdma0$$Length + (U32)Image$$aram_mdma0$$ZI$$Length)
#define MDMA0_DEV_MEMADDR Image$$aram_mdma0$$Base
int multicast_init(void)
{
static BOOL mc_init_done = FALSE;
U32 tmp;
mdma_lane *pcntx;
MMRXdesc tmp_dsc,*prxdsc;
if (mc_init_done)
return 0;
if (( MDMA0_TX_NUMDESCR * sizeof(MMTXdesc) + MDMA0_RX_NUMDESCR *sizeof(MMRXdesc)) > MDMA0_DEV_MEMSIZE )
return 1; // Not enough memory should probably hang a chip here.
// Fill control structure in software.
pcntx= &mdma_lane0_cntx;
memset(pcntx,0,sizeof(mdma_lane0_cntx));
tmp = (U32) MDMA0_DEV_MEMADDR;
pcntx->tx_cur = pcntx->tx_top = (void*) tmp;
pcntx->tx_bot = pcntx->tx_top + (MDMA0_TX_NUMDESCR - 1);
pcntx->free_tx = MDMA0_TX_NUMDESCR;
memset( pcntx->tx_top, 0, MDMA0_TX_NUMDESCR * sizeof(MMTXdesc));
tmp += MDMA0_TX_NUMDESCR * sizeof(MMTXdesc);
pcntx->rx_cur = pcntx->rx_top = (void*) tmp;
pcntx->rx_bot = pcntx->rx_top + (MDMA0_RX_NUMDESCR - 1);
pcntx->pending_rx = 0;
prxdsc=pcntx->rx_cur;
// Initialise rx rescriptors
memset(&tmp_dsc,0,sizeof(tmp_dsc));
tmp_dsc.rxctl = ( MMRX_BUF_ALLOC | ((BaseOffset + ETH_HDR_SIZE)<< MMRX_OFFSET_SHIFT));
do {
SFL_memcpy(prxdsc, &tmp_dsc, sizeof(*prxdsc));
prxdsc +=1;
} while(prxdsc != pcntx->rx_bot);
tmp_dsc.rxctl |= MMRX_WRAP;
SFL_memcpy(pcntx->rx_bot, &tmp_dsc,sizeof(*prxdsc));
// hw bindings
pcntx->txcnt = (V32*) MDMA_TX0_PATH_CTR;
pcntx->rxcnt = (V32*) MDMA_RX0_PATH_CTR;
// Enable mdma block and lane0
*(V32*) MDMA_GLB_CTRL_CFG = 1;
*(V32*) MDMA_TX0_PATH_HEAD = (U32) pcntx->tx_top;
*(V32*) MDMA_RX0_PATH_HEAD = (U32) pcntx->rx_top;
*(V32*) MDMA_INT_MASK_REG &= ~0x33UL; // turn off interrupts from lane0
//TODO mat-20081017 - is burst length needed?
// default is 0x10
// *(V32*) MDMA_AHB_CFG = (*(V32*)MDMA_AHB_CFG & ~0x1f) | TBD
// I am purposly not touching lane1
// It is safe as long work is not queued to it.
mc_init_done = TRUE;
return 0;
}
#endif /* COMCERTO_1000 */