/* SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 2018-2019 Solarflare Communications Inc.
 * All rights reserved.
 */

#include "efx.h"
#include "efx_impl.h"


#if EFSYS_OPT_EVB

#if EFSYS_OPT_SIENA
static const efx_evb_ops_t	__efx_evb_dummy_ops = {
	NULL,		/* eeo_init */
	NULL,		/* eeo_fini */
	NULL,		/* eeo_vswitch_alloc */
	NULL,		/* eeo_vswitch_free */
	NULL,		/* eeo_vport_alloc */
	NULL,		/* eeo_vport_free */
	NULL,		/* eeo_vport_mac_addr_add */
	NULL,		/* eeo_vport_mac_addr_del */
	NULL,		/* eeo_vadaptor_alloc */
	NULL,		/* eeo_vadaptor_free */
	NULL,		/* eeo_vport_assign */
	NULL,		/* eeo_vport_reconfigure */
	NULL,		/* eeo_vport_stats */
};
#endif /* EFSYS_OPT_SIENA */

#if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
static const efx_evb_ops_t	__efx_evb_ef10_ops = {
	ef10_evb_init,			/* eeo_init */
	ef10_evb_fini,			/* eeo_fini */
	ef10_evb_vswitch_alloc,		/* eeo_vswitch_alloc */
	ef10_evb_vswitch_free,		/* eeo_vswitch_free */
	ef10_evb_vport_alloc,		/* eeo_vport_alloc */
	ef10_evb_vport_free,		/* eeo_vport_free */
	ef10_evb_vport_mac_addr_add,	/* eeo_vport_mac_addr_add */
	ef10_evb_vport_mac_addr_del,	/* eeo_vport_mac_addr_del */
	ef10_evb_vadaptor_alloc,	/* eeo_vadaptor_alloc */
	ef10_evb_vadaptor_free,		/* eeo_vadaptor_free */
	ef10_evb_vport_assign,		/* eeo_vport_assign */
	ef10_evb_vport_reconfigure,	/* eeo_vport_reconfigure */
	ef10_evb_vport_stats,		/* eeo_vport_stats */
};
#endif /* EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2 */

	__checkReturn	efx_rc_t
efx_evb_init(
	__in		efx_nic_t *enp)
{
	const efx_evb_ops_t *eeop;
	efx_rc_t rc;
	efx_nic_cfg_t *encp = &(enp->en_nic_cfg);

	EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
	EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_PROBE);
	EFSYS_ASSERT(!(enp->en_mod_flags & EFX_MOD_EVB));

	switch (enp->en_family) {
#if EFSYS_OPT_SIENA
	case EFX_FAMILY_SIENA:
		eeop = &__efx_evb_dummy_ops;
		break;
#endif /* EFSYS_OPT_SIENA */

#if EFSYS_OPT_HUNTINGTON
	case EFX_FAMILY_HUNTINGTON:
		eeop = &__efx_evb_ef10_ops;
		break;
#endif /* EFSYS_OPT_HUNTINGTON */

#if EFSYS_OPT_MEDFORD
	case EFX_FAMILY_MEDFORD:
		eeop = &__efx_evb_ef10_ops;
		break;
#endif /* EFSYS_OPT_MEDFORD */

#if EFSYS_OPT_MEDFORD2
	case EFX_FAMILY_MEDFORD2:
		eeop = &__efx_evb_ef10_ops;
		break;
#endif /* EFSYS_OPT_MEDFORD2 */

	default:
		EFSYS_ASSERT(0);
		rc = ENOTSUP;
		goto fail1;
	}

	if (!encp->enc_datapath_cap_evb || !eeop->eeo_init) {
		rc = ENOTSUP;
		goto fail2;
	}

	if ((rc = eeop->eeo_init(enp)) != 0)
		goto fail3;

	enp->en_eeop = eeop;
	enp->en_mod_flags |= EFX_MOD_EVB;
	return (0);

fail3:
	EFSYS_PROBE(fail3);
fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);

	return (rc);
}

			void
efx_evb_fini(
	__in		efx_nic_t *enp)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;

	EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_PROBE);
	EFSYS_ASSERT(!(enp->en_mod_flags & EFX_MOD_RX));
	EFSYS_ASSERT(!(enp->en_mod_flags & EFX_MOD_TX));

	if (eeop && eeop->eeo_fini)
		eeop->eeo_fini(enp);

	enp->en_eeop = NULL;
	enp->en_mod_flags &= ~EFX_MOD_EVB;
}

/*
 * efx_is_zero_eth_addr returns TRUE if the passed MAC address has all bytes
 * equal to zero. A vport is assigned a MAC address after creation and this
 * function checks if that has happened. It is called in the clean-up function
 * before calling eeo_vport_mac_addr_del to ensure that the vport actually had
 * an allocated MAC address.
 */

__checkReturn				boolean_t
efx_is_zero_eth_addr(
	__in_bcount(EFX_MAC_ADDR_LEN)	const uint8_t *addrp)
{
	return (!(addrp[0] | addrp[1] | addrp[2] |
		addrp[3] | addrp[4] | addrp[5]));
}

static			void
efx_evb_free_vport(
	__in		efx_nic_t *enp,
	__in		efx_vswitch_id_t vswitch_id,
	__inout		efx_vport_config_t *configp)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;

	/* If any callback fails, continue clean-up with others functions */
	if (EFX_VPORT_PCI_FUNCTION_IS_PF(configp)) {
		/* free vadaptor */
		if ((configp->evc_vport_id != EFX_VPORT_ID_INVALID) &&
		    (eeop->eeo_vadaptor_free(enp, vswitch_id,
		    configp->evc_vport_id) != 0)) {
			EFSYS_PROBE2(eeo_vadaptor_free,
			    uint16_t, configp->evc_function,
			    uint32_t, configp->evc_vport_id);
		}
	} else {
		if (configp->evc_vport_assigned == B_TRUE) {
			if (eeop->eeo_vport_assign(enp, vswitch_id,
			    EVB_PORT_ID_NULL,
			    configp->evc_function) != 0) {
				EFSYS_PROBE1(eeo_vport_assign,
				    uint16_t, configp->evc_function);
			}
			configp->evc_vport_assigned = B_FALSE;
		}
	}

	/*
	 * Call eeo_vport_mac_addr_del after checking that this vport is
	 * actually allocated a MAC address in call to efx_evb_configure_vport
	 */
	if (!efx_is_zero_eth_addr(configp->evc_mac_addr)) {
		if (eeop->eeo_vport_mac_addr_del(enp, vswitch_id,
		    configp->evc_vport_id,
		    configp->evc_mac_addr) != 0) {
			EFSYS_PROBE1(eeo_vport_mac_addr_del,
			    uint16_t, configp->evc_function);
		}
		memset(configp->evc_mac_addr, 0x00, EFX_MAC_ADDR_LEN);
	}

	if (configp->evc_vport_id != EFX_VPORT_ID_INVALID) {
		if (eeop->eeo_vport_free(enp, vswitch_id,
		    configp->evc_vport_id) != 0) {
			EFSYS_PROBE1(eeo_vport_free,
			    uint16_t, configp->evc_function);
		}
		configp->evc_vport_id = EFX_VPORT_ID_INVALID;
	}
}

static					void
efx_evb_free_vports(
	__in				efx_nic_t *enp,
	__in				efx_vswitch_id_t vswitch_id,
	__in				uint32_t num_vports,
	__inout_ecount(num_vports)	efx_vport_config_t *vport_configp)
{
	efx_vport_config_t *configp;
	uint32_t i;

	if (vport_configp == NULL) {
		EFSYS_PROBE(null_vport_config);
		return;
	}

	for (i = 0; i < num_vports; i++) {
		configp = vport_configp + i;
		efx_evb_free_vport(enp, vswitch_id, configp);
	}
}

static	__checkReturn	efx_rc_t
efx_evb_configure_vport(
	__in		efx_nic_t *enp,
	__in		efx_vswitch_id_t vswitch_id,
	__in		const efx_evb_ops_t *eeop,
	__inout		efx_vport_config_t *configp)
{
	efx_rc_t rc;
	efx_vport_id_t vport_id;

	if ((rc = eeop->eeo_vport_alloc(enp, vswitch_id,
			EFX_VPORT_TYPE_NORMAL, configp->evc_vid,
			configp->evc_vlan_restrict, &vport_id)) != 0)
		goto fail1;

	configp->evc_vport_id = vport_id;

	if ((rc = eeop->eeo_vport_mac_addr_add(enp, vswitch_id,
			configp->evc_vport_id,
			configp->evc_mac_addr)) != 0)
		goto fail2;

	if (EFX_VPORT_PCI_FUNCTION_IS_PF(configp)) {
		if ((rc = eeop->eeo_vadaptor_alloc(enp, vswitch_id,
				configp->evc_vport_id)) != 0)
			goto fail3;
	} else {
		if ((rc = eeop->eeo_vport_assign(enp, vswitch_id,
				configp->evc_vport_id,
				configp->evc_function)) != 0)
			goto fail4;
		configp->evc_vport_assigned = B_TRUE;
	}

	return (0);

fail4:
	EFSYS_PROBE(fail4);
fail3:
	EFSYS_PROBE(fail3);
fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);

	return (rc);
}

	__checkReturn			efx_rc_t
efx_evb_vswitch_create(
	__in				efx_nic_t *enp,
	__in				uint32_t num_vports,
	__inout_ecount(num_vports)	efx_vport_config_t *vport_configp,
	__deref_out			efx_vswitch_t **evpp)
{
	efx_vswitch_t *evp;
	efx_rc_t rc;
	efx_vswitch_id_t vswitch_id;
	efx_vport_config_t *configp;
	const efx_evb_ops_t *eeop = enp->en_eeop;
	uint32_t i;

	/* vport_configp is a caller allocated array filled in with vports
	 * configuration. Index 0 carries the PF vport configuration and next
	 * num_vports - 1 indices carry VFs configuration.
	 */
	EFSYS_ASSERT((num_vports != 0) && (vport_configp != NULL) &&
		(evpp != NULL));
	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);
	EFSYS_ASSERT(!(enp->en_mod_flags & EFX_MOD_NIC));

	if ((eeop->eeo_vswitch_alloc == NULL) ||
	    (eeop->eeo_vport_alloc == NULL) ||
	    (eeop->eeo_vport_free == NULL) ||
	    (eeop->eeo_vport_mac_addr_add == NULL) ||
	    (eeop->eeo_vport_mac_addr_del == NULL) ||
	    (eeop->eeo_vadaptor_alloc == NULL) ||
	    (eeop->eeo_vadaptor_free == NULL) ||
	    (eeop->eeo_vport_assign == NULL) ||
	    (eeop->eeo_vswitch_free == NULL)) {
		rc = ENOTSUP;
		goto fail1;
	}

	/* Allocate a vSwitch object */
	EFSYS_KMEM_ALLOC(enp->en_esip, sizeof (efx_vswitch_t), evp);

	if (evp == NULL) {
		rc = ENOMEM;
		goto fail2;
	}

	if ((rc = eeop->eeo_vswitch_alloc(enp, &vswitch_id)) != 0)
		goto fail3;

	evp->ev_enp = enp;
	evp->ev_num_vports = num_vports;
	evp->ev_evcp = vport_configp;
	evp->ev_vswitch_id = vswitch_id;

	for (i = 0; i < num_vports; i++) {
		configp = vport_configp + i;

		if ((rc = efx_evb_configure_vport(enp, vswitch_id, eeop,
				configp)) != 0)
			goto fail4;
	}

	enp->en_vswitchp = evp;
	*evpp = evp;
	return (0);

fail4:
	EFSYS_PROBE(fail4);
	efx_evb_free_vports(enp, vswitch_id, i + 1, vport_configp);
	/* Free the vSwitch */
	eeop->eeo_vswitch_free(enp, vswitch_id);

fail3:
	EFSYS_PROBE(fail3);
	/* Free the vSwitch object */
	EFSYS_KMEM_FREE(enp->en_esip, sizeof (efx_vswitch_t), evp);

fail2:
	EFSYS_PROBE(fail2);

fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);

	return (rc);
}

	__checkReturn			efx_rc_t
efx_evb_vport_mac_set(
	__in				efx_nic_t *enp,
	__in				efx_vswitch_t *evp,
	__in				efx_vport_id_t vport_id,
	__in_bcount(EFX_MAC_ADDR_LEN)	uint8_t *addrp)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;
	efx_rc_t rc;

	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);

	if (eeop->eeo_vport_reconfigure == NULL) {
		rc = ENOTSUP;
		goto fail1;
	}

	if (addrp == NULL) {
		rc = EINVAL;
		goto fail2;
	}

	rc = eeop->eeo_vport_reconfigure(enp, evp->ev_vswitch_id, vport_id,
		NULL, addrp, NULL);
	if (rc != 0)
		goto fail3;

	return (0);

fail3:
	EFSYS_PROBE(fail3);
fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);
	return (rc);
}

	__checkReturn	efx_rc_t
efx_evb_vport_vlan_set(
	__in		efx_nic_t *enp,
	__in		efx_vswitch_t *evp,
	__in		efx_vport_id_t vport_id,
	__in		uint16_t vid)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;
	efx_rc_t rc;

	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);

	if (eeop->eeo_vport_reconfigure == NULL) {
		rc = ENOTSUP;
		goto fail1;
	}

	rc = eeop->eeo_vport_reconfigure(enp, evp->ev_vswitch_id, vport_id,
		&vid, NULL, NULL);
	if (rc != 0)
		goto fail2;

	return (0);

fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);
	return (rc);
}

	__checkReturn			efx_rc_t
efx_evb_vport_reset(
	__in				efx_nic_t *enp,
	__in				efx_vswitch_t *evp,
	__in				efx_vport_id_t vport_id,
	__in_bcount(EFX_MAC_ADDR_LEN)	uint8_t *addrp,
	__in				uint16_t vid,
	__out				boolean_t *is_fn_resetp)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;
	efx_rc_t rc;

	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);

	if (eeop->eeo_vport_reconfigure == NULL) {
		rc = ENOTSUP;
		goto fail1;
	}

	if (is_fn_resetp == NULL) {
		rc = EINVAL;
		goto fail2;
	}

	rc = eeop->eeo_vport_reconfigure(enp, evp->ev_vswitch_id, vport_id,
		&vid, addrp, is_fn_resetp);
	if (rc != 0)
		goto fail3;

	return (0);

fail3:
	EFSYS_PROBE(fail3);
fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);
	return (rc);
}
	__checkReturn	efx_rc_t
efx_evb_vswitch_destroy(
	__in		efx_nic_t *enp,
	__in		efx_vswitch_t *evp)
{
	const efx_evb_ops_t *eeop = enp->en_eeop;
	efx_vswitch_id_t vswitch_id;
	efx_rc_t rc;

	EFSYS_ASSERT(evp != NULL);
	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);

	if ((eeop->eeo_vport_mac_addr_del == NULL) ||
	    (eeop->eeo_vadaptor_free == NULL) ||
	    (eeop->eeo_vport_assign == NULL) ||
	    (eeop->eeo_vport_free == NULL) ||
	    (eeop->eeo_vswitch_free == NULL)) {
		rc = ENOTSUP;
		goto fail1;
	}

	vswitch_id  = evp->ev_vswitch_id;
	efx_evb_free_vports(enp, vswitch_id,
		evp->ev_num_vports, evp->ev_evcp);

	/* Free the vSwitch object */
	EFSYS_KMEM_FREE(enp->en_esip, sizeof (efx_vswitch_t), evp);
	enp->en_vswitchp = NULL;

	/* Free the vSwitch */
	if ((rc = eeop->eeo_vswitch_free(enp, vswitch_id)) != 0)
		goto fail2;

	return (0);

fail2:
	EFSYS_PROBE(fail2);

fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);
	return (rc);
}

	__checkReturn	efx_rc_t
efx_evb_vport_stats(
	__in		efx_nic_t *enp,
	__in		efx_vswitch_t *evp,
	__in		efx_vport_id_t vport_id,
	__out		efsys_mem_t *stats_bufferp)
{
	efx_rc_t rc;
	const efx_evb_ops_t *eeop = enp->en_eeop;

	EFSYS_ASSERT(enp->en_mod_flags & EFX_MOD_EVB);

	if (eeop->eeo_vport_stats == NULL) {
		rc = ENOTSUP;
		goto fail1;
	}

	if (stats_bufferp == NULL) {
		rc = EINVAL;
		goto fail2;
	}

	rc = eeop->eeo_vport_stats(enp, evp->ev_vswitch_id,
		vport_id, stats_bufferp);
	if (rc != 0)
		goto fail3;

	return (0);

fail3:
	EFSYS_PROBE(fail3);
fail2:
	EFSYS_PROBE(fail2);
fail1:
	EFSYS_PROBE1(fail1, efx_rc_t, rc);
	return (rc);
}

#endif
