/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <hxge_impl.h>
#include <hxge_classify.h>
#include <hxge_pfc.h>
#include <hpi_pfc.h>
#include <sys/ethernet.h>

static uint32_t crc32_mchash(p_ether_addr_t addr);
static hxge_status_t hxge_pfc_load_hash_table(p_hxge_t hxgep);
static uint32_t hxge_get_blade_id(p_hxge_t hxgep);
static hxge_status_t hxge_tcam_default_add_entry(p_hxge_t hxgep,
	tcam_class_t class);
static hxge_status_t hxge_tcam_default_config(p_hxge_t hxgep);

hxge_status_t
hxge_classify_init(p_hxge_t hxgep)
{
	hxge_status_t status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_classify_init"));

	status = hxge_classify_init_sw(hxgep);
	if (status != HXGE_OK)
		return (status);

	status = hxge_classify_init_hw(hxgep);
	if (status != HXGE_OK) {
		(void) hxge_classify_exit_sw(hxgep);
		return (status);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_classify_init"));

	return (HXGE_OK);
}

hxge_status_t
hxge_classify_uninit(p_hxge_t hxgep)
{
	return (hxge_classify_exit_sw(hxgep));
}

static hxge_status_t
hxge_tcam_dump_entry(p_hxge_t hxgep, uint32_t location)
{
	hxge_tcam_entry_t	tcam_rdptr;
	uint64_t		asc_ram = 0;
	hpi_handle_t		handle;
	hpi_status_t		status;

	handle = hxgep->hpi_reg_handle;

	/* Retrieve the saved entry */
	bcopy((void *)&hxgep->classifier.tcam_entries[location].tce,
	    (void *)&tcam_rdptr, sizeof (hxge_tcam_entry_t));

	/* Compare the entry */
	status = hpi_pfc_tcam_entry_read(handle, location, &tcam_rdptr);
	if (status == HPI_FAILURE) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_tcam_dump_entry: tcam read failed at location %d ",
		    location));
		return (HXGE_ERROR);
	}

	status = hpi_pfc_tcam_asc_ram_entry_read(handle, location, &asc_ram);

	HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "location %x\n"
	    " key:  %llx %llx\n mask: %llx %llx\n ASC RAM %llx \n", location,
	    tcam_rdptr.key0, tcam_rdptr.key1,
	    tcam_rdptr.mask0, tcam_rdptr.mask1, asc_ram));
	return (HXGE_OK);
}

void
hxge_get_tcam(p_hxge_t hxgep, p_mblk_t mp)
{
	uint32_t	tcam_loc;
	uint32_t	*lptr;
	int		location;
	int		start_location = 0;
	int		stop_location = hxgep->classifier.tcam_size;

	lptr = (uint32_t *)mp->b_rptr;
	location = *lptr;

	if ((location >= hxgep->classifier.tcam_size) || (location < -1)) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_tcam_dump: Invalid location %d \n", location));
		return;
	}
	if (location == -1) {
		start_location = 0;
		stop_location = hxgep->classifier.tcam_size;
	} else {
		start_location = location;
		stop_location = location + 1;
	}
	for (tcam_loc = start_location; tcam_loc < stop_location; tcam_loc++)
		(void) hxge_tcam_dump_entry(hxgep, tcam_loc);
}

/*ARGSUSED*/
static hxge_status_t
hxge_add_tcam_entry(p_hxge_t hxgep, flow_resource_t *flow_res)
{
	return (HXGE_OK);
}

void
hxge_put_tcam(p_hxge_t hxgep, p_mblk_t mp)
{
	flow_resource_t *fs;
	fs = (flow_resource_t *)mp->b_rptr;

	HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
	    "hxge_put_tcam addr fs $%p  type %x offset %x",
	    fs, fs->flow_spec.flow_type, fs->channel_cookie));

	(void) hxge_add_tcam_entry(hxgep, fs);
}

static uint32_t
hxge_get_blade_id(p_hxge_t hxgep)
{
	phy_debug_training_vec_t	blade_id;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_get_blade_id"));
	HXGE_REG_RD32(hxgep->hpi_reg_handle, PHY_DEBUG_TRAINING_VEC,
	    &blade_id.value);
	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_get_blade_id: id = %d",
	    blade_id.bits.bld_num));

	return (blade_id.bits.bld_num);
}

static hxge_status_t
hxge_tcam_default_add_entry(p_hxge_t hxgep, tcam_class_t class)
{
	hpi_status_t		rs = HPI_SUCCESS;
	uint32_t		location;
	hxge_tcam_entry_t	entry;
	hxge_tcam_spread_t	*key = NULL;
	hxge_tcam_spread_t	*mask = NULL;
	hpi_handle_t		handle;
	p_hxge_hw_list_t	hw_p;

	if ((hw_p = hxgep->hxge_hw_p) == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_tcam_default_add_entry: common hardware not set"));
		return (HXGE_ERROR);
	}

	bzero(&entry, sizeof (hxge_tcam_entry_t));

	/*
	 * The class id and blade id are common for all classes
	 * Only use the blade id for matching and the rest are wild cards.
	 * This will allow one TCAM entry to match all traffic in order
	 * to spread the traffic using source hash.
	 */
	key = &entry.key.spread;
	mask = &entry.mask.spread;

	key->blade_id = hxge_get_blade_id(hxgep);

	mask->class_code = 0xf;
	mask->class_code_l = 0x1;
	mask->blade_id = 0;
	mask->wild1 = 0x7ffffff;
	mask->wild = 0xffffffff;
	mask->wild_l = 0xffffffff;

	location = class;

	handle = hxgep->hpi_reg_handle;

	MUTEX_ENTER(&hw_p->hxge_tcam_lock);
	rs = hpi_pfc_tcam_entry_write(handle, location, &entry);
	if (rs & HPI_PFC_ERROR) {
		MUTEX_EXIT(&hw_p->hxge_tcam_lock);
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_tcam_default_add_entry tcam entry write"
		    " failed for location %d", location));
		return (HXGE_ERROR);
	}

	/* Add the associative portion */
	entry.match_action.value = 0;

	/* Use source hash to spread traffic */
	entry.match_action.bits.channel_d = 0;
	entry.match_action.bits.channel_c = 1;
	entry.match_action.bits.channel_b = 2;
	entry.match_action.bits.channel_a = 3;
	entry.match_action.bits.source_hash = 1;
	entry.match_action.bits.discard = 0;

	rs = hpi_pfc_tcam_asc_ram_entry_write(handle,
	    location, entry.match_action.value);
	if (rs & HPI_PFC_ERROR) {
		MUTEX_EXIT(&hw_p->hxge_tcam_lock);
		HXGE_DEBUG_MSG((hxgep, PFC_CTL,
		    " hxge_tcam_default_add_entry tcam entry write"
		    " failed for ASC RAM location %d", location));
		return (HXGE_ERROR);
	}

	bcopy((void *) &entry,
	    (void *) &hxgep->classifier.tcam_entries[location].tce,
	    sizeof (hxge_tcam_entry_t));

	MUTEX_EXIT(&hw_p->hxge_tcam_lock);

	return (HXGE_OK);
}

/*
 * Configure one TCAM entry for each class and make it match
 * everything within the class in order to spread the traffic
 * among the DMA channels based on the source hash.
 *
 * This is the default for now. This may change when Crossbow is
 * available for configuring TCAM.
 */
static hxge_status_t
hxge_tcam_default_config(p_hxge_t hxgep)
{
	uint8_t		class;
	uint32_t	class_config;
	hxge_status_t	status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_tcam_default_config"));

	/*
	 * Add TCAM and its associative ram entries
	 * A wild card will be used for the class code in order to match
	 * any classes.
	 */
	class = 0;
	status = hxge_tcam_default_add_entry(hxgep, class);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_tcam_default_config "
		    "hxge_tcam_default_add_entry failed class %d ",
		    class));
		return (HXGE_ERROR);
	}

	/* Enable the classes */
	for (class = TCAM_CLASS_TCP_IPV4;
	    class <= TCAM_CLASS_SCTP_IPV6; class++) {
		/*
		 * By default, it is set to HXGE_CLASS_TCAM_LOOKUP in
		 * hxge_ndd.c. It may be overwritten in hxge.conf.
		 */
		class_config = hxgep->class_config.class_cfg[class];

		status = hxge_pfc_ip_class_config(hxgep, class, class_config);
		if (status & HPI_PFC_ERROR) {
			HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
			    "hxge_tcam_default_config "
			    "hxge_pfc_ip_class_config failed "
			    " class %d config %x ", class, class_config));
			return (HXGE_ERROR);
		}
	}

	status = hxge_pfc_config_tcam_enable(hxgep);

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_tcam_default_config"));

	return (status);
}

hxge_status_t
hxge_pfc_set_default_mac_addr(p_hxge_t hxgep)
{
	hxge_status_t status;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_set_default_mac_addr"));

	MUTEX_ENTER(&hxgep->ouraddr_lock);

	/*
	 * Set new interface local address and re-init device.
	 * This is destructive to any other streams attached
	 * to this device.
	 */
	RW_ENTER_WRITER(&hxgep->filter_lock);
	status = hxge_pfc_set_mac_address(hxgep,
	    HXGE_MAC_DEFAULT_ADDR_SLOT, &hxgep->ouraddr);
	RW_EXIT(&hxgep->filter_lock);

	MUTEX_EXIT(&hxgep->ouraddr_lock);

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_set_default_mac_addr"));
	return (status);
}

/*
 * Add a multicast address entry into the HW hash table
 */
hxge_status_t
hxge_add_mcast_addr(p_hxge_t hxgep, struct ether_addr *addrp)
{
	uint32_t	mchash;
	p_hash_filter_t	hash_filter;
	uint16_t	hash_bit;
	boolean_t	rx_init = B_FALSE;
	uint_t		j;
	hxge_status_t	status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_add_mcast_addr"));

	RW_ENTER_WRITER(&hxgep->filter_lock);
	mchash = crc32_mchash(addrp);

	if (hxgep->hash_filter == NULL) {
		HXGE_DEBUG_MSG((NULL, STR_CTL,
		    "Allocating hash filter storage."));
		hxgep->hash_filter = KMEM_ZALLOC(sizeof (hash_filter_t),
		    KM_SLEEP);
	}

	hash_filter = hxgep->hash_filter;
	/*
	 * Note that mchash is an 8 bit value and thus 0 <= mchash <= 255.
	 * Consequently, 0 <= j <= 15 and 0 <= mchash % HASH_REG_WIDTH <= 15.
	 */
	j = mchash / HASH_REG_WIDTH;
	hash_bit = (1 << (mchash % HASH_REG_WIDTH));
	hash_filter->hash_filter_regs[j] |= hash_bit;

	hash_filter->hash_bit_ref_cnt[mchash]++;
	if (hash_filter->hash_bit_ref_cnt[mchash] == 1) {
		hash_filter->hash_ref_cnt++;
		rx_init = B_TRUE;
	}

	if (rx_init) {
		(void) hpi_pfc_set_l2_hash(hxgep->hpi_reg_handle, B_FALSE);
		(void) hxge_pfc_load_hash_table(hxgep);
		(void) hpi_pfc_set_l2_hash(hxgep->hpi_reg_handle, B_TRUE);
	}

	RW_EXIT(&hxgep->filter_lock);

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_add_mcast_addr"));

	return (HXGE_OK);
fail:
	RW_EXIT(&hxgep->filter_lock);
	HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "hxge_add_mcast_addr: "
	    "Unable to add multicast address"));

	return (status);
}

/*
 * Remove a multicast address entry from the HW hash table
 */
hxge_status_t
hxge_del_mcast_addr(p_hxge_t hxgep, struct ether_addr *addrp)
{
	uint32_t	mchash;
	p_hash_filter_t	hash_filter;
	uint16_t	hash_bit;
	boolean_t	rx_init = B_FALSE;
	uint_t		j;
	hxge_status_t	status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_del_mcast_addr"));
	RW_ENTER_WRITER(&hxgep->filter_lock);
	mchash = crc32_mchash(addrp);
	if (hxgep->hash_filter == NULL) {
		HXGE_DEBUG_MSG((NULL, STR_CTL,
		    "Hash filter already de_allocated."));
		RW_EXIT(&hxgep->filter_lock);
		HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_del_mcast_addr"));
		return (HXGE_OK);
	}

	hash_filter = hxgep->hash_filter;
	hash_filter->hash_bit_ref_cnt[mchash]--;
	if (hash_filter->hash_bit_ref_cnt[mchash] == 0) {
		j = mchash / HASH_REG_WIDTH;
		hash_bit = (1 << (mchash % HASH_REG_WIDTH));
		hash_filter->hash_filter_regs[j] &= ~hash_bit;
		hash_filter->hash_ref_cnt--;
		rx_init = B_TRUE;
	}

	if (hash_filter->hash_ref_cnt == 0) {
		HXGE_DEBUG_MSG((NULL, STR_CTL,
		    "De-allocating hash filter storage."));
		KMEM_FREE(hash_filter, sizeof (hash_filter_t));
		hxgep->hash_filter = NULL;
	}

	if (rx_init) {
		(void) hpi_pfc_set_l2_hash(hxgep->hpi_reg_handle, B_FALSE);
		(void) hxge_pfc_load_hash_table(hxgep);

		/* Enable hash only if there are any hash entries */
		if (hxgep->hash_filter != NULL)
			(void) hpi_pfc_set_l2_hash(hxgep->hpi_reg_handle,
			    B_TRUE);
	}

	RW_EXIT(&hxgep->filter_lock);
	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_del_mcast_addr"));

	return (HXGE_OK);
fail:
	RW_EXIT(&hxgep->filter_lock);
	HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "hxge_del_mcast_addr: "
	    "Unable to remove multicast address"));

	return (status);
}

hxge_status_t
hxge_pfc_clear_mac_address(p_hxge_t hxgep, uint32_t slot)
{
	hpi_status_t status;

	status = hpi_pfc_clear_mac_address(hxgep->hpi_reg_handle, slot);
	if (status != HPI_SUCCESS)
		return (HXGE_ERROR);

	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_set_mac_address(p_hxge_t hxgep, uint32_t slot,
    struct ether_addr *addrp)
{
	hpi_handle_t		handle;
	uint64_t		addr;
	hpi_status_t		hpi_status;
	uint8_t			*address = addrp->ether_addr_octet;
	uint64_t		tmp;
	int			i;

	if (hxgep->hxge_hw_p == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_set_mac_address: common hardware not set"));
		return (HXGE_ERROR);
	}

	/*
	 * Convert a byte array to a 48 bit value.
	 * Need to check endianess if in doubt
	 */
	addr = 0;
	for (i = 0; i < ETHERADDRL; i++) {
		tmp = address[i];
		addr <<= 8;
		addr |= tmp;
	}

	handle = hxgep->hpi_reg_handle;
	hpi_status = hpi_pfc_set_mac_address(handle, slot, addr);

	if (hpi_status != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_set_mac_address: failed to set address"));
		return (HXGE_ERROR);
	}

	return (HXGE_OK);
}

/*ARGSUSED*/
hxge_status_t
hxge_pfc_num_macs_get(p_hxge_t hxgep, uint8_t *nmacs)
{
	*nmacs = PFC_N_MAC_ADDRESSES;
	return (HXGE_OK);
}


hxge_status_t
hxge_pfc_set_hash(p_hxge_t hxgep, uint32_t seed)
{
	hpi_status_t		rs = HPI_SUCCESS;
	hpi_handle_t		handle;
	p_hxge_class_pt_cfg_t 	p_class_cfgp;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " ==> hxge_pfc_set_hash"));

	p_class_cfgp = (p_hxge_class_pt_cfg_t)&hxgep->class_config;
	p_class_cfgp->init_hash = seed;
	handle = hxgep->hpi_reg_handle;

	rs = hpi_pfc_set_hash_seed_value(handle, seed);
	if (rs & HPI_PFC_ERROR) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_set_hash %x failed ", seed));
		return (HXGE_ERROR | rs);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " <== hxge_pfc_set_hash"));

	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_config_tcam_enable(p_hxge_t hxgep)
{
	hpi_handle_t		handle;
	boolean_t		enable = B_TRUE;
	hpi_status_t		hpi_status;

	handle = hxgep->hpi_reg_handle;
	if (hxgep->hxge_hw_p == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_config_tcam_enable: common hardware not set"));
		return (HXGE_ERROR);
	}

	hpi_status = hpi_pfc_set_tcam_enable(handle, enable);
	if (hpi_status != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hpi_pfc_set_tcam_enable: enable tcam failed"));
		return (HXGE_ERROR);
	}

	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_config_tcam_disable(p_hxge_t hxgep)
{
	hpi_handle_t		handle;
	boolean_t		enable = B_FALSE;
	hpi_status_t		hpi_status;

	handle = hxgep->hpi_reg_handle;
	if (hxgep->hxge_hw_p == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_config_tcam_disable: common hardware not set"));
		return (HXGE_ERROR);
	}

	hpi_status = hpi_pfc_set_tcam_enable(handle, enable);
	if (hpi_status != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hpi_pfc_set_tcam_enable: disable tcam failed"));
		return (HXGE_ERROR);
	}

	return (HXGE_OK);
}

static hxge_status_t
hxge_cfg_tcam_ip_class_get(p_hxge_t hxgep, tcam_class_t class,
    uint32_t *class_config)
{
	hpi_status_t	rs = HPI_SUCCESS;
	tcam_key_cfg_t	cfg;
	hpi_handle_t	handle;
	uint32_t	ccfg = 0;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_cfg_tcam_ip_class_get"));

	bzero(&cfg, sizeof (tcam_key_cfg_t));
	handle = hxgep->hpi_reg_handle;

	rs = hpi_pfc_get_l3_class_config(handle, class, &cfg);
	if (rs & HPI_PFC_ERROR) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_cfg_tcam_ip_class opt %x for class %d failed ",
		    class_config, class));
		return (HXGE_ERROR | rs);
	}
	if (cfg.discard)
		ccfg |=  HXGE_CLASS_DISCARD;

	if (cfg.lookup_enable)
		ccfg |= HXGE_CLASS_TCAM_LOOKUP;

	*class_config = ccfg;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " ==> hxge_cfg_tcam_ip_class_get %x",
	    ccfg));

	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_ip_class_config_get(p_hxge_t hxgep, tcam_class_t class,
    uint32_t *config)
{
	uint32_t	t_class_config;
	int		t_status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " ==> hxge_pfc_ip_class_config_get"));
	t_class_config = 0;
	t_status = hxge_cfg_tcam_ip_class_get(hxgep, class, &t_class_config);

	if (t_status & HPI_PFC_ERROR) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_ip_class_config_get for class %d tcam failed",
		    class));
		return (t_status);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " hxge_pfc_ip_class_config tcam %x",
	    t_class_config));

	*config = t_class_config;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_ip_class_config_get"));
	return (HXGE_OK);
}

static hxge_status_t
hxge_pfc_config_init(p_hxge_t hxgep)
{
	hpi_handle_t		handle;
	block_reset_t		reset_reg;

	handle = hxgep->hpi_reg_handle;
	if (hxgep->hxge_hw_p == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_config_init: common hardware not set"));
		return (HXGE_ERROR);
	}

	/* Reset PFC block from PEU to clear any previous state */
	reset_reg.value = 0;
	reset_reg.bits.pfc_rst = 1;
	HXGE_REG_WR32(hxgep->hpi_handle, BLOCK_RESET, reset_reg.value);
	HXGE_DELAY(1000);

	(void) hpi_pfc_set_tcam_enable(handle, B_FALSE);
	(void) hpi_pfc_set_l2_hash(handle, B_FALSE);
	(void) hpi_pfc_set_tcp_cksum(handle, B_TRUE);
	(void) hpi_pfc_set_default_dma(handle, 0);
	(void) hpi_pfc_mac_addr_enable(handle, 0);
	(void) hpi_pfc_set_force_csum(handle, B_FALSE);

	/* Set the drop log mask to ignore the logs */
	(void) hpi_pfc_set_drop_log_mask(handle, 1, 1, 1, 1, 1);

	/* Clear the interrupt masks to receive interrupts */
	(void) hpi_pfc_set_interrupt_mask(handle, 0, 0, 0);

	/* Clear the interrupt status */
	(void) hpi_pfc_clear_interrupt_status(handle);

	return (HXGE_OK);
}

static hxge_status_t
hxge_pfc_tcam_invalidate_all(p_hxge_t hxgep)
{
	hpi_status_t		rs = HPI_SUCCESS;
	hpi_handle_t		handle;
	p_hxge_hw_list_t	hw_p;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL,
	    "==> hxge_pfc_tcam_invalidate_all"));
	handle = hxgep->hpi_reg_handle;
	if ((hw_p = hxgep->hxge_hw_p) == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_tcam_invalidate_all: common hardware not set"));
		return (HXGE_ERROR);
	}

	MUTEX_ENTER(&hw_p->hxge_tcam_lock);
	rs = hpi_pfc_tcam_invalidate_all(handle);
	MUTEX_EXIT(&hw_p->hxge_tcam_lock);

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_tcam_invalidate_all"));
	if (rs != HPI_SUCCESS)
		return (HXGE_ERROR);

	return (HXGE_OK);
}

static hxge_status_t
hxge_pfc_tcam_init(p_hxge_t hxgep)
{
	hpi_status_t	rs = HPI_SUCCESS;
	hpi_handle_t	handle;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_tcam_init"));
	handle = hxgep->hpi_reg_handle;

	if (hxgep->hxge_hw_p == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_tcam_init: common hardware not set"));
		return (HXGE_ERROR);
	}

	/*
	 * Disable the TCAM.
	 */
	rs = hpi_pfc_set_tcam_enable(handle, B_FALSE);
	if (rs != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "failed TCAM Disable\n"));
		return (HXGE_ERROR | rs);
	}

	/*
	 * Invalidate all the TCAM entries for this blade.
	 */
	rs = hxge_pfc_tcam_invalidate_all(hxgep);
	if (rs != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "failed TCAM Disable\n"));
		return (HXGE_ERROR | rs);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_tcam_init"));
	return (HXGE_OK);
}

static hxge_status_t
hxge_pfc_vlan_tbl_clear_all(p_hxge_t hxgep)
{
	hpi_handle_t		handle;
	hpi_status_t		rs = HPI_SUCCESS;
	p_hxge_hw_list_t	hw_p;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_vlan_tbl_clear_all "));

	handle = hxgep->hpi_reg_handle;
	if ((hw_p = hxgep->hxge_hw_p) == NULL) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    " hxge_pfc_vlan_tbl_clear_all: common hardware not set"));
		return (HXGE_ERROR);
	}

	MUTEX_ENTER(&hw_p->hxge_vlan_lock);
	rs = hpi_pfc_cfg_vlan_table_clear(handle);
	MUTEX_EXIT(&hw_p->hxge_vlan_lock);

	if (rs != HPI_SUCCESS) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "failed vlan table clear\n"));
		return (HXGE_ERROR | rs);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_vlan_tbl_clear_all "));
	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_ip_class_config(p_hxge_t hxgep, tcam_class_t class, uint32_t config)
{
	uint32_t		class_config;
	p_hxge_class_pt_cfg_t 	p_class_cfgp;
	tcam_key_cfg_t		cfg;
	hpi_handle_t		handle;
	hpi_status_t		rs = HPI_SUCCESS;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " ==> hxge_pfc_ip_class_config"));
	p_class_cfgp = (p_hxge_class_pt_cfg_t)&hxgep->class_config;
	class_config = p_class_cfgp->class_cfg[class];

	if (class_config != config) {
		p_class_cfgp->class_cfg[class] = config;
		class_config = config;
	}

	handle = hxgep->hpi_reg_handle;

	if (class == TCAM_CLASS_ETYPE_1 || class == TCAM_CLASS_ETYPE_2) {
		rs = hpi_pfc_set_l2_class_slot(handle,
		    class_config & HXGE_CLASS_ETHER_TYPE_MASK,
		    class_config & HXGE_CLASS_VALID,
		    class - TCAM_CLASS_ETYPE_1);
	} else {
		if (class_config & HXGE_CLASS_DISCARD)
			cfg.discard = 1;
		else
			cfg.discard = 0;
		if (class_config & HXGE_CLASS_TCAM_LOOKUP)
			cfg.lookup_enable = 1;
		else
			cfg.lookup_enable = 0;

		rs = hpi_pfc_set_l3_class_config(handle, class, cfg);
	}

	if (rs & HPI_PFC_ERROR) {
		HXGE_DEBUG_MSG((hxgep, PFC_CTL,
		    " hxge_pfc_ip_class_config %x for class %d tcam failed",
		    config, class));
		return (HXGE_ERROR);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_ip_class_config"));
	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_ip_class_config_all(p_hxge_t hxgep)
{
	uint32_t	class_config;
	tcam_class_t	cl;
	int		status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_ip_class_config_all"));

	for (cl = TCAM_CLASS_ETYPE_1; cl <= TCAM_CLASS_SCTP_IPV6; cl++) {
		if (cl == TCAM_CLASS_RESERVED_4 ||
		    cl == TCAM_CLASS_RESERVED_5 ||
		    cl == TCAM_CLASS_RESERVED_6 ||
		    cl == TCAM_CLASS_RESERVED_7)
			continue;

		class_config = hxgep->class_config.class_cfg[cl];
		status = hxge_pfc_ip_class_config(hxgep, cl, class_config);
		if (status & HPI_PFC_ERROR) {
			HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
			    "hxge_pfc_ip_class_config failed "
			    " class %d config %x ", cl, class_config));
		}
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_ip_class_config_all"));
	return (HXGE_OK);
}

static hxge_status_t
hxge_pfc_update_hw(p_hxge_t hxgep)
{
	hxge_status_t	status = HXGE_OK;
	hpi_handle_t	handle;
	p_hxge_param_t	pa;
	int		i;
	boolean_t	parity = 0;
	boolean_t	implicit_valid = 0;
	vlan_id_t	implicit_vlan_id;
	uint32_t	vlanid_group;
	uint64_t	offset;
	int		max_vlan_groups;
	int		vlan_group_step;

	p_hxge_class_pt_cfg_t 	p_class_cfgp;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_update_hw"));
	p_class_cfgp = (p_hxge_class_pt_cfg_t)&hxgep->class_config;
	handle = hxgep->hpi_reg_handle;

	status = hxge_pfc_set_hash(hxgep, p_class_cfgp->init_hash);
	if (status != HXGE_OK) {
		HXGE_DEBUG_MSG((hxgep, PFC_CTL, "hxge_pfc_set_hash Failed"));
		return (HXGE_ERROR);
	}

	/*
	 * configure vlan table to join all vlans in order for Solaris
	 * network to receive vlan packets of any acceptible VIDs.
	 * This may change when Solaris network passes VIDs down.
	 */
	vlanid_group = 0xffffffff;
	max_vlan_groups = 128;
	vlan_group_step = 8;
	for (i = 0; i < max_vlan_groups; i++) {
		offset = PFC_VLAN_TABLE + i * vlan_group_step;
		REG_PIO_WRITE64(handle, offset, vlanid_group);
	}

	/* Configure the vlan_ctrl register */
	/* Let hw generate the parity bits in pfc_vlan_table */
	parity = 0;

	pa = (p_hxge_param_t)&hxgep->param_arr[param_implicit_vlan_id];
	implicit_vlan_id = (vlan_id_t)pa->value;

	/*
	 * Enable it only if there is a valid implicity vlan id either in
	 * NDD table or the .conf file.
	 */
	if (implicit_vlan_id >= VLAN_ID_MIN && implicit_vlan_id <= VLAN_ID_MAX)
		implicit_valid = 1;

	status = hpi_pfc_cfg_vlan_control_set(handle, parity, implicit_valid,
	    implicit_vlan_id);
	if (status != HPI_SUCCESS) {
		HXGE_DEBUG_MSG((hxgep, PFC_CTL,
		    "hxge_pfc_update_hw: hpi_pfc_cfg_vlan_control_set failed"));
		return (HXGE_ERROR);
	}

	/* config MAC addresses */
	/* Need to think about this */

	/* Configure hash value and classes */
	status = hxge_pfc_ip_class_config_all(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_pfc_ip_class_config_all Failed"));
		return (HXGE_ERROR);
	}

	return (HXGE_OK);
}

hxge_status_t
hxge_pfc_hw_reset(p_hxge_t hxgep)
{
	hxge_status_t status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, " ==> hxge_pfc_hw_reset"));

	status = hxge_pfc_config_init(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "failed PFC config init."));
		return (status);
	}

	status = hxge_pfc_tcam_init(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL, "failed TCAM init."));
		return (status);
	}

	/*
	 * invalidate VLAN RDC tables
	 */
	status = hxge_pfc_vlan_tbl_clear_all(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "failed VLAN Table Invalidate. "));
		return (status);
	}
	hxgep->classifier.state |= HXGE_PFC_HW_RESET;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_hw_reset"));

	return (HXGE_OK);
}

hxge_status_t
hxge_classify_init_hw(p_hxge_t hxgep)
{
	hxge_status_t status = HXGE_OK;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_classify_init_hw"));

	if (hxgep->classifier.state & HXGE_PFC_HW_INIT) {
		HXGE_DEBUG_MSG((hxgep, PFC_CTL,
		    "hxge_classify_init_hw already init"));
		return (HXGE_OK);
	}

	/* Now do a real configuration */
	status = hxge_pfc_update_hw(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_pfc_update_hw failed"));
		return (HXGE_ERROR);
	}

	status = hxge_tcam_default_config(hxgep);
	if (status != HXGE_OK) {
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_tcam_default_config failed"));
		return (status);
	}

	hxgep->classifier.state |= HXGE_PFC_HW_INIT;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_classify_init_hw"));

	return (HXGE_OK);
}

hxge_status_t
hxge_classify_init_sw(p_hxge_t hxgep)
{
	int		alloc_size;
	hxge_classify_t	*classify_ptr;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_classify_init_sw"));
	classify_ptr = &hxgep->classifier;

	if (classify_ptr->state & HXGE_PFC_SW_INIT) {
		HXGE_DEBUG_MSG((hxgep, PFC_CTL,
		    "hxge_classify_init_sw already init"));
		return (HXGE_OK);
	}

	/* Init SW structures */
	classify_ptr->tcam_size = TCAM_HXGE_TCAM_MAX_ENTRY;

	alloc_size = sizeof (tcam_flow_spec_t) * classify_ptr->tcam_size;
	classify_ptr->tcam_entries = KMEM_ZALLOC(alloc_size, NULL);
	bzero(classify_ptr->class_usage, sizeof (classify_ptr->class_usage));

	/* Start from the beginning of TCAM */
	hxgep->classifier.tcam_location = 0;
	classify_ptr->state |= HXGE_PFC_SW_INIT;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_classify_init_sw"));

	return (HXGE_OK);
}

hxge_status_t
hxge_classify_exit_sw(p_hxge_t hxgep)
{
	int		alloc_size;
	hxge_classify_t	*classify_ptr;
	int		fsize;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_classify_exit_sw"));
	classify_ptr = &hxgep->classifier;

	fsize = sizeof (tcam_flow_spec_t);
	if (classify_ptr->tcam_entries) {
		alloc_size = fsize * classify_ptr->tcam_size;
		KMEM_FREE((void *) classify_ptr->tcam_entries, alloc_size);
	}
	hxgep->classifier.state = NULL;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_classify_exit_sw"));

	return (HXGE_OK);
}

/*ARGSUSED*/
hxge_status_t
hxge_pfc_handle_sys_errors(p_hxge_t hxgep)
{
	return (HXGE_OK);
}

uint_t
hxge_pfc_intr(caddr_t arg1, caddr_t arg2)
{
	p_hxge_ldv_t		ldvp = (p_hxge_ldv_t)arg1;
	p_hxge_t		hxgep = (p_hxge_t)arg2;
	hpi_handle_t		handle;
	p_hxge_pfc_stats_t	statsp;
	pfc_int_status_t	int_status;
	pfc_bad_cs_counter_t	bad_cs_count;
	pfc_drop_counter_t	drop_count;
	pfc_drop_log_t		drop_log;
	pfc_vlan_par_err_log_t	vlan_par_err_log;
	pfc_tcam_par_err_log_t	tcam_par_err_log;

	if (ldvp == NULL) {
		HXGE_DEBUG_MSG((NULL, INT_CTL,
		    "<== hxge_pfc_intr: hxgep $%p ldvp $%p", hxgep, ldvp));
		return (DDI_INTR_UNCLAIMED);
	}

	if (arg2 == NULL || (void *) ldvp->hxgep != arg2) {
		hxgep = ldvp->hxgep;
	}

	handle = hxgep->hpi_reg_handle;
	statsp = (p_hxge_pfc_stats_t)&hxgep->statsp->pfc_stats;

	/*
	 * need to read the pfc interrupt status register to figure out
	 * what is happenning
	 */
	(void) hpi_pfc_get_interrupt_status(handle, &int_status);

	if (int_status.bits.pkt_drop) {
		statsp->pkt_drop++;
		if (statsp->pkt_drop == 1)
			HXGE_ERROR_MSG((hxgep, INT_CTL, "PFC pkt_drop"));

		/* Collect each individual drops */
		(void) hpi_pfc_get_drop_log(handle, &drop_log);

		if (drop_log.bits.tcp_ctrl_drop)
			statsp->errlog.tcp_ctrl_drop++;
		if (drop_log.bits.l2_addr_drop)
			statsp->errlog.l2_addr_drop++;
		if (drop_log.bits.class_code_drop)
			statsp->errlog.class_code_drop++;
		if (drop_log.bits.tcam_drop)
			statsp->errlog.tcam_drop++;
		if (drop_log.bits.vlan_drop)
			statsp->errlog.vlan_drop++;

		/* Collect the total drops for all kinds */
		(void) hpi_pfc_get_drop_counter(handle, &drop_count.value);
		statsp->drop_count += drop_count.bits.drop_count;
	}

	if (int_status.bits.tcam_parity_err) {
		statsp->tcam_parity_err++;

		(void) hpi_pfc_get_tcam_parity_log(handle, &tcam_par_err_log);
		statsp->errlog.tcam_par_err_log = tcam_par_err_log.bits.addr;

		if (statsp->tcam_parity_err == 1)
			HXGE_ERROR_MSG((hxgep,
			    INT_CTL, " TCAM parity error addr: 0x%x",
			    tcam_par_err_log.bits.addr));
	}

	if (int_status.bits.vlan_parity_err) {
		statsp->vlan_parity_err++;

		(void) hpi_pfc_get_vlan_parity_log(handle, &vlan_par_err_log);
		statsp->errlog.vlan_par_err_log = vlan_par_err_log.bits.addr;

		if (statsp->vlan_parity_err == 1)
			HXGE_ERROR_MSG((hxgep, INT_CTL,
			    " vlan table parity error addr: 0x%x",
			    vlan_par_err_log.bits.addr));
	}

	(void) hpi_pfc_get_bad_csum_counter(handle, &bad_cs_count.value);
	statsp->bad_cs_count += bad_cs_count.bits.bad_cs_count;

	(void) hpi_pfc_clear_interrupt_status(handle);
	return (DDI_INTR_CLAIMED);
}

static void
hxge_pfc_get_next_mac_addr(uint8_t *st_mac, struct ether_addr *final_mac)
{
	uint64_t	mac[ETHERADDRL];
	uint64_t	mac_addr = 0;
	int		i, j;

	for (i = ETHERADDRL - 1, j = 0; j < ETHERADDRL; i--, j++) {
		mac[j] = st_mac[i];
		mac_addr |= (mac[j] << (j*8));
	}

	final_mac->ether_addr_octet[0] = (mac_addr & 0xff0000000000) >> 40;
	final_mac->ether_addr_octet[1] = (mac_addr & 0xff00000000) >> 32;
	final_mac->ether_addr_octet[2] = (mac_addr & 0xff000000) >> 24;
	final_mac->ether_addr_octet[3] = (mac_addr & 0xff0000) >> 16;
	final_mac->ether_addr_octet[4] = (mac_addr & 0xff00) >> 8;
	final_mac->ether_addr_octet[5] = (mac_addr & 0xff);
}

hxge_status_t
hxge_pfc_mac_addrs_get(p_hxge_t hxgep)
{
	hxge_status_t	status = HXGE_OK;
	hpi_status_t	hpi_status = HPI_SUCCESS;
	hpi_handle_t	handle = HXGE_DEV_HPI_HANDLE(hxgep);
	uint8_t		mac_addr[ETHERADDRL];

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_mac_addr_get"));

	hpi_status = hpi_pfc_mac_addr_get_i(handle, mac_addr, 0);
	if (hpi_status != HPI_SUCCESS) {
		status = (HXGE_ERROR | hpi_status);
		HXGE_ERROR_MSG((hxgep, HXGE_ERR_CTL,
		    "hxge_pfc_mac_addr_get: pfc_mac_addr_get_i failed"));
		goto exit;
	}

	hxge_pfc_get_next_mac_addr(mac_addr, &hxgep->factaddr);
	HXGE_ERROR_MSG((hxgep, PFC_CTL, "MAC Addr(0): %x:%x:%x:%x:%x:%x\n",
	    mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3],
	    mac_addr[4], mac_addr[5]));

exit:
	HXGE_DEBUG_MSG((hxgep, CFG_CTL, "<== hxge_pfc_mac_addr_get, "
	    "status [0x%x]", status));
	return (status);
}

/*
 * Calculate the bit in the multicast address filter
 * that selects the given * address.
 * Note: For Hydra, the last 8-bits are used.
 */
static uint32_t
crc32_mchash(p_ether_addr_t addr)
{
	uint8_t		*cp;
	uint32_t	crc;
	uint32_t	c;
	int		byte;
	int		bit;

	cp = (uint8_t *)addr;
	crc = (uint32_t)0xffffffff;
	for (byte = 0; byte < ETHERADDRL; byte++) {
		/* Hydra calculates the hash backwardly */
		c = (uint32_t)cp[ETHERADDRL - 1 - byte];
		for (bit = 0; bit < 8; bit++) {
			if ((c & 0x1) ^ (crc & 0x1))
				crc = (crc >> 1)^0xedb88320;
			else
				crc = (crc >> 1);
			c >>= 1;
		}
	}
	return ((~crc) >> (32 - HASH_BITS));
}

static hxge_status_t
hxge_pfc_load_hash_table(p_hxge_t hxgep)
{
	uint32_t		i;
	uint16_t		hashtab_e;
	p_hash_filter_t		hash_filter;
	hpi_handle_t		handle;

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "==> hxge_pfc_load_hash_table\n"));
	handle = hxgep->hpi_reg_handle;

	/*
	 * Load the multicast hash filter bits.
	 */
	hash_filter = hxgep->hash_filter;
	for (i = 0; i < MAC_MAX_HASH_ENTRY; i++) {
		if (hash_filter != NULL) {
			hashtab_e = (uint16_t)hash_filter->hash_filter_regs[i];
		} else {
			hashtab_e = 0;
		}

		if (hpi_pfc_set_multicast_hash_table(handle, i,
		    hashtab_e) != HPI_SUCCESS)
			return (HXGE_ERROR);
	}

	HXGE_DEBUG_MSG((hxgep, PFC_CTL, "<== hxge_pfc_load_hash_table\n"));

	return (HXGE_OK);
}