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

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * IEEE 802.3ad Link Aggregation -- IOCTL processing.
 */

#include <sys/ddi.h>
#include <sys/aggr.h>
#include <sys/aggr_impl.h>
#include <sys/strsun.h>

static int aggr_ioc_create(mblk_t *, int);
static int aggr_ioc_delete(mblk_t *, int);
static int aggr_ioc_info(mblk_t *, int);
static int aggr_ioc_add(mblk_t *, int);
static int aggr_ioc_remove(mblk_t *, int);
static int aggr_ioc_status(mblk_t *, int);
static int aggr_ioc_modify(mblk_t *, int);

typedef struct ioc_cmd_s {
	int ic_cmd;
	int (*ic_func)(mblk_t *, int);
} ioc_cmd_t;

static ioc_cmd_t ioc_cmd[] = {
	{LAIOC_CREATE, aggr_ioc_create},
	{LAIOC_DELETE, aggr_ioc_delete},
	{LAIOC_INFO, aggr_ioc_info},
	{LAIOC_ADD, aggr_ioc_add},
	{LAIOC_REMOVE, aggr_ioc_remove},
	{LAIOC_MODIFY, aggr_ioc_modify}};

#define	IOC_CMD_SZ	(sizeof (ioc_cmd) / sizeof (ioc_cmd_t))

/*
 * Process a LAIOC_MODIFY request.
 */
static int
aggr_ioc_modify(mblk_t *mp, int mode)
{
	STRUCT_HANDLE(laioc_modify, modify_arg);
	uint32_t policy;
	boolean_t mac_fixed;
	uchar_t mac_addr[ETHERADDRL];
	uint8_t modify_mask_arg, modify_mask = 0;
	uint32_t rc, key;
	aggr_lacp_mode_t lacp_mode;
	aggr_lacp_timer_t lacp_timer;

	STRUCT_SET_HANDLE(modify_arg, mode, (void *)mp->b_cont->b_rptr);
	if (MBLKL(mp->b_cont) < STRUCT_SIZE(modify_arg))
		return (EINVAL);

	key = STRUCT_FGET(modify_arg, lu_key);
	modify_mask_arg = STRUCT_FGET(modify_arg, lu_modify_mask);

	if (modify_mask_arg & LAIOC_MODIFY_POLICY) {
		modify_mask |= AGGR_MODIFY_POLICY;
		policy = STRUCT_FGET(modify_arg, lu_policy);
	}

	if (modify_mask_arg & LAIOC_MODIFY_MAC) {
		modify_mask |= AGGR_MODIFY_MAC;
		bcopy(STRUCT_FGET(modify_arg, lu_mac), mac_addr, ETHERADDRL);
		mac_fixed = STRUCT_FGET(modify_arg, lu_mac_fixed);
	}

	if (modify_mask_arg & LAIOC_MODIFY_LACP_MODE) {
		modify_mask |= AGGR_MODIFY_LACP_MODE;
		lacp_mode = STRUCT_FGET(modify_arg, lu_lacp_mode);
	}

	if (modify_mask_arg & LAIOC_MODIFY_LACP_TIMER) {
		modify_mask |= AGGR_MODIFY_LACP_TIMER;
		lacp_timer = STRUCT_FGET(modify_arg, lu_lacp_timer);
	}

	rc = aggr_grp_modify(key, NULL, modify_mask, policy, mac_fixed,
	    mac_addr, lacp_mode, lacp_timer);

	freemsg(mp->b_cont);
	mp->b_cont = NULL;
	return (rc);
}

/*
 * Process a LAIOC_CREATE request.
 */
static int
aggr_ioc_create(mblk_t *mp, int mode)
{
	STRUCT_HANDLE(laioc_create, create_arg);
	uint16_t nports;
	laioc_port_t *ports = NULL;
	uint32_t policy;
	boolean_t mac_fixed;
	uchar_t mac_addr[ETHERADDRL];
	aggr_lacp_mode_t lacp_mode;
	aggr_lacp_timer_t lacp_timer;
	int rc, len;

	STRUCT_SET_HANDLE(create_arg, mode, (void *)mp->b_cont->b_rptr);
	if ((len = MBLKL(mp->b_cont)) < STRUCT_SIZE(create_arg))
		return (EINVAL);

	nports = STRUCT_FGET(create_arg, lc_nports);
	if (nports > AGGR_MAX_PORTS)
		return (EINVAL);

	policy = STRUCT_FGET(create_arg, lc_policy);
	lacp_mode = STRUCT_FGET(create_arg, lc_lacp_mode);
	lacp_timer = STRUCT_FGET(create_arg, lc_lacp_timer);

	if (len < STRUCT_SIZE(create_arg) + (nports * sizeof (laioc_port_t)))
		return (EINVAL);

	ports = (laioc_port_t *)(STRUCT_BUF(create_arg) + 1);

	bcopy(STRUCT_FGET(create_arg, lc_mac), mac_addr, ETHERADDRL);
	mac_fixed = STRUCT_FGET(create_arg, lc_mac_fixed);

	rc = aggr_grp_create(STRUCT_FGET(create_arg, lc_key),
	    nports, ports, policy, mac_fixed, mac_addr, lacp_mode, lacp_timer);

	freemsg(mp->b_cont);
	mp->b_cont = NULL;
	return (rc);
}

static int
aggr_ioc_delete(mblk_t *mp, int mode)
{
	STRUCT_HANDLE(laioc_delete, delete_arg);
	int rc;

	STRUCT_SET_HANDLE(delete_arg, mode, (void *)mp->b_cont->b_rptr);
	if (STRUCT_SIZE(delete_arg) > MBLKL(mp))
		return (EINVAL);

	rc = aggr_grp_delete(STRUCT_FGET(delete_arg, ld_key));

	freemsg(mp->b_cont);
	mp->b_cont = NULL;
	return (rc);
}

typedef struct aggr_ioc_info_state {
	uint32_t bytes_left;
	uchar_t *where;
} aggr_ioc_info_state_t;

static int
aggr_ioc_info_new_grp(void *arg, uint32_t key, uchar_t *mac,
    boolean_t mac_fixed, uint32_t policy, uint32_t nports,
    aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer)
{
	aggr_ioc_info_state_t *state = arg;
	laioc_info_group_t grp;

	if (state->bytes_left < sizeof (grp))
		return (ENOSPC);

	grp.lg_key = key;
	bcopy(mac, grp.lg_mac, ETHERADDRL);
	grp.lg_mac_fixed = mac_fixed;
	grp.lg_policy = policy;
	grp.lg_nports = nports;
	grp.lg_lacp_mode = lacp_mode;
	grp.lg_lacp_timer = lacp_timer;

	bcopy(&grp, state->where, sizeof (grp));
	state->where += sizeof (grp);
	state->bytes_left -= sizeof (grp);

	return (0);
}

static int
aggr_ioc_info_new_port(void *arg, char *devname, uchar_t *mac,
    aggr_port_state_t portstate, aggr_lacp_state_t *lacp_state)
{
	aggr_ioc_info_state_t *state = arg;
	laioc_info_port_t port;

	if (state->bytes_left < sizeof (port))
		return (ENOSPC);

	bcopy(devname, port.lp_devname, MAXNAMELEN + 1);
	bcopy(mac, port.lp_mac, ETHERADDRL);
	port.lp_state = portstate;
	port.lp_lacp_state = *lacp_state;

	bcopy(&port, state->where, sizeof (port));
	state->where += sizeof (port);
	state->bytes_left -= sizeof (port);

	return (0);
}

/*ARGSUSED*/
static int
aggr_ioc_info(mblk_t *mp, int mode)
{
	laioc_info_t *info_argp;
	uint32_t ngroups, group_key;
	int rc, len;
	aggr_ioc_info_state_t state;

	if ((len = MBLKL(mp->b_cont)) < sizeof (*info_argp))
		return (EINVAL);

	info_argp = (laioc_info_t *)mp->b_cont->b_rptr;
	/*
	 * Key of the group to return. If zero, the call returns information
	 * regarding all groups currently defined.
	 */
	group_key = info_argp->li_group_key;

	state.bytes_left = len - sizeof (laioc_info_t);
	state.where = (uchar_t *)(info_argp + 1);

	rc = aggr_grp_info(&ngroups, group_key, &state, aggr_ioc_info_new_grp,
	    aggr_ioc_info_new_port);
	if (rc == 0)
		info_argp->li_ngroups = ngroups;

	return (rc);
}

static int
aggr_ioc_add(mblk_t *mp, int mode)
{
	STRUCT_HANDLE(laioc_add_rem, add_arg);
	uint32_t nports;
	laioc_port_t *ports = NULL;
	int rc, len;

	STRUCT_SET_HANDLE(add_arg, mode, (void *)mp->b_cont->b_rptr);
	if ((len = MBLKL(mp->b_cont)) < STRUCT_SIZE(add_arg))
		return (EINVAL);

	nports = STRUCT_FGET(add_arg, la_nports);
	if (nports > AGGR_MAX_PORTS)
		return (EINVAL);

	if (len < STRUCT_SIZE(add_arg) + (nports * sizeof (laioc_port_t)))
		return (EINVAL);

	ports = (laioc_port_t *)(STRUCT_BUF(add_arg) + 1);

	rc = aggr_grp_add_ports(STRUCT_FGET(add_arg, la_key),
	    nports, ports);

	freemsg(mp->b_cont);
	mp->b_cont = NULL;
	return (rc);
}

static int
aggr_ioc_remove(mblk_t *mp, int mode)
{
	STRUCT_HANDLE(laioc_add_rem, rem_arg);
	uint32_t nports;
	laioc_port_t *ports = NULL;
	int rc, len;

	STRUCT_SET_HANDLE(rem_arg, mode, (void *)mp->b_cont->b_rptr);
	if ((len = MBLKL(mp->b_cont)) < STRUCT_SIZE(rem_arg))
		return (EINVAL);

	nports = STRUCT_FGET(rem_arg, la_nports);
	if (nports > AGGR_MAX_PORTS)
		return (EINVAL);

	if (len < STRUCT_SIZE(rem_arg) + (nports * sizeof (laioc_port_t)))
		return (EINVAL);

	ports = (laioc_port_t *)(STRUCT_BUF(rem_arg) + 1);

	rc = aggr_grp_rem_ports(STRUCT_FGET(rem_arg, la_key),
	    nports, ports);

	freemsg(mp->b_cont);
	mp->b_cont = NULL;
	return (rc);
}

void
aggr_ioctl(queue_t *wq, mblk_t *mp)
{
	struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
	int i, err = EINVAL;
	mblk_t *nmp;

	if (mp->b_cont == NULL)
		goto done;

	/*
	 * Construct contiguous message
	 */
	if ((nmp = msgpullup(mp->b_cont, -1)) == NULL) {
		err = ENOMEM;
		goto done;
	}

	freemsg(mp->b_cont);
	mp->b_cont = nmp;

	for (i = 0; i < IOC_CMD_SZ; i++) {
		if (iocp->ioc_cmd == ioc_cmd[i].ic_cmd) {
			err = ioc_cmd[i].ic_func(mp, (int)iocp->ioc_flag);
			break;
		}
	}

	if (err == 0) {
		int len = 0;

		if (mp->b_cont != NULL) {
			len = MBLKL(mp->b_cont);
		}
		miocack(wq, mp, len, 0);
		return;
	}

done:
	miocnak(wq, mp, 0, err);
}