/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/rwlock.h>
#include <sys/errno.h>
#include <sys/queue.h>
#include <inet/common.h>
#include <inet/led.h>
#include <inet/ip.h>
#include <sys/neti.h>
#include <sys/zone.h>

static net_handle_t net_find(const char *protocol, neti_stack_t *ns);

static net_handle_t
net_find(const char *protocol, neti_stack_t *nts)
{
	struct net_data *n;

	ASSERT(protocol != NULL);
	ASSERT(nts != NULL);

	LIST_FOREACH(n, &nts->nts_netd_head, netd_list) {
		ASSERT(n->netd_info.netp_name != NULL);
		/*
		 * If they're trying to find a protocol that is being
		 * shutdown, just ignore it..
		 */
		if (n->netd_condemned != 0)
			continue;
		if (strcmp(n->netd_info.netp_name, protocol) == 0) {
			break;
		}
	}

	return (n);
}

net_handle_t
net_protocol_register(netid_t id, const net_protocol_t *info)
{
	struct net_data *n, *new;
	neti_stack_t *nts;

	ASSERT(info != NULL);

	nts = net_getnetistackbyid(id);
	if (nts == NULL)
		return (NULL);

	new = kmem_alloc(sizeof (*new), KM_SLEEP);
	new->netd_refcnt = 1;
	new->netd_hooks = NULL;
	new->netd_info = *info;
	new->netd_stack = nts;
	new->netd_condemned = 0;

	mutex_enter(&nts->nts_lock);
	n = net_find(info->netp_name, nts);
	if (n != NULL) {
		mutex_exit(&nts->nts_lock);
		kmem_free(new, sizeof (*new));
		return (NULL);
	}

	if (LIST_EMPTY(&nts->nts_netd_head)) {
		LIST_INSERT_HEAD(&nts->nts_netd_head, new, netd_list);
	} else {
		LIST_INSERT_AFTER(LIST_FIRST(&nts->nts_netd_head),
		    new, netd_list);
	}
	mutex_exit(&nts->nts_lock);

	return (new);
}

int
net_protocol_unregister(net_handle_t info)
{
	neti_stack_t *nts;

	ASSERT(info != NULL);

	nts = info->netd_stack;
	ASSERT(nts != NULL);

	mutex_enter(&nts->nts_lock);
	LIST_REMOVE(info, netd_list);
	info->netd_stack = NULL;
	mutex_exit(&nts->nts_lock);

	(void) net_protocol_release(info);

	return (0);
}

net_handle_t
net_protocol_lookup(netid_t netid, const char *protocol)
{
	neti_stack_t *nts;
	net_handle_t nd;

	ASSERT(protocol != NULL);

	nts = net_getnetistackbyid(netid);
	if (nts == NULL)
		return (NULL);

	mutex_enter(&nts->nts_lock);
	nd = net_find(protocol, nts);
	if (nd != NULL)
		atomic_add_32((uint_t *)&nd->netd_refcnt, 1);
	mutex_exit(&nts->nts_lock);
	return (nd);
}

/*
 * Note: the man page specifies "returns -1 if the value passed in is unknown
 * to this framework".  We are not doing a lookup in this function, just a
 * simply add to the netd_refcnt of the net_handle_t passed in, so -1 is never a
 * return value.
 */
int
net_protocol_release(net_handle_t info)
{

	ASSERT(info->netd_refcnt > 0);
	/*
	 * Is this safe? No hold on nts_lock? Consider that if the caller
	 * of net_protocol_release() is going to free this structure then
	 * it is now the only owner (refcnt==1) and it will have been
	 * removed from the nts_netd_head list on the neti_stack_t from a
	 * call to net_protocol_unregister already, so it is thus an orphan.
	 */
	if (atomic_add_32_nv((uint_t *)&info->netd_refcnt, -1) == 0) {
		ASSERT(info->netd_hooks == NULL);
		ASSERT(info->netd_stack == NULL);
		kmem_free(info, sizeof (struct net_data));
	}

	return (0);
}

net_handle_t
net_protocol_walk(netid_t netid, net_handle_t info)
{
	struct net_data *n = NULL;
	boolean_t found = B_FALSE;
	neti_stack_t *nts;

	nts = net_getnetistackbyid(netid);
	ASSERT(nts != NULL);

	if (info == NULL)
		found = B_TRUE;

	mutex_enter(&nts->nts_lock);
	LIST_FOREACH(n, &nts->nts_netd_head, netd_list) {
		if (found) {
			/*
			 * We are only interested in finding protocols that
			 * are not in some sort of shutdown state.  There is
			 * no need to check for netd_stack==NULL because
			 * that implies it is no longer on this list.
			 */
			if (n->netd_condemned == 0)
				continue;
			break;
		}

		if (n == info)
			found = B_TRUE;
	}

	if (info != NULL)
		(void) net_protocol_release(info);

	if (n != NULL)
		atomic_add_32((uint_t *)&n->netd_refcnt, 1);

	mutex_exit(&nts->nts_lock);

	return (n);
}

/*
 * Public accessor functions
 */
int
net_getifname(net_handle_t info, phy_if_t nic, char *buffer,
    const size_t buflen)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_getifname(info, nic, buffer, buflen));
}

int
net_getmtu(net_handle_t info, phy_if_t nic, lif_if_t ifdata)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_getmtu(info, nic, ifdata));
}

int
net_getpmtuenabled(net_handle_t info)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_getpmtuenabled(info));
}

int
net_getlifaddr(net_handle_t info, phy_if_t nic, lif_if_t ifdata,
    int nelem, net_ifaddr_t type[], void *storage)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_getlifaddr(info, nic, ifdata,
	    nelem, type, storage));
}

int
net_getlifzone(net_handle_t info, phy_if_t phy_ifdata, lif_if_t ifdata,
    zoneid_t *zoneid)
{
	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.neti_getlifzone(info, phy_ifdata, ifdata,
	    zoneid));
}

int
net_getlifflags(net_handle_t info, phy_if_t phy_ifdata, lif_if_t ifdata,
    uint64_t *flags)
{
	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.neti_getlifflags(info, phy_ifdata, ifdata,
	    flags));
}

phy_if_t
net_phygetnext(net_handle_t info, phy_if_t nic)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return ((phy_if_t)-1);

	return (info->netd_info.netp_phygetnext(info, nic));
}

phy_if_t
net_phylookup(net_handle_t info, const char *name)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return ((phy_if_t)-1);

	return (info->netd_info.netp_phylookup(info, name));
}

lif_if_t
net_lifgetnext(net_handle_t info, phy_if_t ifidx, lif_if_t ifdata)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return ((lif_if_t)-1);

	return (info->netd_info.netp_lifgetnext(info, ifidx, ifdata));
}

int
net_inject(net_handle_t info, inject_t style, net_inject_t *packet)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_inject(info, style, packet));
}

phy_if_t
net_routeto(net_handle_t info, struct sockaddr *address, struct sockaddr *next)
{

	ASSERT(info != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return ((phy_if_t)-1);

	return (info->netd_info.netp_routeto(info, address, next));
}

int
net_ispartialchecksum(net_handle_t info, mblk_t *mp)
{

	ASSERT(info != NULL);
	ASSERT(mp != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_ispartialchecksum(info, mp));
}

int
net_isvalidchecksum(net_handle_t info, mblk_t *mp)
{

	ASSERT(info != NULL);
	ASSERT(mp != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (-1);

	return (info->netd_info.netp_isvalidchecksum(info, mp));
}

/*
 * Hooks related functions
 */

/*
 * Function:	net_family_register
 * Returns:	int - 0 = Succ, Else = Fail
 * Parameters:	info(I) - protocol
 *		hf(I) - family pointer
 *
 * Call hook_family_add to register family
 *
 * There is no need to bump netd_refcnt in the two functions
 * net_family_register and net_family_unregister because the caller of these
 * two functions is assumed to "own" a reference on 'info' via an earlier
 * call to net_protocol_register().  Thus the owner is expected to do a
 * call to net_protocol_unregister() after having done a
 * net_family_unregister() to make sure things are properly cleaned up.
 * Passing a pointer to info->netd_hooks into hook_family_add is required
 * so that this can be set before the notify functions are called. If this
 * does not happen, the notify function may do something that seems fine,
 * like add a notify function to the family but cause a panic because
 * netd_hooks is NULL when we get to hook_family_notify_register.
 */
int
net_family_register(net_handle_t info, hook_family_t *hf)
{
	netstack_t *ns;

	ASSERT(info != NULL);
	ASSERT(hf != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (ESHUTDOWN);

	if (info->netd_hooks != NULL)
		return (EEXIST);

	ns = info->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	if (hook_family_add(hf, ns->netstack_hook,
	    (void **)&info->netd_hooks) == NULL)
		return (EEXIST);

	return (0);
}

/*
 * Function:	net_family_unregister
 * Returns:	int - transparent value, explained by caller
 * Parameters:	info(I) - protocol
 *		hf(I) - family pointer
 *
 * Call hook_family_remove to unregister family
 */
int
net_family_unregister(net_handle_t info, hook_family_t *hf)
{
	int ret;

	ASSERT(info != NULL);
	ASSERT(hf != NULL);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	if (strcmp(info->netd_hooks->hfi_family.hf_name,
	    hf->hf_name) != 0)
		return (EINVAL);

	ret = hook_family_remove(info->netd_hooks);
	if (ret == 0)
		info->netd_hooks = NULL;

	return (ret);
}

int
net_family_shutdown(net_handle_t info, hook_family_t *hf)
{

	ASSERT(info != NULL);
	ASSERT(hf != NULL);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	if (strcmp(info->netd_hooks->hfi_family.hf_name,
	    hf->hf_name) != 0)
		return (EINVAL);

	return (hook_family_shutdown(info->netd_hooks));
}

/*
 * Function:	net_event_register
 * Returns:	internal event pointer - NULL = Fail
 * Parameters:	info(I) - protocol
 *		he(I) - event pointer
 *
 * Call hook_event_add to register event on specific family
 * 	Internal event pointer is returned so caller can get
 * 	handle to run hooks
 */
hook_event_token_t
net_event_register(net_handle_t info, hook_event_t *he)
{
	hook_event_int_t *hei;

	ASSERT(info != NULL);
	ASSERT(he != NULL);

	if (info->netd_hooks == NULL || info->netd_condemned != 0 ||
	    info->netd_stack == NULL)
		return (NULL);

	hei = hook_event_add(info->netd_hooks, he);
	return ((hook_event_token_t)hei);
}

/*
 * Function:	net_event_unregister
 * Returns:	int - transparent value, explained by caller
 * Parameters:	info(I) - protocol
 *		he(I) - event pointer
 *
 * Call hook_event_remove to unregister event on specific family
 */
int
net_event_unregister(net_handle_t info, hook_event_t *he)
{

	ASSERT(info != NULL);
	ASSERT(he != NULL);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	return (hook_event_remove(info->netd_hooks, he));
}

int
net_event_shutdown(net_handle_t info, hook_event_t *he)
{

	ASSERT(info != NULL);
	ASSERT(he != NULL);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	return (hook_event_shutdown(info->netd_hooks, he));
}

/*
 * Function:	net_hook_register
 * Returns:	int - transparent value, explained by caller
 * Parameters:	info(I) - protocol
 *		event(I) - event name
 *		h(I) - hook pointer
 *
 * Call hook_register to add hook on specific family/event
 */
int
net_hook_register(net_handle_t info, char *event, hook_t *h)
{

	ASSERT(info != NULL);
	ASSERT(event != NULL);
	ASSERT(h != NULL);

	if (info->netd_condemned != 0 || info->netd_stack == NULL)
		return (ESHUTDOWN);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	return (hook_register(info->netd_hooks, event, h));
}

/*
 * Function:	net_hook_unregister
 * Returns:	int - transparent value, explained by caller
 * Parameters:	info(I) - protocol
 *		event(I) - event name
 *		h(I) - hook pointer
 *
 * Call hook_unregister to remove hook on specific family/event
 */
int
net_hook_unregister(net_handle_t info, char *event, hook_t *h)
{

	ASSERT(info != NULL);
	ASSERT(event != NULL);
	ASSERT(h != NULL);

	if (info->netd_hooks == NULL)
		return (ENXIO);

	return (hook_unregister(info->netd_hooks, event, h));
}

netid_t
net_getnetid(net_handle_t netd)
{

	if (netd->netd_stack == NULL)
		return (-1);
	return (netd->netd_stack->nts_id);
}

net_inject_t *
net_inject_alloc(const int version)
{
	net_inject_t *ni;

	ni = kmem_zalloc(sizeof (*ni), KM_NOSLEEP);
	if (ni == NULL)
		return (NULL);

	ni->ni_version = version;
	return (ni);
}

void
net_inject_free(net_inject_t *ni)
{
	kmem_free(ni, sizeof (*ni));
}

kstat_t *
net_kstat_create(netid_t netid, char *module, int instance, char *name,
    char *class, uchar_t type, ulong_t ndata, uchar_t ks_flag)
{
	netstackid_t stackid = net_getnetstackidbynetid(netid);

	if (stackid == -1)
		return (NULL);

	return (kstat_create_netstack(module, instance, name, class, type,
	    ndata, ks_flag, stackid));
}

void
net_kstat_delete(netid_t netid, kstat_t *ks)
{
	netstackid_t stackid = net_getnetstackidbynetid(netid);

	if (stackid != -1)
		kstat_delete_netstack(ks, stackid);
}

int
net_event_notify_register(net_handle_t family, char *event,
    hook_notify_fn_t callback, void *arg)
{
	int error;

	if (family->netd_condemned != 0 || family->netd_stack == NULL)
		return (ESHUTDOWN);

	error = hook_event_notify_register(family->netd_hooks, event,
	    callback, arg);

	return (error);
}

int
net_event_notify_unregister(net_handle_t family, char *event,
    hook_notify_fn_t callback)
{
	int error;

	error = hook_event_notify_unregister(family->netd_hooks, event,
	    callback);

	return (error);
}

int
net_protocol_notify_register(net_handle_t family, hook_notify_fn_t callback,
    void *arg)
{
	int error;

	if (family->netd_condemned != 0 || family->netd_stack == NULL)
		return (ESHUTDOWN);

	error = hook_family_notify_register(family->netd_hooks, callback,
	    arg);

	return (error);
}

int
net_protocol_notify_unregister(net_handle_t family, hook_notify_fn_t callback)
{
	int error;

	error = hook_family_notify_unregister(family->netd_hooks, callback);

	return (error);
}