/*
 * 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.
 */

/*
 * IEEE 802.3ad Link Aggregation - Receive
 *
 * Implements the collector function.
 * Manages the RX resources exposed by a link aggregation group.
 */

#include <sys/sysmacros.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
#include <sys/byteorder.h>
#include <sys/aggr.h>
#include <sys/aggr_impl.h>

static void
aggr_mac_rx(mac_handle_t lg_mh, mac_resource_handle_t mrh, mblk_t *mp)
{
	if (mrh == NULL) {
		mac_rx(lg_mh, mrh, mp);
	} else {
		aggr_pseudo_rx_ring_t	*ring = (aggr_pseudo_rx_ring_t *)mrh;
		mac_rx_ring(lg_mh, ring->arr_rh, mp, ring->arr_gen);
	}
}

void
aggr_recv_lacp(aggr_port_t *port, mac_resource_handle_t mrh, mblk_t *mp)
{
	aggr_grp_t *grp = port->lp_grp;

	/* in promiscuous mode, send copy of packet up */
	if (grp->lg_promisc) {
		mblk_t *nmp = copymsg(mp);

		if (nmp != NULL)
			aggr_mac_rx(grp->lg_mh, mrh, nmp);
	}

	aggr_lacp_rx_enqueue(port, mp);
}

/*
 * Callback function invoked by MAC service module when packets are
 * made available by a MAC port.
 */
/* ARGSUSED */
void
aggr_recv_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp,
    boolean_t loopback)
{
	aggr_port_t *port = (aggr_port_t *)arg;
	aggr_grp_t *grp = port->lp_grp;

	if (grp->lg_lacp_mode == AGGR_LACP_OFF) {
		aggr_mac_rx(grp->lg_mh, mrh, mp);
	} else {
		mblk_t *cmp, *last, *head;
		struct ether_header *ehp;
		uint16_t sap;

		/* filter out slow protocol packets (LACP & Marker) */
		last = NULL;
		head = cmp = mp;
		while (cmp != NULL) {
			if (MBLKL(cmp) < sizeof (struct ether_header)) {
				/* packet too short */
				if (head == cmp) {
					/* no packets accumulated */
					head = cmp->b_next;
					cmp->b_next = NULL;
					freemsg(cmp);
					cmp = head;
				} else {
					/* send up accumulated packets */
					last->b_next = NULL;
					if (port->lp_collector_enabled) {
						aggr_mac_rx(grp->lg_mh, mrh,
						    head);
					} else {
						freemsgchain(head);
					}
					head = cmp->b_next;
					cmp->b_next = NULL;
					freemsg(cmp);
					cmp = head;
					last = NULL;
				}
				continue;
			}
			ehp = (struct ether_header *)cmp->b_rptr;

			sap = ntohs(ehp->ether_type);
			if (sap == ETHERTYPE_SLOW) {
				/*
				 * LACP or Marker packet. Send up pending
				 * chain, and send LACP/Marker packet
				 * to LACP subsystem.
				 */
				if (head == cmp) {
					/* first packet of chain */
					ASSERT(last == NULL);
					head = cmp->b_next;
					cmp->b_next = NULL;
					aggr_recv_lacp(port, mrh, cmp);
					cmp = head;
				} else {
					/* previously accumulated packets */
					ASSERT(last != NULL);
					/* send up non-LACP packets */
					last->b_next = NULL;
					if (port->lp_collector_enabled) {
						aggr_mac_rx(grp->lg_mh, mrh,
						    head);
					} else {
						freemsgchain(head);
					}
					/* unlink and pass up LACP packets */
					head = cmp->b_next;
					cmp->b_next = NULL;
					aggr_recv_lacp(port, mrh, cmp);
					cmp = head;
					last = NULL;
				}
			} else {
				last = cmp;
				cmp = cmp->b_next;
			}
		}
		if (head != NULL) {
			if (port->lp_collector_enabled)
				aggr_mac_rx(grp->lg_mh, mrh, head);
			else
				freemsgchain(head);
		}
	}
}