/*
 * Copyright (c) 2012, 2016, The Linux Foundation. All rights reserved.
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all copies.
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */



#include "fal_nat.h"
#include "fal_ip.h"
#include "hsl_api.h"
#include "sw.h"
#include "hsl.h"
#include "hsl_dev.h"
#include "hsl_port_prop.h"
#include "isisc_igmp.h"
#include "isisc_reg.h"
#include "isisc_acl.h"
#include "fal_multi.h"
#include "sal/os/aos_lock.h"

#if 0
/**
 * I/F prototype for complete igmpv3 & mldv2 support
 */

/*supports 32 entries*/
#define FAL_IGMP_SG_ENTRY_MAX 32

typedef enum
{
    FAL_ADDR_IPV4 = 0,
    FAL_ADDR_IPV6
} fal_addr_type_t;

typedef struct
{
    fal_addr_type_t type;
    union
    {
        fal_ip4_addr_t ip4_addr;
        fal_ip6_addr_t ip6_addr;
    } u;
} fal_igmp_sg_addr_t;

typedef struct
{
    fal_igmp_sg_addr_t source;
    fal_igmp_sg_addr_t group;
    fal_pbmp_t port_map;
} fal_igmp_sg_entry_t;

/**
 * @brief set PortMap of IGMP sg entry.
 *        search entry according to source/group address,
 *        update PortMap if SG entry is found, otherwise create a new sg entry.
 * @param[in] dev_id device id
 * @param[in-out] entry SG entry
 * @return SW_OK or error code
 */
HSL_LOCAL sw_error_t
isisc_igmp_sg_entry_set(a_uint32_t dev_id, fal_igmp_sg_entry_t * entry);

/**
 * @brief clear PortMap of IGMP sg entry.
 *        search entry according to source/group address,
 *        update PortMap if SG entry is found, delete the entry in case PortMap was 0.
 *        SW_NOT_FOUND will be returned in case search failed.
 * @param[in] dev_id device id
 * @param[in-out] entry SG entry
 * @return SW_OK or error code
 */
HSL_LOCAL sw_error_t
isisc_igmp_sg_entry_clear(a_uint32_t dev_id, fal_igmp_sg_entry_t * entry);

#define MULTI_DEBUG_
#ifdef MULTI_DEBUG_
#define MULTI_DEBUG(x...) aos_printk(x)
#else
#define MULTI_DEBUG(x...)
#endif

#define FAL_ACL_LIST_MULTICAST 55
#define FAL_MULTICAST_PRI   5

#define MULT_ACTION_SET      1
#define MULT_ACTION_CLEAR    1

static a_uint32_t rule_nr=1;

typedef struct
{
    a_uint8_t index; //MAX is 32
    fal_igmp_sg_entry_t entry; //Stores the specific ACL rule info
} multi_acl_info_t;
#endif

static a_uint32_t mul_rule_nr=1;

void
isisc_multicast_init(a_uint32_t dev_id);

HSL_LOCAL sw_error_t multi_portmap_aclreg_set(a_uint32_t dev_id, a_uint32_t pos, fal_igmp_sg_entry_t * entry);

static multi_acl_info_t multi_acl_info[FAL_IGMP_SG_ENTRY_MAX];
static multi_acl_info_t multi_acl_group[FAL_IGMP_SG_ENTRY_MAX];

static int ip6_addr_is_null(fal_ip6_addr_t *ip6)
{
    if (NULL == ip6)
    {
        aos_printk("Invalid ip6 address\n");
        return -1;
    }
    if(0 == ip6->ul[0] && 0 == ip6->ul[1] && 0 == ip6->ul[2] && 0 == ip6->ul[3])
        return 1;
    else
        return 0;
}
static int multi_source_is_null(fal_igmp_sg_addr_t *s)
{
    if (NULL == s)
    {
        aos_printk("Invalid source address\n");
        return -1;
    }
    if(0 == s->type && 0==s->u.ip4_addr)
        return 1;
    if(1 == s->type && 1 == ip6_addr_is_null(&(s->u.ip6_addr)))
        return 1;

    return 0;
}

HSL_LOCAL int iterate_multicast_acl_rule(a_uint32_t dev_id, int list_id, int start_n)
{
    a_uint32_t rule_id;
    sw_error_t ret;
    fal_acl_rule_t  rule= {0};

    if(start_n>=FAL_IGMP_SG_ENTRY_MAX || start_n < 0)
    {
        return -1;
    }

    for(rule_id=0; rule_id<FAL_IGMP_SG_ENTRY_MAX; rule_id++)
    {
        ret = isisc_acl_rule_query(dev_id, list_id, rule_id, &rule);

        if (ret==SW_NOT_FOUND )
            break;//NOT found in ACL rule
        if((rule_id+start_n)>=FAL_IGMP_SG_ENTRY_MAX)
        {
            return -1;
        }

        multi_acl_info[rule_id+start_n].index = rule_id; // consider here... index is NOT related start_n
        //MULTI_DEBUG("normal query1: rule dest_ip4_val=%x, src ip4=%x, dst_ip6=%x, ports=%x\n",
        //rule.dest_ip4_val, rule.src_ip4_val, rule.dest_ip6_val.ul[0], rule.ports);

        if(rule.dest_ip4_val !=0 && ip6_addr_is_null(&rule.dest_ip6_val))  //only ip4
        {
            multi_acl_info[rule_id+start_n].entry.group.type = FAL_ADDR_IPV4;
            multi_acl_info[rule_id+start_n].entry.source.type = FAL_ADDR_IPV4;
            multi_acl_info[rule_id+start_n].entry.group.u.ip4_addr = rule.dest_ip4_val;
            multi_acl_info[rule_id+start_n].entry.source.u.ip4_addr = rule.src_ip4_val;
            multi_acl_info[rule_id+start_n].entry.port_map= rule.ports;
        }
        else if(rule.dest_ip4_val ==0 && !ip6_addr_is_null(&rule.dest_ip6_val))  //only ip6
        {
            multi_acl_info[rule_id+start_n].entry.group.type = FAL_ADDR_IPV6;
            multi_acl_info[rule_id+start_n].entry.source.type = FAL_ADDR_IPV6;
            memcpy(&(multi_acl_info[rule_id+start_n].entry.group.u.ip6_addr), &(rule.dest_ip6_val), sizeof(rule.dest_ip6_val));
            memcpy(&(multi_acl_info[rule_id+start_n].entry.source.u.ip6_addr), &(rule.src_ip6_val), sizeof(rule.src_ip6_val));
            multi_acl_info[rule_id+start_n].entry.port_map= rule.ports;
        }
        if (FAL_FIELD_FLG_TST(rule.field_flg, FAL_ACL_FIELD_MAC_VID))
        {
            multi_acl_info[rule_id+start_n].entry.vlan_id = rule.vid_val;
        }
        else
        {
            multi_acl_info[rule_id+start_n].entry.vlan_id = 0xffff;
        }
    }

    return rule_id+start_n;
}
/*
** Iterate the total 32 multicast ACL entries.
    After the function completes:
         1. Stores all multicast related ACL rules in multi_acl_info[32]
         2. return the number of multicast related ACL rules
*/
HSL_LOCAL a_uint32_t isisc_multicast_acl_query(a_uint32_t dev_id)
{
    int start_n;
    int total_n;
    //a_uint32_t i;

    start_n = iterate_multicast_acl_rule(dev_id, FAL_ACL_LIST_MULTICAST, 0);
    if(-1 == start_n)
        aos_printk("ACL rule1 is FULL\n");
    total_n = iterate_multicast_acl_rule(dev_id, FAL_ACL_LIST_MULTICAST+1, start_n);
    if(-1 == total_n)
        aos_printk("ACL rule2 is FULL\n");

    MULTI_DEBUG("KKK, the total ACL rule number is %d, (G,S) number=%d\n", total_n, start_n);
    /*
    for(i=0;i<total_n;i++)
    MULTI_DEBUG("KKK, indx=%d, multi_acl_info[%d].entry=[%d][%x]\n", multi_acl_info[i].index,i,
        multi_acl_info[i].entry.group.type, multi_acl_info[i].entry.group.u.ip4_addr );
        */

    return total_n;
}

HSL_LOCAL a_uint32_t isisc_multicast_acl_total_n(a_uint32_t dev_id, a_uint32_t list_id)
{
    a_uint32_t ret;
    a_uint32_t rule_id;
    fal_acl_rule_t  rule= {0};

    for(rule_id=0; rule_id<FAL_IGMP_SG_ENTRY_MAX; rule_id++)
    {
        ret = isisc_acl_rule_query(dev_id, list_id,
                                   rule_id, &rule);
        if(ret==SW_NOT_FOUND)
            return rule_id;

    }
    return 0;
}

#define ISISC_FILTER_ACT_ADDR    0x5a000
#define ISISC_FILTER_MSK_ADDR    0x59000
HSL_LOCAL sw_error_t multi_portmap_aclreg_set_all(a_uint32_t dev_id, a_uint32_t pos, fal_igmp_sg_entry_t * entry)
{
    a_uint32_t i, base, addr;
    a_uint32_t msk_valid=0;
    sw_error_t rv = SW_OK;

    /*  2'b00:start; 2'b01:continue; 2'b10:end; 2'b11:start&end*/
    for(i=pos; i<pos+4; i++)
    {
        base = ISISC_FILTER_MSK_ADDR +(i<<5);
        addr = base+(4<<2); //fifth byte
        HSL_REG_ENTRY_GEN_GET(rv, dev_id, addr, sizeof (a_uint32_t),
                              (a_uint8_t *) (&msk_valid),
                              sizeof (a_uint32_t));

        SW_RTN_ON_ERROR(rv);
        if ((((msk_valid>>6)&0x3) == 0x3) || (((msk_valid>>6)&0x3) == 0x2))
        {
            rv = multi_portmap_aclreg_set(dev_id, i, entry);
            break;
        }
        else if ((((msk_valid>>6)&0x3)) == 0x0 || (((msk_valid>>6)&0x3) == 0x1))
        {
            rv = multi_portmap_aclreg_set(dev_id, i, entry);
            continue;
        }
        else
        {
            aos_printk("The rule valid bit:6 7 is wrong!!!");
            break;
        }
    }
    return rv;
}
HSL_LOCAL sw_error_t multi_portmap_aclreg_set(a_uint32_t dev_id, a_uint32_t pos, fal_igmp_sg_entry_t * entry)
{
    a_uint32_t i, base, addr;
    sw_error_t rv;
    a_uint32_t act[3]= {0};
    fal_pbmp_t pm;

    pm = entry->port_map;

    base = ISISC_FILTER_ACT_ADDR + (pos << 4);
    for (i = 0; i < 3; i++)
    {
        addr = base + (i << 2);
        HSL_REG_ENTRY_GEN_GET(rv, dev_id, addr, sizeof (a_uint32_t),
                              (a_uint8_t *) (&act[i]),
                              sizeof (a_uint32_t));
        //MULTI_DEBUG("2:Get register value  0x%x =%x\n", addr, act[i]);
        SW_RTN_ON_ERROR(rv);
    }

    act[1] &= ~(0x7<<29); // clear the high 3 bits
    act[1] |= (pm&0x7)<<29;  //the low 3 bits of pm means redirect port 0,1,2

    /* New modification: update acl ACTION register from DENY to redirect */
    if (((act[2]>>6)&0x7) == 0x7) //DENY mode
    {
        if(pm)
        {
            act[2] &= ~(0x7<<6);//clear DENY bits
            act[2] |= (0x1<<4); //DES_PORT_EN set 1, enable
        }
    }
    else if (((act[2]>>4)&0x1) == 0x1) //redirect mode
    {
        if(pm==0)
        {
            act[2] &= ~(0x1<<4);//clear redirect bits
            act[2] |= (0x7<<6); //set to DENY
        }
    }

    act[2] &= ~0xf; //clear the low 4 bits of port 3,4,5,6
    act[2] |= (pm>>3)&0xf;

    addr = base + (1<<2);
    HSL_REG_ENTRY_GEN_SET(rv, dev_id, addr, sizeof (a_uint32_t),
                          (a_uint8_t *) (&act[1]), sizeof (a_uint32_t));
    addr = base + (2<<2);
    HSL_REG_ENTRY_GEN_SET(rv, dev_id, addr, sizeof (a_uint32_t),
                          (a_uint8_t *) (&act[2]), sizeof (a_uint32_t));
    MULTI_DEBUG("pos=%d, before sync portmap, the new act=%x %x\n", pos, act[1],act[2]);
    if((rv = isisc_acl_rule_sync_multi_portmap(dev_id, pos, act)) < 0)
        aos_printk("Sync multicast portmap error\n");
    return rv;
}

HSL_LOCAL int multi_get_dp(a_uint32_t dev_id)
{
    a_uint32_t addr;
    sw_error_t rv;
    int val=0;

    addr = 0x624;//GLOBAL_FW_CTRL1
    HSL_REG_ENTRY_GEN_GET(rv, dev_id, addr, sizeof (a_uint32_t),
                          (a_uint8_t *) (&val),
                          sizeof (a_uint32_t));
    if (rv != SW_OK)
        aos_printk("Get entry value error\n");

    val = (val>>24)&0x7f; //30:24, IGMP_JOIN_LEAVE_DP

    return val;
}
static int old_bind_p=-1;
HSL_LOCAL int multi_acl_bind(a_uint32_t dev_id)
{
    int bind_p;
    int i;

    bind_p = multi_get_dp(dev_id);
    if(bind_p == old_bind_p)
        return 0;
    old_bind_p = bind_p;

    for(i=0; i<7; i++)
    {
        isisc_acl_list_unbind(dev_id, FAL_ACL_LIST_MULTICAST, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
        isisc_acl_list_unbind(dev_id, FAL_ACL_LIST_MULTICAST+1, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
    }

    if(bind_p==0)
    {
        for(i=0; i<7; i++)
        {
            isisc_acl_list_bind(dev_id, FAL_ACL_LIST_MULTICAST, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
            isisc_acl_list_bind(dev_id, FAL_ACL_LIST_MULTICAST+1, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
        }
    }
    else
    {
        for(i=0; i<7; i++)
            if((bind_p>>i) &0x1)
            {
                isisc_acl_list_bind(dev_id, FAL_ACL_LIST_MULTICAST, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
                isisc_acl_list_bind(dev_id, FAL_ACL_LIST_MULTICAST+1, FAL_ACL_DIREC_IN, FAL_ACL_BIND_PORT, i);
            }
            else
                continue;
    }
	return 0;
}
/*
** Only update the related portmap from the privious input.
*/
HSL_LOCAL sw_error_t isisc_multicast_acl_update(a_uint32_t dev_id, int list_id, int acl_index, fal_igmp_sg_entry_t * entry, int action)
{
    a_uint32_t rule_pos;
    sw_error_t rv;

    if(acl_index<0)
    {
        aos_printk("Something is wrong...\n");
        return SW_FAIL;
    }

    rule_pos = isisc_acl_rule_get_offset(dev_id, list_id, multi_acl_group[acl_index].index);
    if(MULT_ACTION_SET == action)
    {
        multi_acl_group[acl_index].entry.port_map |= entry->port_map;
        if(entry->port_map == 0)
        {
            multi_acl_group[acl_index].entry.port_map = 0;
        }
    }
    else if(MULT_ACTION_CLEAR == action)
        multi_acl_group[acl_index].entry.port_map &= ~(entry->port_map);

    rv = multi_portmap_aclreg_set_all(dev_id, rule_pos, &multi_acl_group[acl_index].entry);

    multi_acl_bind(dev_id); //Here need extra bind since IGMP join/leave would happen
    return rv;
}

HSL_LOCAL sw_error_t isisc_multicast_acl_del(a_uint32_t dev_id, int list_id, int index)
{
    sw_error_t rv;
    int rule_id;

    rule_id = multi_acl_group[index].index;

    rv = isisc_acl_rule_delete(dev_id, list_id, rule_id, 1);
    multi_acl_bind(dev_id); //Here need extra bind since IGMP join/leave would happen
    return rv;
}

/*
** Add new acl rule with parameters: DIP, SIP, redirect port.
*/
HSL_LOCAL sw_error_t isisc_multicast_acl_add(a_uint32_t dev_id, int list_id, fal_igmp_sg_entry_t * entry)
{
    sw_error_t val;
    a_uint32_t pos;
    fal_acl_rule_t acl= {0};

    /* IPv4 multicast */
    if( entry->group.type == FAL_ADDR_IPV4 )
    {
        MULTI_DEBUG("KKK1, group[%d][%x], source[%d][%x]\n",entry->group.type,
                    entry->group.u.ip4_addr, entry->source.type, entry->source.u.ip4_addr);

        acl.rule_type = FAL_ACL_RULE_IP4;

        if(entry->group.u.ip4_addr!= 0)
        {
            acl.dest_ip4_val = entry->group.u.ip4_addr;
            acl.dest_ip4_mask = 0xffffffff;//e->ip.dmsk.s_addr;
            FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_IP4_DIP);
        }
        if(entry->source.u.ip4_addr!= 0)
        {
            acl.src_ip4_val = entry->source.u.ip4_addr;
            acl.src_ip4_mask = 0xffffffff;//e->ip.smsk.s_addr;
            FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_IP4_SIP);
        }
        if( entry->port_map==0 )
            FAL_ACTION_FLG_SET ( acl.action_flg, FAL_ACL_ACTION_DENY);
        else
            //FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_INVERSE_ALL);
            FAL_ACTION_FLG_SET ( acl.action_flg, FAL_ACL_ACTION_PERMIT );

        /* Be careful, _isisc_acl_action_parse() will block FAL_ACL_ACTION_DENY action, So we change it. */
        if( entry->port_map )
        {
            FAL_ACTION_FLG_SET(acl.action_flg, FAL_ACL_ACTION_REDPT);
            acl.ports = entry->port_map;
        }
    }
    else if( entry->group.type == FAL_ADDR_IPV6 )
    {
        MULTI_DEBUG("KKK2, group[%d][%x], source[%d][%x], pm=%x\n",entry->group.type,
                    entry->group.u.ip6_addr.ul[0], entry->source.type, entry->source.u.ip6_addr.ul[0], entry->port_map);

        acl.rule_type = FAL_ACL_RULE_IP6;

        if(!ip6_addr_is_null(&(entry->group.u.ip6_addr)))
        {
            memcpy(&acl.dest_ip6_val, &(entry->group.u.ip6_addr), sizeof(entry->group.u.ip6_addr));
            acl.dest_ip6_mask.ul[0] = 0xffffffff;
            acl.dest_ip6_mask.ul[1] = 0xffffffff;
            acl.dest_ip6_mask.ul[2] = 0xffffffff;
            acl.dest_ip6_mask.ul[3] = 0xffffffff;
            FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_IP6_DIP);
        }
        if(!ip6_addr_is_null(&(entry->source.u.ip6_addr)))
        {
            memcpy(&acl.src_ip6_val, &(entry->source.u.ip6_addr), sizeof(entry->source.u.ip6_addr));
            acl.src_ip6_mask.ul[0] = 0xffffffff;
            acl.src_ip6_mask.ul[1] = 0xffffffff;
            acl.src_ip6_mask.ul[2] = 0xffffffff;
            acl.src_ip6_mask.ul[3] = 0xffffffff;
            FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_IP6_SIP);
        }

        if( entry->port_map==0 )
            FAL_ACTION_FLG_SET ( acl.action_flg, FAL_ACL_ACTION_DENY);
        else
            //FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_INVERSE_ALL);
            FAL_ACTION_FLG_SET ( acl.action_flg, FAL_ACL_ACTION_PERMIT );

        /* Be careful, _isisc_acl_action_parse() will block FAL_ACL_ACTION_DENY action, So we change it. */
        if( entry->port_map )
        {
            FAL_ACTION_FLG_SET(acl.action_flg, FAL_ACL_ACTION_REDPT);
            acl.ports = entry->port_map;
        }
    }

    if (entry->vlan_id < 4096)
    {
        FAL_FIELD_FLG_SET(acl.field_flg, FAL_ACL_FIELD_MAC_VID);
        acl.vid_val = entry->vlan_id;
        acl.vid_op = FAL_ACL_FIELD_MASK;
        acl.vid_mask = 0xfff;
    }

    pos = isisc_multicast_acl_total_n(dev_id, list_id);

    MULTI_DEBUG("In isisc_multicast_acl_add, list_id=%d, rule_id=%d\n", list_id, pos);
    val = isisc_acl_rule_add(dev_id, list_id, pos, mul_rule_nr, &acl);

    multi_acl_bind(dev_id);

    return val;
}


HSL_LOCAL int iterate_multicast_acl_group(a_uint32_t number, fal_igmp_sg_entry_t * entry)
{
    int count=0;
    int i;

    if (number == 0)
        return 0; //no any ACL rules based the query

    for(i=0; i<number; i++)
    {

        /*MULTI_DEBUG("2:iterate_multicast_acl_group, index=%d, multi_acl_info[%d].entry=type[%d]-addr[%x], pm=%x, new entry=type[%d]-addr[%x], pm=%x\n",
            multi_acl_info[i].index,i, multi_acl_info[i].entry.group.type, multi_acl_info[i].entry.group.u.ip6_addr.ul[0], multi_acl_info[i].entry.port_map,
            entry->group.type, entry->group.u.ip6_addr.ul[0], entry->port_map);*/

        if(0 == memcmp(&(multi_acl_info[i].entry.group), &(entry->group), sizeof(entry->group)))
        {
            memcpy(&multi_acl_group[count], &multi_acl_info[i], sizeof(multi_acl_info[i]));
            count++;//return the real number of multi_acl_group[]
            MULTI_DEBUG("in iterate_multicast_acl_group, count=%d, i=%d\n", count, i);
        }
    }

    return count;
}

HSL_LOCAL int mult_acl_has_entry(fal_igmp_sg_addr_t * group, fal_igmp_sg_addr_t *source)
{
    int rule_id;
    int ret = 0;
#if 0
    if(source != NULL)
    {
        MULTI_DEBUG("new group[%d]= %x %x %x %x, new source[%d]=%x %x %x %x\n",
                    group->type, group->u.ip6_addr.ul[0], group->u.ip6_addr.ul[1], group->u.ip6_addr.ul[2], group->u.ip6_addr.ul[3],
                    source->type, source->u.ip6_addr.ul[0], source->u.ip6_addr.ul[1], source->u.ip6_addr.ul[2], source->u.ip6_addr.ul[3]);

        MULTI_DEBUG("old group[%d]= %x %x %x %x, old source[%d]=%x %x %x %x\n",
                    multi_acl_group[0].entry.group.type, multi_acl_group[0].entry.group.u.ip6_addr.ul[0],
                    multi_acl_group[0].entry.group.u.ip6_addr.ul[1], multi_acl_group[0].entry.group.u.ip6_addr.ul[2], multi_acl_group[0].entry.group.u.ip6_addr.ul[3],
                    multi_acl_group[0].entry.source.type, multi_acl_group[0].entry.source.u.ip6_addr.ul[0],
                    multi_acl_group[0].entry.source.u.ip6_addr.ul[1], multi_acl_group[0].entry.source.u.ip6_addr.ul[2], multi_acl_group[0].entry.source.u.ip6_addr.ul[3]);
    }
#endif
    if(source == NULL)
    {
        for(rule_id=0; rule_id<FAL_IGMP_SG_ENTRY_MAX; rule_id++)
        {
            if( (0==memcmp(&multi_acl_group[rule_id].entry.group, group, sizeof(fal_igmp_sg_addr_t))) &&
                    (multi_source_is_null(&multi_acl_group[rule_id].entry.source)))
            {
                MULTI_DEBUG("Source=0:Orignal ACL rule have this entry! rule id= %d\n", rule_id);
                ret = rule_id+1; // ensure the return value is the actually number of entry
                break;
            }
        }
    }
    else
    {
        for(rule_id=0; rule_id<FAL_IGMP_SG_ENTRY_MAX; rule_id++)
        {
            if( (0==memcmp(&multi_acl_group[rule_id].entry.group, group, sizeof(fal_igmp_sg_addr_t))) &&
                    (0==memcmp(&multi_acl_group[rule_id].entry.source, source, sizeof(fal_igmp_sg_addr_t))))
            {
                MULTI_DEBUG("Orignal ACL rule have this entry! rule id= %d\n", rule_id);
                ret = rule_id+1; // ensure the return value is the actually number of entry
                break;
            }
        }
    }

    return ret;
}
HSL_LOCAL int portmap_null(int index, fal_pbmp_t portmap)
{
    int val;
    if (index<0)
    {
        aos_printk("portmap_null, index error\n");
        return SW_FAIL;
    }
    val = multi_acl_group[index].entry.port_map&(~portmap);

    if( 0 == (val&0xff) )
        return 1;
    else
        return 0;
}

HSL_LOCAL int portmap_valid(fal_igmp_sg_entry_t *g_source, fal_igmp_sg_entry_t *g_star)
{
    /* return 0 means the portmap is Not valid
        return 1 means the protmap is valid
    */
    /* MULTI_DEBUG("portmap_valid:g_source portmap=%x,  source=%x,group=%x, g_star portmap=%x, source=%x, group=%x\n",
          g_source->port_map, g_source->source.u.ip4_addr, g_source->group.u.ip4_addr,
          g_star->port_map, g_star->source.u.ip4_addr,g_star->group.u.ip4_addr);*/

    if(multi_source_is_null(&(g_star->source)))
    {
        if((g_source->port_map|g_star->port_map) == g_star->port_map)
        {
            return 0;
        }
    }

    return 1;
}


HSL_LOCAL int portmap_clear_type(int count, int index, fal_pbmp_t portmap)
{
    if(count>=0 && index<count) //new_index must >0; this means there're (G,*) and (G,S)
    {
        //if the new clear portmap will cause (G,S)=(G,*), Delete the (G,S)
        if((multi_acl_group[index].entry.port_map & (~portmap)) == multi_acl_group[count].entry.port_map)
            return 1; //delete


        //The following means there must be at least one bit clear wrong. Clear the (G,*) portmap.
        if( ((multi_acl_group[index].entry.port_map & (~portmap)) & (multi_acl_group[count].entry.port_map))
                != (multi_acl_group[count].entry.port_map))
            return 0;

        return 2; //Normal update
    }
    return 0;
}
sw_error_t isisc_igmp_sg_entry_set(a_uint32_t dev_id, fal_igmp_sg_entry_t * entry)
{
    int number, count;
    int new_index=0;
    sw_error_t rv;
    int action = MULT_ACTION_SET;
    int i=0;

    HSL_API_LOCK;
    isisc_multicast_init(dev_id);
    aos_mem_zero(multi_acl_info, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    aos_mem_zero(multi_acl_group, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    MULTI_DEBUG("Before query: group=%x, source=%x, portmap=%x\n", entry->group.u.ip4_addr, entry->source.u.ip4_addr, entry->port_map);
    //number is the total multicast ACL rules amount, stores in multi_acl_info[];
    number = isisc_multicast_acl_query(dev_id);
    if(number > FAL_IGMP_SG_ENTRY_MAX)
	return SW_FAIL;
    //count the total specific multicast group ACL rules, stores in multi_acl_group[]; count <=number
    count = iterate_multicast_acl_group(number, entry);
    //new_index-1 is the found entry index in multi_acl_group[], the real index is [new_index-1], 0 means no entry
    new_index = mult_acl_has_entry(&entry->group, &entry->source);

    MULTI_DEBUG("Start entry set: number=%d, count=%d, new_index=%d, pm=%x\n", number, count, new_index, entry->port_map);
    if( 0==multi_source_is_null(&entry->source) )  // new entry is (G, S)
    {
        MULTI_DEBUG("the new entry is (G,S)\n");
        if(count>0 && 0 == portmap_valid(entry, &(multi_acl_group[count-1].entry))) //specfic group entry exist,(G,S) or (G,*)
        {
            //return SW_NO_CHANGE; // The new portmap is Not valid
            MULTI_DEBUG("KKK, modified 1 !!!\n");
        }

        if(0 == new_index) //new entry, need add
        {
#if 0
            /*The method:
                1. predict if the portmap should be modified.
                2. add new acl rule with new portmap value.
            */
            if((tmp_index = mult_acl_has_entry(&entry->group, NULL))>0) // (G, *) entry exist
            {
                /*Here the update should new (G, S) OR orignal (G,*) portmap,
                be careful, entry's portmap value will be modified, so I use tmp_entry.
                */
                memcpy(tmp_entry, entry, sizeof(fal_igmp_sg_entry_t));
                MULTI_DEBUG("Here, (G,*) exist! tmp_index=%d\n", tmp_index);
                sw_multicast_acl_update(FAL_ACL_LIST_MULTICAST+1, tmp_index-1, tmp_entry, action);

                isisc_multicast_acl_add(FAL_ACL_LIST_MULTICAST, tmp_entry);
                return SW_OK;
            }
#endif
            isisc_multicast_acl_add(dev_id, FAL_ACL_LIST_MULTICAST, entry);
            MULTI_DEBUG("Here, need add (G, S), portmap=%x\n", entry->port_map);
            return SW_OK;
        }
        else
        {
            //Here update Just: the old exist entry portmap OR the new entry portmap
            isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, new_index-1, entry, action);
            return SW_OK;
        }
    } //end of memcmp
    else  // new entry is (G, *)
    {
        if(0 == new_index) //new entry, need add
        {
            isisc_multicast_acl_add(dev_id, FAL_ACL_LIST_MULTICAST+1, entry);
            rv = SW_OK;
        }
        else if(new_index > 0) // (G, *) entry exist?
        {
            //Update exist (G, *) portmap with new portmap
            MULTI_DEBUG("(G,*) exist, before update, new_index=%d\n", new_index );
            isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST+1, new_index-1, entry, action);
            rv = SW_OK;
        }

        if(new_index>0&&count>1) //(G,S*) and (G,*) exist, new entry is (G,*)
        {
            for(i=count-2; i>=0&&i<FAL_IGMP_SG_ENTRY_MAX; i--)
            {
                if(multi_acl_group[i].entry.port_map==0) //This ACL rule should be done nothing, DENY rule
                    continue;

                if(0 == portmap_valid(&(multi_acl_group[i].entry), &(multi_acl_group[count-1].entry)))
                {
                    MULTI_DEBUG("1:portmap is not valid, should delete, i=%d, source portmap=%x, gstar pm=%x\n",
                                i, multi_acl_group[i].entry.port_map, multi_acl_group[count-1].entry.port_map);
                    isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST, i);
                    rv = SW_NO_MORE;
                }
                else
                {
                    MULTI_DEBUG("1:Start update all (G,S),i=%d, gstar portmap=%x\n", i, multi_acl_group[count-1].entry.port_map);
                    //Update all (G,S) entry portmap with new(G, *) portmap
                    isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, i, entry, action);
                    rv = SW_OK;
                }
            }
        }
        else if(new_index==0&&count>0) //only exist (G,S*) orignally
        {
            for(i=count-1; i>=0&&i<FAL_IGMP_SG_ENTRY_MAX; i--)
            {
                if(multi_acl_group[i].entry.port_map==0) //This ACL rule should be done nothing, DENY rule
                    continue;

                if(0 == portmap_valid(&(multi_acl_group[i].entry), entry))
                {
                    MULTI_DEBUG("2:portmap is not valid, should delete, i=%d, source portmap=%x, gstar pm=%x\n",
                                i, multi_acl_group[i].entry.port_map, entry->port_map);
                    isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST, i);
                    rv = SW_NO_MORE;
                }
                else
                {
                    MULTI_DEBUG("2:Start update all (G,S),i=%d, portmap=%x\n", i, entry->port_map);
                    //Update all (G,S) entry portmap with new(G, *) portmap
                    isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, i, entry, action);
                    rv = SW_OK;
                }
            }
        }
    }
    HSL_API_UNLOCK;
    return rv;
}

sw_error_t isisc_igmp_sg_entry_clear(a_uint32_t dev_id, fal_igmp_sg_entry_t * entry)
{
    a_uint32_t number, count;
    int new_index=0;
    sw_error_t rv = SW_OK;
    int action= MULT_ACTION_CLEAR;
    int i=0;
    int pm_type;

    HSL_API_LOCK;
    isisc_multicast_init(dev_id);
    aos_mem_zero(multi_acl_info, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    aos_mem_zero(multi_acl_group, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    //number is the total multicast ACL rules amount, stores in multi_acl_info[];
    number = isisc_multicast_acl_query(dev_id);
    if(number > FAL_IGMP_SG_ENTRY_MAX)
	return SW_FAIL;
    //count the total specific multicast group ACL rules, stores in multi_acl_group[]; count <=number
    count = iterate_multicast_acl_group(number, entry);
    if(count == 0)
        return SW_OK;

    //new_index-1 is the found entry index in multi_acl_group[]
    new_index = mult_acl_has_entry(&entry->group, &entry->source);

    MULTI_DEBUG("Start entry clear: number=%d, count=%d, new_index=%d\n", number, count, new_index);
    if(0 == new_index || new_index > FAL_IGMP_SG_ENTRY_MAX || count > FAL_IGMP_SG_ENTRY_MAX) //new entry, the user command is wrong
    {
        return SW_NO_SUCH;
    }

    if( 0==multi_source_is_null(&entry->source) )  // new entry is (G, S)
    {
        if (portmap_null(new_index-1, entry->port_map))
        {
            MULTI_DEBUG("KKK entry clear, new(G,S), with null portmap. \n");
            isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST, new_index-1);
            return SW_OK;
        }
        else
        {
            MULTI_DEBUG("KKK entry clear, new(G,S), with NOT null portmap. \n");
            /* If (G,*) doesn't exist, [count-1] is the last specfic group, maybe(G,*) */
            if(0 == multi_source_is_null(&(multi_acl_group[count-1].entry.source)))
            {
                isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, new_index-1, entry, action);
            }
            else //(G,*) exist
            {
                pm_type = portmap_clear_type(count-1, new_index-1, entry->port_map);
                if(pm_type == 0)
                    return SW_NO_CHANGE;
                else if(pm_type == 1)
                {
                    isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST, new_index-1);
                    return SW_OK;
                }
                else
                {
                    //normal update; consider here...wangson
                    isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, new_index-1, entry, action);
                }
            }
        }
        return SW_OK;
    }
    else  //clear entry is (G,*)
    {
        MULTI_DEBUG("Here, new_index[%d]>=0, new portmap to clear is %x\n", new_index, entry->port_map);
        if (portmap_null(new_index-1, entry->port_map))
        {
            isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST+1, new_index-1);
            rv = SW_OK;
        }
        else
        {
            MULTI_DEBUG("Update (G,*)!, new_index=%d, pm=%x\n", new_index, entry->port_map);
            isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST+1, new_index-1, entry, action);
        }
        MULTI_DEBUG("KKK, ready clear (G, S*), count=%d\n", count);
#if 0
        if(count>1) // (G, S*) entry exist, if count=1 here, only exist(G,*)entry
        {
            //count must >=2
            for(i=count-2; i>=0; i--)
            {
                if(portmap_null(i, entry->port_map))
                {
                    MULTI_DEBUG("portmap_null, i=%d\n", i);
                    isisc_multicast_acl_del(FAL_ACL_LIST_MULTICAST, i);
                    rv = SW_NO_MORE;
                }
                else
                {
                    //Update all (G,S) entry portmap with new(G, *) portmap
                    isisc_multicast_acl_update(FAL_ACL_LIST_MULTICAST, i, entry, action);
                    rv = SW_OK;
                }
            }
        }
#else
        if(count>1) // (G, S*) entry exist, if count=1 here, only exist(G,*)entry
        {
            //count must >=2
            for(i=count-2; i>=0&&i<FAL_IGMP_SG_ENTRY_MAX; i--)
            {
                //PortMap of entry (S,G) == (*,G) portmap after clear?
                if((multi_acl_group[new_index-1].entry.port_map&(~(entry->port_map))) ==
                        multi_acl_group[i].entry.port_map)
                    isisc_multicast_acl_del(dev_id, FAL_ACL_LIST_MULTICAST, i);
                else
                    //Update all (G,S) entry portmap with new(G, *) portmap
                    isisc_multicast_acl_update(dev_id, FAL_ACL_LIST_MULTICAST, i, entry, action);
                rv = SW_OK;
            }
        }
#endif
    }
    HSL_API_UNLOCK;
    return rv;
}

static void
print_ip4addr(char * param_name, a_uint32_t * buf,
              a_uint32_t size)
{
    a_uint32_t i;
    fal_ip4_addr_t ip4;

    ip4 = *((fal_ip4_addr_t *) buf);
    aos_printk("%s", param_name);
    for (i = 0; i < 3; i++)
    {
        aos_printk("%d.", (ip4 >> (24 - i * 8)) & 0xff);
    }
    aos_printk("%d", (ip4 & 0xff));
}
static void
print_ip6addr(char * param_name, a_uint32_t * buf,
              a_uint32_t size)
{
    a_uint32_t i;
    fal_ip6_addr_t ip6;

    ip6 = *(fal_ip6_addr_t *) buf;
    aos_printk("%s", param_name);
    for (i = 0; i < 3; i++)
    {
        aos_printk("%x:%x:", (ip6.ul[i] >> 16) & 0xffff, ip6.ul[i] & 0xffff);
    }
    aos_printk("%x:%x", (ip6.ul[3] >> 16) & 0xffff, ip6.ul[3] & 0xffff);
}
sw_error_t isisc_igmp_sg_entry_show(a_uint32_t dev_id)
{
    a_uint32_t number;
    int i;

    HSL_API_LOCK;
    isisc_multicast_init(dev_id);
    aos_mem_zero(multi_acl_info, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    aos_mem_zero(multi_acl_group, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    //number is the total multicast ACL rules amount, stores in multi_acl_info[];
    number = isisc_multicast_acl_query(dev_id);

    for(i=0; i<number; i++)
    {
        if(0 == multi_acl_info[i].entry.group.type)  //ipv4
        {
            aos_printk("\n[%d]:", i);
            print_ip4addr(" [Group IPv4 addr]:", (a_uint32_t *)&(multi_acl_info[i].entry.group.u.ip4_addr), sizeof (fal_ip4_addr_t));
            print_ip4addr(" [Source IPv4 addr]:", (a_uint32_t *)&(multi_acl_info[i].entry.source.u.ip4_addr), sizeof (fal_ip4_addr_t));
            aos_printk("\n     [Portmap]: 0x%x ", multi_acl_info[i].entry.port_map);
            aos_printk(" [Vlanid]: %d ", multi_acl_info[i].entry.vlan_id);
        }
        else if(1 == multi_acl_info[i].entry.group.type)  //ipv6
        {
            aos_printk("\n[%d]:", i);
            print_ip6addr(" [Group IPv6 addr]: ", (a_uint32_t *)&(multi_acl_info[i].entry.group.u.ip6_addr), sizeof (fal_ip6_addr_t));
            print_ip6addr(" [Source IPv6 addr]: ", (a_uint32_t *)&(multi_acl_info[i].entry.source.u.ip6_addr), sizeof (fal_ip6_addr_t));
            aos_printk("\n     [Portmap]: 0x%x ", multi_acl_info[i].entry.port_map);
            aos_printk(" [Vlanid]: %d ", multi_acl_info[i].entry.vlan_id);
        }

    }
    aos_printk("\n\nTotal %d multicast ACL rules.\n", number);
    HSL_API_UNLOCK;

    return SW_OK;
}

void
isisc_multicast_init(a_uint32_t dev_id)
{
    sw_error_t val;

    isisc_acl_status_set(dev_id, 1);

    val = isisc_acl_list_creat(dev_id, FAL_ACL_LIST_MULTICAST, FAL_MULTICAST_PRI);
    if(val !=SW_OK && val != SW_ALREADY_EXIST)
        aos_printk("Multicast 1 acl list create error, val=%d\n", val);

    val = isisc_acl_list_creat(dev_id, FAL_ACL_LIST_MULTICAST+1, FAL_MULTICAST_PRI+1);
    if(val !=SW_OK && val != SW_ALREADY_EXIST)
        aos_printk("Multicast 2 acl list create error, val=%d\n", val);

}

sw_error_t isisc_igmp_sg_entry_query(a_uint32_t dev_id, fal_igmp_sg_info_t *info)
{
    a_uint32_t number;
    int i;

    HSL_API_LOCK;
    isisc_multicast_init(dev_id);
    aos_mem_zero(multi_acl_info, FAL_IGMP_SG_ENTRY_MAX * sizeof (multi_acl_info_t));
    /*number is the total multicast ACL rules amount, stores in multi_acl_info[];*/
    number = isisc_multicast_acl_query(dev_id);
    if(number > FAL_IGMP_SG_ENTRY_MAX)
    {
	    HSL_API_UNLOCK;
	    return SW_FAIL;
    }
    info->cnt = number;

    for(i=0; i<number; i++)
    {
        aos_mem_copy(&(info->acl_info[i]), &(multi_acl_info[i]), sizeof(multi_acl_info_t));
    }
    HSL_API_UNLOCK;

    return SW_OK;
}

