blob: c2c4fe04bacfea4fc4d3fca4e6cd6cd659443ed9 [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.
*
*
*/
/* $id$ */
#ifndef _MULTICAST_H_
#define _MULTICAST_H_
#include "system.h"
#include "fpp.h"
#include "module_expt.h"
#include "module_qm.h"
#ifdef COMCERTO_1000
#include "xdma.h"
#endif
#include "channels.h"
#if defined(COMCERTO_1000)||defined(COMCERTO_2000_CLASS)
#include "layer2.h"
#endif
#include "module_ipv4.h"
#include "module_ipv6.h"
#ifdef COMCERTO_2000_CLASS
#include "module_tx.h"
#include "pfe/class.h"
#endif
#define MCx_MAX_LISTENERS_PER_GROUP 10
#define MCx_MAX_MC_LISTENERS_PER_GROUP 4
#define MCx_MAX_LISTENERS_IN_QUERY 5
#define MC_MODE_BRIDGED 0x0001
#define MC_MODE_ROUTED 0x0000
#define MC_MODE_MASK 0x0001
#define MC_ACP_LISTENER 0x2
#define MC4_MAC_CHECKER 0x0001005E // in big-endian format
#define MC4_MAC_DEST_MARKER1 ntohs(0x0100)
#define MC4_MAC_DEST_MARKER2 ntohs(0x5E00)
#define MC6_MAC_DEST_MARKER 0x3333
#if defined(COMCERTO_2000)
#define HASH_ENTRY_VALID (1 << 0)
#define HASH_ENTRY_USED (1 << 1)
#define HASH_ENTRY_UPDATING (1 << 2)
#endif
#ifdef COMCERTO_100
typedef struct _tMCListener {
//Make L2 header aligned the same way as BaseOffset
#if ((BaseOffset & 0x3) == 0)
U8 L2_header[ETH_HDR_SIZE + VLAN_HDR_SIZE];
U8 L2_header_size;
U8 output_index;
#else
U8 L2_header_size;
U8 output_index;
U8 L2_header[ETH_HDR_SIZE + VLAN_HDR_SIZE];
#endif
int timer;
U8 output_port;
U8 shaper_mask;
} __attribute__((aligned(4))) MCListener, *PMCListener, *PMC4Listener, *PMC6Listener;
#else
typedef struct _tMCListener {
struct itf *itf;
int timer;
U8 output_index;
U8 output_port;
U8 shaper_mask;
U8 family;
U8 uc_bit;
#ifdef ENDIAN_LITTLE
U8 queue_base:5,
q_bit:1,
rsvd:2;
#else
U8 rsvd:2,
q_bit:1,
queue_base:5;
#endif
U8 dstmac[6];
} __attribute__((aligned(4))) MCListener, *PMCListener, *PMC4Listener, *PMC6Listener;
#endif
typedef struct _tMCxEntry {
U8 src_mask_len;
U8 num_listeners;
U8 num_mc_uc_listeners; /* Number of MC UC Listeners */
#ifdef CFG_WIFI_OFFLOAD
U8 num_wifi_mc_listeners; /* Number of WiFi MC Listeners (Doesn't include ACP) */
#endif
U8 queue_base;
U8 flags;
#ifdef CFG_WIFI_OFFLOAD
U8 padding[6];
#else
U8 padding[7];
#endif
int wifi_listener_timer;
MCListener listeners[MCx_MAX_LISTENERS_PER_GROUP];
}MCxEntry, *PMCxEntry;
#ifdef COMCERTO_1000
int multicast_init(void);
#endif
extern void class_mtd_copy_to_ddr(PMetadata mtd, void *ddr_start, u32 len, void *dmem_buf, unsigned int dmem_len);
static __inline void mc_to_expt(PMetadata mtd)
{
if ((mtd->flags & MTD_MC6) && IS_ICMPv6(mtd->data + mtd->offset))
mtd->queue = EXPT_ARP_QUEUE;
else
mtd->queue = DSCP_to_Q[mtd->priv_mcast_dscp];
SEND_TO(EXPT, mtd);
}
/** Computes a multicast MAC address based on the IP (v4 or v6) header.
* Only assumption is that variables are 16-bit aligned.
* C2k PFE class version differs in the handling of endianness in the IPv4 case,
* and is also a bit optimized for the PE CPU (this is the reason it doesn't use
* READ_UNALIGNED_INT/WRITE_UNALIGNED_INT macros).
* @param[out] dstmac Pointer to destination buffer
* @param[in] iphdr IP (v4 or v6) header to be used to generate the MAC address.
* @param[in] family Either PROTO_IPV4 or PROTO_IPV6, depending on IP version.
*/
#if !defined(COMCERTO_2000_CLASS)
static __inline void mc_mac_from_ip(U8 *dstmac, void *iphdr, U8 family)
{
if (family == PROTO_IPV4)
{
U32 da = READ_UNALIGNED_INT(((ipv4_hdr_t *) iphdr)->DestinationAddress);
*(U16 *)dstmac = MC4_MAC_DEST_MARKER1;
__WRITE_UNALIGNED_INT(dstmac + 2, MC4_MAC_DEST_MARKER2 | (da & 0xFFFF7F00));
} else
{
U32 da_lo = READ_UNALIGNED_INT(((ipv6_hdr_t *) iphdr)->DestinationAddress[IP6_LO_ADDR]);
*(U16 *)dstmac = MC6_MAC_DEST_MARKER;
__WRITE_UNALIGNED_INT(dstmac + 2, da_lo);
}
}
#endif
#ifdef COMCERTO_100
void mc_prepend_header(PMetadata mtd, PMCListener pmcl)
{
int l2len;
U8 *p;
l2len = pmcl->L2_header_size;
mtd->offset -= l2len;
mtd->length += l2len;
p = mtd->data + mtd->offset;
mtd->output_port = pmcl->output_port;
#if ((BaseOffset & 0x3) == 0)
if (mtd->priv_mcast_flags & MC_BRIDGED)
{
// need to modify L2_header to reflect unmodified src mac
*(U16*)(pmcl->L2_header + 6) = *(U16*)(mtd->data + BaseOffset + 6);
*(U32*)(pmcl->L2_header + 8) = *(U32*)(mtd->data + BaseOffset + 8);
}
*(U32*)p = *(U32*)&pmcl->L2_header[0];
*(U32*)(p + 4) = *(U32*)&pmcl->L2_header[4];
*(U32*)(p + 8) = *(U32*)&pmcl->L2_header[8];
if (l2len == ETH_HDR_SIZE) {
*(U16*)(p + 12) = *(U16*)&pmcl->L2_header[12];
} else {
*(U32*)(p + 12) = *(U32*)&pmcl->L2_header[12];
*(U16*)(p + 16) = *(U16*)&pmcl->L2_header[16];
}
#else
if (mtd->priv_mcast_flags & MC_BRIDGED)
{
// need to modify L2_header to reflect unmodified src mac
*(U32*)(pmcl->L2_header + 6) = *(U32*)(mtd->data + BaseOffset + 6);
*(U16*)(pmcl->L2_header + 10) = *(U16*)(mtd->data + BaseOffset + 10);
}
if (l2len == ETH_HDR_SIZE) {
copy16(p,&pmcl->L2_header[0]);
} else {
copy20(p,&pmcl->L2_header[0]);
}
#endif
}
/** Clone and send a packet.
* Device-specific (C100), 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)
{
PMCListener this_listener;
PMetadata mtd_new;
U32 num_copies;
U32 n = mcdest->num_listeners;
int i;
if (n == 1 && (mcdest->flags & MC_ACP_LISTENER == 0))
{
// here if just one listener (and no wifi)
mc_prepend_header(mtd, (PMCListener) first_listener);
send_to_tx(mtd);
return;
}
num_copies = 1;
// have to replicate
// prepare mtd properties to copy to clones.
mtd->flags |= MTD_MULTICAST_TX;
if (mcdest->flags & MC_ACP_LISTENER)
{
if ((mtd_new = mtd_alloc()) != NULL) {
*mtd_new = *mtd;
num_copies++;
mc_to_expt(mtd_new);
}
}
for (i = 0; i < n - 1; i++)
{
this_listener = &mcdest->listeners[i];
if ((mtd_new = mtd_alloc()) != NULL) {
*mtd_new = *mtd;
if (mcdest->flags & MC_MODE_BRIDGED) {
// need to modify L2_header to reflect unmodified src mac
*(U16*)(this_listener->L2_header + 6) = *(U16*)(mtd->data + BaseOffset + 6);
*(U32*)(this_listener->L2_header + 8) = *(U32*)(mtd->data + BaseOffset + 8);
L1_dc_clean(this_listener->L2_header,this_listener->L2_header+sizeof(this_listener->L2_header)-1);
}
mtd_new->priv.dword =(U64) this_listener->L2_header | (( (U64) this_listener->L2_header_size)<< 32);
mtd_new->output_port = this_listener->output_port;
mtd_new->repl_msk = this_listener->shaper_mask;
send_to_tx(mtd_new);
num_copies++;
}
else {
break;
}
}
// last packet left - use original mtd.
this_listener = &mcdest->listeners[i];
if (mcdest->flags & MC_MODE_BRIDGED) {
// need to modify L2_header to reflect unmodified src mac
*(U16*)(this_listener->L2_header + 6) = *(U16*)(mtd->data + BaseOffset + 6);
*(U32*)(this_listener->L2_header + 8) = *(U32*)(mtd->data + BaseOffset + 8);
L1_dc_clean(this_listener->L2_header,this_listener->L2_header+sizeof(this_listener->L2_header)-1);
}
mtd->priv.dword =(U64) this_listener->L2_header | (( (U64) this_listener->L2_header_size)<< 32);
mtd->output_port = this_listener->output_port;
mtd->repl_msk = this_listener->shaper_mask;
send_to_tx(mtd);
// save reference count in the data buffer
*(U32*)(mtd->data+MC_REFCOUNT_OFFSET) = num_copies;
}
#endif
#ifdef COMCERTO_1000
void mc_prepend_header(PMetadata mtd, PMCListener pmcl)
{
RouteEntry RtEntry;
mc_mac_from_ip(RtEntry.dstmac, mtd->data+mtd->offset, pmcl->family);
RtEntry.itf = pmcl->itf;
l2_prepend_header(mtd, &RtEntry, (U16) pmcl->family);
}
//
// MDMA lane descriptor
typedef struct _mlane {
V32 *txcnt;
V32 *rxcnt;
MMTXdesc *tx_top;
MMTXdesc *tx_bot;
MMTXdesc *tx_cur;
U32 free_tx;
MMRXdesc *rx_top;
MMRXdesc *rx_bot;
MMRXdesc *rx_cur;
U32 pending_rx;
} mdma_lane;
#if defined(SIM)
#define MDMA0_TX_NUMDESCR 8
#else
#define MDMA0_TX_NUMDESCR 48
#endif
#define MDMA0_RX_NUMDESCR ((MCx_MAX_LISTENERS_PER_GROUP-1) *MDMA0_TX_NUMDESCR)
extern mdma_lane mdma_lane0_cntx;
static __inline int mdma_request_copy(PMetadata mtd, int num_copies, PMCxEntry listeners)
{
// returns number of copies requested by mdma
int copies_requested = 0;
#if !defined(COMCERTO_2000)
U64 tmpU64;
if (num_copies == 0)
return 0;
mdma_lane *pcntx = &mdma_lane0_cntx;
if (pcntx->free_tx)
{
copies_requested = MDMA0_RX_NUMDESCR - pcntx->pending_rx;
if (copies_requested > num_copies)
copies_requested = num_copies;
if (copies_requested > 0)
{
// Some copies will be made. Need to flush d-cache in the "copy from" buffer.
// Only ttl is modified - can do a small flush.
// 7 is an offset to last byte of ipv6.HopLimit
// 11 is offset into ipv4.checksum
L1_dc_clean(mtd->data+mtd->offset,mtd->data+mtd->offset+11);
// Cache will be invalidated in module_tx or free_packet
tmpU64 = (((U64) mtd) << 32) | ((U32) listeners);
Write64(tmpU64, ((U64*) pcntx->tx_cur)+1);
tmpU64 =(U64) (mtd->data+mtd->offset);
tmpU64 += (((U64)mtd->length<<MMTX_LENGTH_SHIFT) | ((U64)copies_requested<< MMTX_NUMCPY_SHIFT) | ((U64)mtd->offset << MMTX_OFFSET_SHIFT ))<< 32;
if (pcntx->tx_cur == pcntx->tx_bot)
{
tmpU64 += ((U64) MMTX_WRAP ) << 32;
Write64(tmpU64, (U64*) pcntx->tx_cur);
pcntx->tx_cur = pcntx->tx_top;
}
else
{
Write64(tmpU64, (U64*) pcntx->tx_cur);
pcntx->tx_cur += 1;
}
*pcntx->txcnt = 1;
*pcntx->rxcnt = copies_requested;
pcntx->pending_rx += copies_requested;
pcntx->free_tx -= 1;
set_event(gEventStatusReg, (1 << EVENT_MC6));
}
}
#endif
return copies_requested;
}
static __inline int multicast_poola_hwm() {
// report max number of poolA buffers held in mdma_rx
return MDMA0_RX_NUMDESCR;
}
/** Clone and send a packet.
* Device-specific (C1000), 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
*/
static __inline void __mc_clone_and_send(PMetadata mtd, PMCxEntry mcdest, PMCListener first_listener)
{
U32 num_copies;
U32 n = mcdest->num_listeners;
// clone using mdma
if (mcdest->flags & MC_ACP_LISTENER) {
// At the end mtd will be redirected to exception path
mtd->priv_mcast_flags |= MC_LASTMTD_WIFI;
} else {
// At the end mtd will be used for last lan transmit
n -= 1;
}
if ((num_copies = mdma_request_copy(mtd, n, mcdest)) > 0) {
mtd->priv_mcast_refcnt = num_copies;
mtd->priv_mcast_rxndx = 0;
}
else
{
// here if just one listener, or mdma request failed
mc_prepend_header(mtd, (PMCListener)first_listener);
#ifdef CFG_WIFI_OFFLOAD
if (IS_WIFI_PORT(first_listener->output_port))
// DSCP_to_Qmod could also be considered here, but since we are no longer doing full Wifi offload,
// the packet will go to the exception path and so DSCP_to_Q makes more sense.
mtd->queue = DSCP_to_Q[mtd->priv_mcast_dscp];
#endif
send_to_tx(mtd);
}
}
#endif // COMCERTO_1000
#ifdef COMCERTO_2000_CLASS
/** Fill in a VLAN header structure based on given field values.
* @param[out] pvlanHdr pointer to VLAN header structure to fill in.
* @param[in] ethertype Ethertype to use, in network endianness.
* @param[in] VlanId VLAN id, in network endianness.
* @param[in] vlan_pbits VLAN priority bits (in bits [2:0]).
* We can't use the vlan_hdr_t structure as it (purposefully) doesn't match the standard 802.1Q format.
*/
static inline void fill_real_vlan_header(real_vlan_hdr_t *pvlanHdr, U16 ethertype, U16 VlanId, U8 vlan_pbits)
{
pvlanHdr->TPID = ethertype;
pvlanHdr->TCI = VlanId | htons(vlan_pbits << 13);
}
U32 mc_prepend_upper_header(PMetadata mtd, PMCListener pmcl, U16 **srcMac, real_vlan_hdr_t *vlan_tag, U8 flags);
void __mc_clone_and_send(PMetadata mtd, PMCxEntry mcdest, PMCListener first_listener);
/* TODO this is the length of the metadata added to packets towards the exception path
* so that the HIF driver on the host can determine how to handle the packet
* (wifi offload, MSP, IPsec, etc).
*/
#define ACP_MTD_LEN 8
#endif
#if defined(COMCERTO_2000_CLASS)||defined(COMCERTO_100)||defined(COMCERTO_1000)
void mc_clone_and_send(PMetadata mtd, PMCxEntry mcdest);
#endif
#endif /* _MULTICAST_H_ */