/* * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Joyent, Inc. */ /* * IEEE 802.3ad Link Aggregation - LACP & Marker Protocol processing. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct ether_addr etherzeroaddr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* * Slow_Protocol_Multicast address, as per IEEE 802.3ad spec. */ static struct ether_addr slow_multicast_addr = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x02 }; #ifdef DEBUG /* LACP state machine debugging support */ static uint32_t aggr_lacp_debug = 0; #define AGGR_LACP_DBG(x) if (aggr_lacp_debug) { (void) printf x; } #else #define AGGR_LACP_DBG(x) {} #endif /* DEBUG */ #define NSECS_PER_SEC 1000000000ll /* used by lacp_misconfig_walker() */ typedef struct lacp_misconfig_check_state_s { aggr_port_t *cs_portp; boolean_t cs_found; } lacp_misconfig_check_state_t; static const char *lacp_receive_str[] = LACP_RECEIVE_STATE_STRINGS; static const char *lacp_periodic_str[] = LACP_PERIODIC_STRINGS; static const char *lacp_mux_str[] = LACP_MUX_STRINGS; static uint16_t lacp_port_priority = 0x1000; static uint16_t lacp_system_priority = 0x1000; /* * Maintains a list of all ports in ATTACHED state. This information * is used to detect misconfiguration. */ typedef struct lacp_sel_ports { datalink_id_t sp_grp_linkid; datalink_id_t sp_linkid; /* Note: sp_partner_system must be 2-byte aligned */ struct ether_addr sp_partner_system; uint32_t sp_partner_key; struct lacp_sel_ports *sp_next; } lacp_sel_ports_t; static lacp_sel_ports_t *sel_ports = NULL; static kmutex_t lacp_sel_lock; static void periodic_timer_pop(void *); static void periodic_timer_pop_handler(aggr_port_t *); static void lacp_xmit_sm(aggr_port_t *); static void lacp_periodic_sm(aggr_port_t *); static void fill_lacp_pdu(aggr_port_t *, lacp_t *); static void fill_lacp_ether(aggr_port_t *, struct ether_header *); static void lacp_on(aggr_port_t *); static void lacp_off(aggr_port_t *); static boolean_t valid_lacp_pdu(aggr_port_t *, lacp_t *); static void lacp_receive_sm(aggr_port_t *, lacp_t *); static void aggr_set_coll_dist(aggr_port_t *, boolean_t); static void start_wait_while_timer(aggr_port_t *); static void stop_wait_while_timer(aggr_port_t *); static void lacp_reset_port(aggr_port_t *); static void stop_current_while_timer(aggr_port_t *); static void current_while_timer_pop(void *); static void current_while_timer_pop_handler(aggr_port_t *); static void update_default_selected(aggr_port_t *); static boolean_t update_selected(aggr_port_t *, lacp_t *); static boolean_t lacp_sel_ports_add(aggr_port_t *); static void lacp_sel_ports_del(aggr_port_t *); static void wait_while_timer_pop(void *); static void wait_while_timer_pop_handler(aggr_port_t *); void aggr_lacp_init(void) { mutex_init(&lacp_sel_lock, NULL, MUTEX_DEFAULT, NULL); } void aggr_lacp_fini(void) { mutex_destroy(&lacp_sel_lock); } /* * The following functions are used for handling LACP timers. * * Note that we cannot fully rely on the aggr's mac perimeter in the timeout * handler routine, otherwise it may cause deadlock with the untimeout() call * which is usually called with the mac perimeter held. Instead, a * lacp_timer_lock mutex is introduced, which protects a bitwise flag * (lacp_timer_bits). This flag is set/cleared by timeout()/stop_timer() * routines and is checked by a dedicated thread, that executes the real * timeout operation. */ static void aggr_port_timer_thread(void *arg) { aggr_port_t *port = arg; aggr_lacp_port_t *pl = &port->lp_lacp; aggr_grp_t *grp = port->lp_grp; uint32_t lacp_timer_bits; mac_perim_handle_t mph; callb_cpr_t cprinfo; CALLB_CPR_INIT(&cprinfo, &pl->lacp_timer_lock, callb_generic_cpr, "aggr_port_timer_thread"); mutex_enter(&pl->lacp_timer_lock); for (;;) { if ((lacp_timer_bits = pl->lacp_timer_bits) == 0) { CALLB_CPR_SAFE_BEGIN(&cprinfo); cv_wait(&pl->lacp_timer_cv, &pl->lacp_timer_lock); CALLB_CPR_SAFE_END(&cprinfo, &pl->lacp_timer_lock); continue; } pl->lacp_timer_bits = 0; if (lacp_timer_bits & LACP_THREAD_EXIT) break; if (lacp_timer_bits & LACP_PERIODIC_TIMEOUT) pl->periodic_timer.id = 0; if (lacp_timer_bits & LACP_WAIT_WHILE_TIMEOUT) pl->wait_while_timer.id = 0; if (lacp_timer_bits & LACP_CURRENT_WHILE_TIMEOUT) pl->current_while_timer.id = 0; mutex_exit(&pl->lacp_timer_lock); mac_perim_enter_by_mh(grp->lg_mh, &mph); if (port->lp_closing) { mac_perim_exit(mph); mutex_enter(&pl->lacp_timer_lock); break; } if (lacp_timer_bits & LACP_PERIODIC_TIMEOUT) periodic_timer_pop_handler(port); if (lacp_timer_bits & LACP_WAIT_WHILE_TIMEOUT) wait_while_timer_pop_handler(port); if (lacp_timer_bits & LACP_CURRENT_WHILE_TIMEOUT) current_while_timer_pop_handler(port); mac_perim_exit(mph); mutex_enter(&pl->lacp_timer_lock); if (pl->lacp_timer_bits & LACP_THREAD_EXIT) break; } pl->lacp_timer_bits = 0; pl->lacp_timer_thread = NULL; cv_broadcast(&pl->lacp_timer_cv); /* CALLB_CPR_EXIT drops the lock */ CALLB_CPR_EXIT(&cprinfo); /* * Release the reference of the grp so aggr_grp_delete() can call * mac_unregister() safely. */ aggr_grp_port_rele(port); thread_exit(); } /* * Set the port LACP state to SELECTED. Returns B_FALSE if the operation * could not be performed due to a memory allocation error, B_TRUE otherwise. */ static boolean_t lacp_port_select(aggr_port_t *portp) { ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); if (!lacp_sel_ports_add(portp)) return (B_FALSE); portp->lp_lacp.sm.selected = AGGR_SELECTED; return (B_TRUE); } /* * Set the port LACP state to UNSELECTED. */ static void lacp_port_unselect(aggr_port_t *portp) { aggr_grp_t *grp = portp->lp_grp; ASSERT((grp->lg_mh == NULL) || MAC_PERIM_HELD(grp->lg_mh)); lacp_sel_ports_del(portp); portp->lp_lacp.sm.selected = AGGR_UNSELECTED; } /* * Initialize group specific LACP state and parameters. */ void aggr_lacp_init_grp(aggr_grp_t *aggrp) { aggrp->aggr.PeriodicTimer = AGGR_LACP_TIMER_SHORT; aggrp->aggr.ActorSystemPriority = (uint16_t)lacp_system_priority; aggrp->aggr.CollectorMaxDelay = 10; aggrp->lg_lacp_mode = AGGR_LACP_OFF; aggrp->aggr.ready = B_FALSE; } /* * Complete LACP info initialization at port creation time. */ void aggr_lacp_init_port(aggr_port_t *portp) { aggr_grp_t *aggrp = portp->lp_grp; aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(aggrp->lg_mh == NULL || MAC_PERIM_HELD(aggrp->lg_mh)); ASSERT(MAC_PERIM_HELD(portp->lp_mh)); /* actor port # */ pl->ActorPortNumber = portp->lp_portid; AGGR_LACP_DBG(("aggr_lacp_init_port(%d): " "ActorPortNumber = 0x%x\n", portp->lp_linkid, pl->ActorPortNumber)); pl->ActorPortPriority = (uint16_t)lacp_port_priority; pl->ActorPortAggrId = 0; /* aggregator id - not used */ pl->NTT = B_FALSE; /* need to transmit */ pl->ActorAdminPortKey = aggrp->lg_key; pl->ActorOperPortKey = pl->ActorAdminPortKey; AGGR_LACP_DBG(("aggr_lacp_init_port(%d) " "ActorAdminPortKey = 0x%x, ActorAdminPortKey = 0x%x\n", portp->lp_linkid, pl->ActorAdminPortKey, pl->ActorOperPortKey)); /* Actor admin. port state */ pl->ActorAdminPortState.bit.activity = B_FALSE; pl->ActorAdminPortState.bit.timeout = B_TRUE; pl->ActorAdminPortState.bit.aggregation = B_TRUE; pl->ActorAdminPortState.bit.sync = B_FALSE; pl->ActorAdminPortState.bit.collecting = B_FALSE; pl->ActorAdminPortState.bit.distributing = B_FALSE; pl->ActorAdminPortState.bit.defaulted = B_FALSE; pl->ActorAdminPortState.bit.expired = B_FALSE; pl->ActorOperPortState = pl->ActorAdminPortState; /* * Partner Administrative Information * (All initialized to zero except for the following) * Fast Timeouts. */ pl->PartnerAdminPortState.bit.timeout = pl->PartnerOperPortState.bit.timeout = B_TRUE; pl->PartnerCollectorMaxDelay = 0; /* tens of microseconds */ /* * State machine information. */ pl->sm.lacp_on = B_FALSE; /* LACP Off default */ pl->sm.begin = B_TRUE; /* Prevents transmissions */ pl->sm.lacp_enabled = B_FALSE; pl->sm.port_enabled = B_FALSE; /* Link Down */ pl->sm.actor_churn = B_FALSE; pl->sm.partner_churn = B_FALSE; pl->sm.ready_n = B_FALSE; pl->sm.port_moved = B_FALSE; lacp_port_unselect(portp); pl->sm.periodic_state = LACP_NO_PERIODIC; pl->sm.receive_state = LACP_INITIALIZE; pl->sm.mux_state = LACP_DETACHED; pl->sm.churn_state = LACP_NO_ACTOR_CHURN; /* * Timer information. */ pl->current_while_timer.id = 0; pl->current_while_timer.val = SHORT_TIMEOUT_TIME; pl->periodic_timer.id = 0; pl->periodic_timer.val = FAST_PERIODIC_TIME; pl->wait_while_timer.id = 0; pl->wait_while_timer.val = AGGREGATE_WAIT_TIME; pl->lacp_timer_bits = 0; mutex_init(&pl->lacp_timer_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&pl->lacp_timer_cv, NULL, CV_DRIVER, NULL); pl->lacp_timer_thread = thread_create(NULL, 0, aggr_port_timer_thread, portp, 0, &p0, TS_RUN, minclsyspri); /* * Hold a reference of the grp and the port and this reference will * be release when the thread exits. * * The reference on the port is used for aggr_port_delete() to * continue without waiting for the thread to exit; the reference * on the grp is used for aggr_grp_delete() to wait for the thread * to exit before calling mac_unregister(). */ aggr_grp_port_hold(portp); } /* * Port initialization when we need to * turn LACP on/off, etc. Not everything is * reset like in the above routine. * Do NOT modify things like link status. */ static void lacp_reset_port(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); pl->NTT = B_FALSE; /* need to transmit */ /* reset operational port state */ pl->ActorOperPortState.bit.timeout = pl->ActorAdminPortState.bit.timeout; pl->ActorOperPortState.bit.sync = B_FALSE; pl->ActorOperPortState.bit.collecting = B_FALSE; pl->ActorOperPortState.bit.distributing = B_FALSE; pl->ActorOperPortState.bit.defaulted = B_TRUE; pl->ActorOperPortState.bit.expired = B_FALSE; pl->PartnerOperPortState.bit.timeout = B_TRUE; /* fast t/o */ pl->PartnerCollectorMaxDelay = 0; /* tens of microseconds */ /* * State machine information. */ pl->sm.begin = B_TRUE; /* Prevents transmissions */ pl->sm.actor_churn = B_FALSE; pl->sm.partner_churn = B_FALSE; pl->sm.ready_n = B_FALSE; lacp_port_unselect(portp); pl->sm.periodic_state = LACP_NO_PERIODIC; pl->sm.receive_state = LACP_INITIALIZE; pl->sm.mux_state = LACP_DETACHED; pl->sm.churn_state = LACP_NO_ACTOR_CHURN; /* * Timer information. */ pl->current_while_timer.val = SHORT_TIMEOUT_TIME; pl->periodic_timer.val = FAST_PERIODIC_TIME; } static void aggr_lacp_mcast_on(aggr_port_t *port) { ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh)); ASSERT(MAC_PERIM_HELD(port->lp_mh)); if (port->lp_state != AGGR_PORT_STATE_ATTACHED) return; (void) aggr_port_multicst(port, B_TRUE, (uchar_t *)&slow_multicast_addr); } static void aggr_lacp_mcast_off(aggr_port_t *port) { ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh)); ASSERT(MAC_PERIM_HELD(port->lp_mh)); if (port->lp_state != AGGR_PORT_STATE_ATTACHED) return; (void) aggr_port_multicst(port, B_FALSE, (uchar_t *)&slow_multicast_addr); } static void start_periodic_timer(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if (pl->periodic_timer.id == 0) { pl->periodic_timer.id = timeout(periodic_timer_pop, portp, drv_usectohz(1000000 * portp->lp_lacp.periodic_timer.val)); } mutex_exit(&pl->lacp_timer_lock); } static void stop_periodic_timer(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; timeout_id_t id; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if ((id = pl->periodic_timer.id) != 0) { pl->lacp_timer_bits &= ~LACP_PERIODIC_TIMEOUT; pl->periodic_timer.id = 0; } mutex_exit(&pl->lacp_timer_lock); if (id != 0) (void) untimeout(id); } /* * When the timer pops, we arrive here to * clear out LACPDU count as well as transmit an * LACPDU. We then set the periodic state and let * the periodic state machine restart the timer. */ static void periodic_timer_pop(void *data) { aggr_port_t *portp = data; aggr_lacp_port_t *pl = &portp->lp_lacp; mutex_enter(&pl->lacp_timer_lock); pl->lacp_timer_bits |= LACP_PERIODIC_TIMEOUT; cv_broadcast(&pl->lacp_timer_cv); mutex_exit(&pl->lacp_timer_lock); } /* * When the timer pops, we arrive here to * clear out LACPDU count as well as transmit an * LACPDU. We then set the periodic state and let * the periodic state machine restart the timer. */ static void periodic_timer_pop_handler(aggr_port_t *portp) { ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); portp->lp_lacp_stats.LACPDUsTx = 0; /* current timestamp */ portp->lp_lacp.time = gethrtime(); portp->lp_lacp.NTT = B_TRUE; lacp_xmit_sm(portp); /* * Set Periodic State machine state based on the * value of the Partner Operation Port State timeout * bit. */ if (portp->lp_lacp.PartnerOperPortState.bit.timeout) { portp->lp_lacp.periodic_timer.val = FAST_PERIODIC_TIME; portp->lp_lacp.sm.periodic_state = LACP_FAST_PERIODIC; } else { portp->lp_lacp.periodic_timer.val = SLOW_PERIODIC_TIME; portp->lp_lacp.sm.periodic_state = LACP_SLOW_PERIODIC; } lacp_periodic_sm(portp); } /* * Invoked from: * - startup upon aggregation * - when the periodic timer pops * - when the periodic timer value is changed * - when the port is attached or detached * - when LACP mode is changed. */ static void lacp_periodic_sm(aggr_port_t *portp) { lacp_periodic_state_t oldstate = portp->lp_lacp.sm.periodic_state; aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); /* LACP_OFF state not in specification so check here. */ if (!pl->sm.lacp_on) { /* Stop timer whether it is running or not */ stop_periodic_timer(portp); pl->sm.periodic_state = LACP_NO_PERIODIC; pl->NTT = B_FALSE; AGGR_LACP_DBG(("lacp_periodic_sm(%d):NO LACP " "%s--->%s\n", portp->lp_linkid, lacp_periodic_str[oldstate], lacp_periodic_str[pl->sm.periodic_state])); return; } if (pl->sm.begin || !pl->sm.lacp_enabled || !pl->sm.port_enabled || !pl->ActorOperPortState.bit.activity && !pl->PartnerOperPortState.bit.activity) { /* Stop timer whether it is running or not */ stop_periodic_timer(portp); pl->sm.periodic_state = LACP_NO_PERIODIC; pl->NTT = B_FALSE; AGGR_LACP_DBG(("lacp_periodic_sm(%d):STOP %s--->%s\n", portp->lp_linkid, lacp_periodic_str[oldstate], lacp_periodic_str[pl->sm.periodic_state])); return; } /* * Startup with FAST_PERIODIC_TIME if no previous LACPDU * has been received. Then after we timeout, then it is * possible to go to SLOW_PERIODIC_TIME. */ if (pl->sm.periodic_state == LACP_NO_PERIODIC) { pl->periodic_timer.val = FAST_PERIODIC_TIME; pl->sm.periodic_state = LACP_FAST_PERIODIC; } else if ((pl->sm.periodic_state == LACP_SLOW_PERIODIC) && pl->PartnerOperPortState.bit.timeout) { /* * If we receive a bit indicating we are going to * fast periodic from slow periodic, stop the timer * and let the periodic_timer_pop routine deal * with reseting the periodic state and transmitting * a LACPDU. */ stop_periodic_timer(portp); periodic_timer_pop_handler(portp); } /* Rearm timer with value provided by partner */ start_periodic_timer(portp); } /* * This routine transmits an LACPDU if lacp_enabled * is TRUE and if NTT is set. */ static void lacp_xmit_sm(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; size_t len; mblk_t *mp; hrtime_t now, elapsed; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); /* LACP_OFF state not in specification so check here. */ if (!pl->sm.lacp_on || !pl->NTT) return; /* * Do nothing if LACP has been turned off or if the * periodic state machine is not enabled. */ if ((pl->sm.periodic_state == LACP_NO_PERIODIC) || !pl->sm.lacp_enabled || pl->sm.begin) { pl->NTT = B_FALSE; return; } /* * If we have sent 5 Slow packets in the last second, avoid * sending any more here. No more than three LACPDUs may be transmitted * in any Fast_Periodic_Time interval. */ if (portp->lp_lacp_stats.LACPDUsTx >= 3) { /* * Grab the current time value and see if * more than 1 second has passed. If so, * reset the timestamp and clear the count. */ now = gethrtime(); elapsed = now - pl->time; if (elapsed > NSECS_PER_SEC) { portp->lp_lacp_stats.LACPDUsTx = 0; pl->time = now; } else { return; } } len = sizeof (lacp_t) + sizeof (struct ether_header); mp = allocb(len, BPRI_MED); if (mp == NULL) return; mp->b_wptr = mp->b_rptr + len; bzero(mp->b_rptr, len); fill_lacp_ether(portp, (struct ether_header *)mp->b_rptr); fill_lacp_pdu(portp, (lacp_t *)(mp->b_rptr + sizeof (struct ether_header))); /* Send the packet over the first TX ring */ mp = mac_hwring_send_priv(portp->lp_mch, portp->lp_tx_rings[0], mp); if (mp != NULL) freemsg(mp); pl->NTT = B_FALSE; portp->lp_lacp_stats.LACPDUsTx++; } /* * Initialize the ethernet header of a LACP packet sent from the specified * port. */ static void fill_lacp_ether(aggr_port_t *port, struct ether_header *ether) { bcopy(port->lp_addr, (uint8_t *)&(ether->ether_shost), ETHERADDRL); bcopy(&slow_multicast_addr, (uint8_t *)&(ether->ether_dhost), ETHERADDRL); ether->ether_type = htons(ETHERTYPE_SLOW); } static void fill_lacp_pdu(aggr_port_t *portp, lacp_t *lacp) { aggr_lacp_port_t *pl = &portp->lp_lacp; aggr_grp_t *aggrp = portp->lp_grp; mac_perim_handle_t pmph; ASSERT(MAC_PERIM_HELD(aggrp->lg_mh)); mac_perim_enter_by_mh(portp->lp_mh, &pmph); lacp->subtype = LACP_SUBTYPE; lacp->version = LACP_VERSION; /* * Actor Information */ lacp->actor_info.tlv_type = ACTOR_TLV; lacp->actor_info.information_len = sizeof (link_info_t); lacp->actor_info.system_priority = htons(aggrp->aggr.ActorSystemPriority); bcopy(aggrp->lg_addr, (uchar_t *)&lacp->actor_info.system_id, ETHERADDRL); lacp->actor_info.key = htons(pl->ActorOperPortKey); lacp->actor_info.port_priority = htons(pl->ActorPortPriority); lacp->actor_info.port = htons(pl->ActorPortNumber); lacp->actor_info.state.state = pl->ActorOperPortState.state; /* * Partner Information */ lacp->partner_info.tlv_type = PARTNER_TLV; lacp->partner_info.information_len = sizeof (link_info_t); lacp->partner_info.system_priority = htons(pl->PartnerOperSysPriority); lacp->partner_info.system_id = pl->PartnerOperSystem; lacp->partner_info.key = htons(pl->PartnerOperKey); lacp->partner_info.port_priority = htons(pl->PartnerOperPortPriority); lacp->partner_info.port = htons(pl->PartnerOperPortNum); lacp->partner_info.state.state = pl->PartnerOperPortState.state; /* Collector Information */ lacp->tlv_collector = COLLECTOR_TLV; lacp->collector_len = 0x10; lacp->collector_max_delay = htons(aggrp->aggr.CollectorMaxDelay); /* Termination Information */ lacp->tlv_terminator = TERMINATOR_TLV; lacp->terminator_len = 0x0; mac_perim_exit(pmph); } /* * lacp_mux_sm - LACP mux state machine * This state machine is invoked from: * - startup upon aggregation * - from the Selection logic * - when the wait_while_timer pops * - when the aggregation MAC address is changed * - when receiving DL_NOTE_LINK_UP/DOWN * - when receiving DL_NOTE_AGGR_AVAIL/UNAVAIL * - when LACP mode is changed. * - when a DL_NOTE_SPEED is received */ static void lacp_mux_sm(aggr_port_t *portp) { aggr_grp_t *aggrp = portp->lp_grp; boolean_t NTT_updated = B_FALSE; aggr_lacp_port_t *pl = &portp->lp_lacp; lacp_mux_state_t oldstate = pl->sm.mux_state; ASSERT(MAC_PERIM_HELD(aggrp->lg_mh)); /* LACP_OFF state not in specification so check here. */ if (!pl->sm.lacp_on) { pl->sm.mux_state = LACP_DETACHED; pl->ActorOperPortState.bit.sync = B_FALSE; if (pl->ActorOperPortState.bit.collecting || pl->ActorOperPortState.bit.distributing) { AGGR_LACP_DBG(("trunk link: (%d): " "Collector_Distributor Disabled.\n", portp->lp_linkid)); } pl->ActorOperPortState.bit.collecting = pl->ActorOperPortState.bit.distributing = B_FALSE; return; } if (pl->sm.begin || !pl->sm.lacp_enabled) pl->sm.mux_state = LACP_DETACHED; again: /* determine next state, or return if state unchanged */ switch (pl->sm.mux_state) { case LACP_DETACHED: if (pl->sm.begin) { break; } if ((pl->sm.selected == AGGR_SELECTED) || (pl->sm.selected == AGGR_STANDBY)) { pl->sm.mux_state = LACP_WAITING; break; } return; case LACP_WAITING: if (pl->sm.selected == AGGR_UNSELECTED) { pl->sm.mux_state = LACP_DETACHED; break; } if ((pl->sm.selected == AGGR_SELECTED) && aggrp->aggr.ready) { pl->sm.mux_state = LACP_ATTACHED; break; } return; case LACP_ATTACHED: if ((pl->sm.selected == AGGR_UNSELECTED) || (pl->sm.selected == AGGR_STANDBY)) { pl->sm.mux_state = LACP_DETACHED; break; } if ((pl->sm.selected == AGGR_SELECTED) && pl->PartnerOperPortState.bit.sync) { pl->sm.mux_state = LACP_COLLECTING_DISTRIBUTING; break; } return; case LACP_COLLECTING_DISTRIBUTING: if ((pl->sm.selected == AGGR_UNSELECTED) || (pl->sm.selected == AGGR_STANDBY) || !pl->PartnerOperPortState.bit.sync) { pl->sm.mux_state = LACP_ATTACHED; break; } return; } AGGR_LACP_DBG(("lacp_mux_sm(%d):%s--->%s\n", portp->lp_linkid, lacp_mux_str[oldstate], lacp_mux_str[pl->sm.mux_state])); /* perform actions on entering a new state */ switch (pl->sm.mux_state) { case LACP_DETACHED: if (pl->ActorOperPortState.bit.collecting || pl->ActorOperPortState.bit.distributing) { AGGR_LACP_DBG(("trunk link: (%d): " "Collector_Distributor Disabled.\n", portp->lp_linkid)); } pl->ActorOperPortState.bit.sync = pl->ActorOperPortState.bit.collecting = B_FALSE; /* Turn OFF Collector_Distributor */ aggr_set_coll_dist(portp, B_FALSE); pl->ActorOperPortState.bit.distributing = B_FALSE; NTT_updated = B_TRUE; break; case LACP_WAITING: start_wait_while_timer(portp); break; case LACP_ATTACHED: if (pl->ActorOperPortState.bit.collecting || pl->ActorOperPortState.bit.distributing) { AGGR_LACP_DBG(("trunk link: (%d): " "Collector_Distributor Disabled.\n", portp->lp_linkid)); } pl->ActorOperPortState.bit.sync = B_TRUE; pl->ActorOperPortState.bit.collecting = B_FALSE; /* Turn OFF Collector_Distributor */ aggr_set_coll_dist(portp, B_FALSE); pl->ActorOperPortState.bit.distributing = B_FALSE; NTT_updated = B_TRUE; if (pl->PartnerOperPortState.bit.sync) { /* * We had already received an updated sync from * the partner. Attempt to transition to * collecting/distributing now. */ goto again; } break; case LACP_COLLECTING_DISTRIBUTING: if (!pl->ActorOperPortState.bit.collecting && !pl->ActorOperPortState.bit.distributing) { AGGR_LACP_DBG(("trunk link: (%d): " "Collector_Distributor Enabled.\n", portp->lp_linkid)); } pl->ActorOperPortState.bit.distributing = B_TRUE; /* Turn Collector_Distributor back ON */ aggr_set_coll_dist(portp, B_TRUE); pl->ActorOperPortState.bit.collecting = B_TRUE; NTT_updated = B_TRUE; break; } /* * If we updated the state of the NTT variable, then * initiate a LACPDU transmission. */ if (NTT_updated) { pl->NTT = B_TRUE; lacp_xmit_sm(portp); } } /* lacp_mux_sm */ static int receive_marker_pdu(aggr_port_t *portp, mblk_t *mp) { marker_pdu_t *markerp = (marker_pdu_t *)mp->b_rptr; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); AGGR_LACP_DBG(("trunk link: (%d): MARKER PDU received:\n", portp->lp_linkid)); /* LACP_OFF state not in specification so check here. */ if (!portp->lp_lacp.sm.lacp_on) return (-1); if (MBLKL(mp) < sizeof (marker_pdu_t)) return (-1); if (markerp->version != MARKER_VERSION) { AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: " "version = %d does not match s/w version %d\n", portp->lp_linkid, markerp->version, MARKER_VERSION)); return (-1); } if (markerp->tlv_marker == MARKER_RESPONSE_TLV) { /* We do not yet send out MARKER info PDUs */ AGGR_LACP_DBG(("trunk link (%d): MARKER RESPONSE PDU: " " MARKER TLV = %d - We don't send out info type!\n", portp->lp_linkid, markerp->tlv_marker)); return (-1); } if (markerp->tlv_marker != MARKER_INFO_TLV) { AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: " " MARKER TLV = %d \n", portp->lp_linkid, markerp->tlv_marker)); return (-1); } if (markerp->marker_len != MARKER_INFO_RESPONSE_LENGTH) { AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: " " MARKER length = %d \n", portp->lp_linkid, markerp->marker_len)); return (-1); } if (markerp->requestor_port != portp->lp_lacp.PartnerOperPortNum) { AGGR_LACP_DBG(("trunk link (%d): MARKER PDU: " " MARKER Port %d not equal to Partner port %d\n", portp->lp_linkid, markerp->requestor_port, portp->lp_lacp.PartnerOperPortNum)); return (-1); } if (ether_cmp(&markerp->system_id, &portp->lp_lacp.PartnerOperSystem) != 0) { AGGR_LACP_DBG(("trunk link (%d): MARKER PDU: " " MARKER MAC not equal to Partner MAC\n", portp->lp_linkid)); return (-1); } /* * Turn into Marker Response PDU * and return mblk to sending system */ markerp->tlv_marker = MARKER_RESPONSE_TLV; /* reuse the space that was used by received ethernet header */ ASSERT(MBLKHEAD(mp) >= sizeof (struct ether_header)); mp->b_rptr -= sizeof (struct ether_header); fill_lacp_ether(portp, (struct ether_header *)mp->b_rptr); return (0); } /* * Update the LACP mode (off, active, or passive) of the specified group. */ void aggr_lacp_update_mode(aggr_grp_t *grp, aggr_lacp_mode_t mode) { aggr_lacp_mode_t old_mode = grp->lg_lacp_mode; aggr_port_t *port; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); ASSERT(!grp->lg_closing); if (mode == old_mode) return; grp->lg_lacp_mode = mode; for (port = grp->lg_ports; port != NULL; port = port->lp_next) { port->lp_lacp.ActorAdminPortState.bit.activity = port->lp_lacp.ActorOperPortState.bit.activity = (mode == AGGR_LACP_ACTIVE); if (old_mode == AGGR_LACP_OFF) { /* OFF -> {PASSIVE,ACTIVE} */ /* turn OFF Collector_Distributor */ aggr_set_coll_dist(port, B_FALSE); lacp_on(port); } else if (mode == AGGR_LACP_OFF) { /* {PASSIVE,ACTIVE} -> OFF */ lacp_off(port); /* Turn ON Collector_Distributor */ aggr_set_coll_dist(port, B_TRUE); } else { /* PASSIVE->ACTIVE or ACTIVE->PASSIVE */ port->lp_lacp.sm.begin = B_TRUE; lacp_mux_sm(port); lacp_periodic_sm(port); /* kick off state machines */ lacp_receive_sm(port, NULL); lacp_mux_sm(port); } } } /* * Update the LACP timer (short or long) of the specified group. */ void aggr_lacp_update_timer(aggr_grp_t *grp, aggr_lacp_timer_t timer) { aggr_port_t *port; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); if (timer == grp->aggr.PeriodicTimer) return; grp->aggr.PeriodicTimer = timer; for (port = grp->lg_ports; port != NULL; port = port->lp_next) { port->lp_lacp.ActorAdminPortState.bit.timeout = port->lp_lacp.ActorOperPortState.bit.timeout = (timer == AGGR_LACP_TIMER_SHORT); } } void aggr_port_lacp_set_mode(aggr_grp_t *grp, aggr_port_t *port) { aggr_lacp_mode_t mode; aggr_lacp_timer_t timer; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); mode = grp->lg_lacp_mode; timer = grp->aggr.PeriodicTimer; port->lp_lacp.ActorAdminPortState.bit.activity = port->lp_lacp.ActorOperPortState.bit.activity = (mode == AGGR_LACP_ACTIVE); port->lp_lacp.ActorAdminPortState.bit.timeout = port->lp_lacp.ActorOperPortState.bit.timeout = (timer == AGGR_LACP_TIMER_SHORT); if (mode == AGGR_LACP_OFF) { /* Turn ON Collector_Distributor */ aggr_set_coll_dist(port, B_TRUE); } else { /* LACP_ACTIVE/PASSIVE */ lacp_on(port); } } /* * Sets the initial LACP mode (off, active, passive) and LACP timer * (short, long) of the specified group. */ void aggr_lacp_set_mode(aggr_grp_t *grp, aggr_lacp_mode_t mode, aggr_lacp_timer_t timer) { aggr_port_t *port; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); grp->lg_lacp_mode = mode; grp->aggr.PeriodicTimer = timer; for (port = grp->lg_ports; port != NULL; port = port->lp_next) aggr_port_lacp_set_mode(grp, port); } /* * Verify that the Partner MAC and Key recorded by the specified * port are not found in other ports that are not part of our * aggregation. Returns B_TRUE if such a port is found, B_FALSE * otherwise. */ static boolean_t lacp_misconfig_check(aggr_port_t *portp) { aggr_grp_t *grp = portp->lp_grp; lacp_sel_ports_t *cport; mutex_enter(&lacp_sel_lock); for (cport = sel_ports; cport != NULL; cport = cport->sp_next) { /* skip entries of the group of the port being checked */ if (cport->sp_grp_linkid == grp->lg_linkid) continue; if ((ether_cmp(&cport->sp_partner_system, &grp->aggr.PartnerSystem) == 0) && (cport->sp_partner_key == grp->aggr.PartnerOperAggrKey)) { char mac_str[ETHERADDRL*3]; struct ether_addr *mac = &cport->sp_partner_system; /* * The Partner port information is already in use * by ports in another aggregation so disable this * port. */ (void) snprintf(mac_str, sizeof (mac_str), "%x:%x:%x:%x:%x:%x", mac->ether_addr_octet[0], mac->ether_addr_octet[1], mac->ether_addr_octet[2], mac->ether_addr_octet[3], mac->ether_addr_octet[4], mac->ether_addr_octet[5]); portp->lp_lacp.sm.selected = AGGR_UNSELECTED; cmn_err(CE_NOTE, "aggr %d port %d: Port Partner " "MAC %s and key %d in use on aggregation %d " "port %d\n", grp->lg_linkid, portp->lp_linkid, mac_str, portp->lp_lacp.PartnerOperKey, cport->sp_grp_linkid, cport->sp_linkid); break; } } mutex_exit(&lacp_sel_lock); return (cport != NULL); } /* * Remove the specified port from the list of selected ports. */ static void lacp_sel_ports_del(aggr_port_t *portp) { lacp_sel_ports_t *cport, **prev = NULL; mutex_enter(&lacp_sel_lock); prev = &sel_ports; for (cport = sel_ports; cport != NULL; prev = &cport->sp_next, cport = cport->sp_next) { if (portp->lp_linkid == cport->sp_linkid) break; } if (cport == NULL) { mutex_exit(&lacp_sel_lock); return; } *prev = cport->sp_next; kmem_free(cport, sizeof (*cport)); mutex_exit(&lacp_sel_lock); } /* * Add the specified port to the list of selected ports. Returns B_FALSE * if the operation could not be performed due to an memory allocation * error. */ static boolean_t lacp_sel_ports_add(aggr_port_t *portp) { lacp_sel_ports_t *new_port; lacp_sel_ports_t *cport, **last; mutex_enter(&lacp_sel_lock); /* check if port is already in the list */ last = &sel_ports; for (cport = sel_ports; cport != NULL; last = &cport->sp_next, cport = cport->sp_next) { if (portp->lp_linkid == cport->sp_linkid) { ASSERT(cport->sp_partner_key == portp->lp_lacp.PartnerOperKey); ASSERT(ether_cmp(&cport->sp_partner_system, &portp->lp_lacp.PartnerOperSystem) == 0); mutex_exit(&lacp_sel_lock); return (B_TRUE); } } /* create and initialize new entry */ new_port = kmem_zalloc(sizeof (lacp_sel_ports_t), KM_NOSLEEP); if (new_port == NULL) { mutex_exit(&lacp_sel_lock); return (B_FALSE); } new_port->sp_grp_linkid = portp->lp_grp->lg_linkid; bcopy(&portp->lp_lacp.PartnerOperSystem, &new_port->sp_partner_system, sizeof (new_port->sp_partner_system)); new_port->sp_partner_key = portp->lp_lacp.PartnerOperKey; new_port->sp_linkid = portp->lp_linkid; *last = new_port; mutex_exit(&lacp_sel_lock); return (B_TRUE); } /* * lacp_selection_logic - LACP selection logic * Sets the selected variable on a per port basis * and sets Ready when all waiting ports are ready * to go online. * * parameters: * - portp - instance this applies to. * * invoked: * - when initialization is needed * - when UNSELECTED is set from the lacp_receive_sm() in LACP_CURRENT state * - When the lacp_receive_sm goes to the LACP_DEFAULTED state * - every time the wait_while_timer pops * - everytime we turn LACP on/off */ static void lacp_selection_logic(aggr_port_t *portp) { aggr_port_t *tpp; aggr_grp_t *aggrp = portp->lp_grp; int ports_waiting; boolean_t reset_mac = B_FALSE; aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(aggrp->lg_mh)); /* LACP_OFF state not in specification so check here. */ if (!pl->sm.lacp_on) { lacp_port_unselect(portp); aggrp->aggr.ready = B_FALSE; lacp_mux_sm(portp); return; } if (pl->sm.begin || !pl->sm.lacp_enabled || (portp->lp_state != AGGR_PORT_STATE_ATTACHED)) { AGGR_LACP_DBG(("lacp_selection_logic:(%d): " "selected %d-->%d (begin=%d, lacp_enabled = %d, " "lp_state=%d)\n", portp->lp_linkid, pl->sm.selected, AGGR_UNSELECTED, pl->sm.begin, pl->sm.lacp_enabled, portp->lp_state)); lacp_port_unselect(portp); aggrp->aggr.ready = B_FALSE; lacp_mux_sm(portp); return; } /* * If LACP is not enabled then selected is never set. */ if (!pl->sm.lacp_enabled) { AGGR_LACP_DBG(("lacp_selection_logic:(%d): selected %d-->%d\n", portp->lp_linkid, pl->sm.selected, AGGR_UNSELECTED)); lacp_port_unselect(portp); lacp_mux_sm(portp); return; } /* * Check if the Partner MAC or Key are zero. If so, we have * not received any LACP info or it has expired and the * receive machine is in the LACP_DEFAULTED state. */ if (ether_cmp(&pl->PartnerOperSystem, ðerzeroaddr) == 0 || (pl->PartnerOperKey == 0)) { for (tpp = aggrp->lg_ports; tpp; tpp = tpp->lp_next) { if (ether_cmp(&tpp->lp_lacp.PartnerOperSystem, ðerzeroaddr) != 0 && (tpp->lp_lacp.PartnerOperKey != 0)) break; } /* * If all ports have no key or aggregation address, * then clear the negotiated Partner MAC and key. */ if (tpp == NULL) { /* Clear the aggregation Partner MAC and key */ aggrp->aggr.PartnerSystem = etherzeroaddr; aggrp->aggr.PartnerOperAggrKey = 0; } return; } /* * Insure that at least one port in the aggregation * matches the Partner aggregation MAC and key. If not, * then clear the aggregation MAC and key. Later we will * set the Partner aggregation MAC and key to that of the * current port's Partner MAC and key. */ if (ether_cmp(&pl->PartnerOperSystem, &aggrp->aggr.PartnerSystem) != 0 || (pl->PartnerOperKey != aggrp->aggr.PartnerOperAggrKey)) { for (tpp = aggrp->lg_ports; tpp; tpp = tpp->lp_next) { if (ether_cmp(&tpp->lp_lacp.PartnerOperSystem, &aggrp->aggr.PartnerSystem) == 0 && (tpp->lp_lacp.PartnerOperKey == aggrp->aggr.PartnerOperAggrKey)) { /* Set aggregation Partner MAC and key */ aggrp->aggr.PartnerSystem = pl->PartnerOperSystem; aggrp->aggr.PartnerOperAggrKey = pl->PartnerOperKey; break; } } if (tpp == NULL) { /* Clear the aggregation Partner MAC and key */ aggrp->aggr.PartnerSystem = etherzeroaddr; aggrp->aggr.PartnerOperAggrKey = 0; reset_mac = B_TRUE; } } /* * If our Actor MAC is found in the Partner MAC * on this port then we have a loopback misconfiguration. */ if (ether_cmp(&pl->PartnerOperSystem, (struct ether_addr *)&aggrp->lg_addr) == 0) { cmn_err(CE_NOTE, "trunk link: (%d): Loopback condition.\n", portp->lp_linkid); lacp_port_unselect(portp); lacp_mux_sm(portp); return; } /* * If our Partner MAC and Key are found on any other * ports that are not in our aggregation, we have * a misconfiguration. */ if (lacp_misconfig_check(portp)) { lacp_mux_sm(portp); return; } /* * If the Aggregation Partner MAC and Key have not been * set, then this is either the first port or the aggregation * MAC and key have been reset. In either case we must set * the values of the Partner MAC and key. */ if (ether_cmp(&aggrp->aggr.PartnerSystem, ðerzeroaddr) == 0 && (aggrp->aggr.PartnerOperAggrKey == 0)) { /* Set aggregation Partner MAC and key */ aggrp->aggr.PartnerSystem = pl->PartnerOperSystem; aggrp->aggr.PartnerOperAggrKey = pl->PartnerOperKey; /* * If we reset Partner aggregation MAC, then restart * selection_logic on ports that match new MAC address. */ if (reset_mac) { for (tpp = aggrp->lg_ports; tpp; tpp = tpp->lp_next) { if (tpp == portp) continue; if (ether_cmp(&tpp->lp_lacp.PartnerOperSystem, &aggrp->aggr.PartnerSystem) == 0 && (tpp->lp_lacp.PartnerOperKey == aggrp->aggr.PartnerOperAggrKey)) lacp_selection_logic(tpp); } } } else if (ether_cmp(&pl->PartnerOperSystem, &aggrp->aggr.PartnerSystem) != 0 || (pl->PartnerOperKey != aggrp->aggr.PartnerOperAggrKey)) { /* * The Partner port information does not match * that of the other ports in the aggregation * so disable this port. */ lacp_port_unselect(portp); cmn_err(CE_NOTE, "trunk link: (%d): Port Partner MAC " "or key (%d) incompatible with Aggregation Partner " "MAC or key (%d)\n", portp->lp_linkid, pl->PartnerOperKey, aggrp->aggr.PartnerOperAggrKey); lacp_mux_sm(portp); return; } /* If we get to here, automatically set selected */ if (pl->sm.selected != AGGR_SELECTED) { AGGR_LACP_DBG(("lacp_selection_logic:(%d): " "selected %d-->%d\n", portp->lp_linkid, pl->sm.selected, AGGR_SELECTED)); if (!lacp_port_select(portp)) return; lacp_mux_sm(portp); } /* * From this point onward we have selected the port * and are simply checking if the Ready flag should * be set. */ /* * If at least two ports are waiting to aggregate * and ready_n is set on all ports waiting to aggregate * then set READY for the aggregation. */ ports_waiting = 0; if (!aggrp->aggr.ready) { /* * If all ports in the aggregation have received compatible * partner information and they match up correctly with the * switch, there is no need to wait for all the * wait_while_timers to pop. */ for (tpp = aggrp->lg_ports; tpp; tpp = tpp->lp_next) { if (((tpp->lp_lacp.sm.mux_state == LACP_WAITING) || tpp->lp_lacp.sm.begin) && !tpp->lp_lacp.PartnerOperPortState.bit.sync) { /* Add up ports uninitialized or waiting */ ports_waiting++; if (!tpp->lp_lacp.sm.ready_n) { DTRACE_PROBE1(port___not__ready, aggr_port_t *, tpp); return; } } } } if (aggrp->aggr.ready) { AGGR_LACP_DBG(("lacp_selection_logic:(%d): " "aggr.ready already set\n", portp->lp_linkid)); lacp_mux_sm(portp); } else { AGGR_LACP_DBG(("lacp_selection_logic:(%d): Ready %d-->%d\n", portp->lp_linkid, aggrp->aggr.ready, B_TRUE)); aggrp->aggr.ready = B_TRUE; for (tpp = aggrp->lg_ports; tpp; tpp = tpp->lp_next) lacp_mux_sm(tpp); } } /* * wait_while_timer_pop - When the timer pops, we arrive here to * set ready_n and trigger the selection logic. */ static void wait_while_timer_pop(void *data) { aggr_port_t *portp = data; aggr_lacp_port_t *pl = &portp->lp_lacp; mutex_enter(&pl->lacp_timer_lock); pl->lacp_timer_bits |= LACP_WAIT_WHILE_TIMEOUT; cv_broadcast(&pl->lacp_timer_cv); mutex_exit(&pl->lacp_timer_lock); } /* * wait_while_timer_pop_handler - When the timer pops, we arrive here to * set ready_n and trigger the selection logic. */ static void wait_while_timer_pop_handler(aggr_port_t *portp) { ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); AGGR_LACP_DBG(("trunk link:(%d): wait_while_timer pop \n", portp->lp_linkid)); portp->lp_lacp.sm.ready_n = B_TRUE; lacp_selection_logic(portp); } static void start_wait_while_timer(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if (pl->wait_while_timer.id == 0) { pl->wait_while_timer.id = timeout(wait_while_timer_pop, portp, drv_usectohz(1000000 * portp->lp_lacp.wait_while_timer.val)); } mutex_exit(&pl->lacp_timer_lock); } static void stop_wait_while_timer(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; timeout_id_t id; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if ((id = pl->wait_while_timer.id) != 0) { pl->lacp_timer_bits &= ~LACP_WAIT_WHILE_TIMEOUT; pl->wait_while_timer.id = 0; } mutex_exit(&pl->lacp_timer_lock); if (id != 0) (void) untimeout(id); } /* * Invoked when a port has been attached to a group. * Complete the processing that couldn't be finished from lacp_on() * because the port was not started. We know that the link is full * duplex and ON, otherwise it wouldn't be attached. */ void aggr_lacp_port_attached(aggr_port_t *portp) { aggr_grp_t *grp = portp->lp_grp; aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); ASSERT(MAC_PERIM_HELD(portp->lp_mh)); ASSERT(portp->lp_state == AGGR_PORT_STATE_ATTACHED); AGGR_LACP_DBG(("aggr_lacp_port_attached: port %d\n", portp->lp_linkid)); portp->lp_lacp.sm.port_enabled = B_TRUE; /* link on */ if (grp->lg_lacp_mode == AGGR_LACP_OFF) return; pl->sm.lacp_enabled = B_TRUE; pl->ActorOperPortState.bit.aggregation = B_TRUE; pl->sm.begin = B_TRUE; lacp_receive_sm(portp, NULL); lacp_mux_sm(portp); /* Enable Multicast Slow Protocol address */ aggr_lacp_mcast_on(portp); /* periodic_sm is started up from the receive machine */ lacp_selection_logic(portp); } /* * Invoked when a port has been detached from a group. Turn off * LACP processing if it was enabled. */ void aggr_lacp_port_detached(aggr_port_t *portp) { aggr_grp_t *grp = portp->lp_grp; ASSERT(MAC_PERIM_HELD(grp->lg_mh)); ASSERT(MAC_PERIM_HELD(portp->lp_mh)); AGGR_LACP_DBG(("aggr_lacp_port_detached: port %d\n", portp->lp_linkid)); portp->lp_lacp.sm.port_enabled = B_FALSE; if (grp->lg_lacp_mode == AGGR_LACP_OFF) return; portp->lp_lacp.sm.lacp_enabled = B_FALSE; lacp_selection_logic(portp); lacp_mux_sm(portp); lacp_periodic_sm(portp); /* * Disable Slow Protocol Timers. */ stop_periodic_timer(portp); stop_current_while_timer(portp); stop_wait_while_timer(portp); /* Disable Multicast Slow Protocol address */ aggr_lacp_mcast_off(portp); aggr_set_coll_dist(portp, B_FALSE); } /* * Enable Slow Protocol LACP and Marker PDUs. */ static void lacp_on(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; mac_perim_handle_t mph; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mac_perim_enter_by_mh(portp->lp_mh, &mph); /* * Reset the state machines and Partner operational * information. Careful to not reset things like * our link state. */ lacp_reset_port(portp); pl->sm.lacp_on = B_TRUE; AGGR_LACP_DBG(("lacp_on:(%d): \n", portp->lp_linkid)); if (portp->lp_state == AGGR_PORT_STATE_ATTACHED) { pl->sm.port_enabled = B_TRUE; pl->sm.lacp_enabled = B_TRUE; pl->ActorOperPortState.bit.aggregation = B_TRUE; } lacp_receive_sm(portp, NULL); lacp_mux_sm(portp); if (portp->lp_state == AGGR_PORT_STATE_ATTACHED) { /* Enable Multicast Slow Protocol address */ aggr_lacp_mcast_on(portp); /* periodic_sm is started up from the receive machine */ lacp_selection_logic(portp); } done: mac_perim_exit(mph); } /* lacp_on */ /* Disable Slow Protocol LACP and Marker PDUs */ static void lacp_off(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; mac_perim_handle_t mph; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mac_perim_enter_by_mh(portp->lp_mh, &mph); pl->sm.lacp_on = B_FALSE; AGGR_LACP_DBG(("lacp_off:(%d): \n", portp->lp_linkid)); if (portp->lp_state == AGGR_PORT_STATE_ATTACHED) { /* * Disable Slow Protocol Timers. */ stop_periodic_timer(portp); stop_current_while_timer(portp); stop_wait_while_timer(portp); /* Disable Multicast Slow Protocol address */ aggr_lacp_mcast_off(portp); pl->sm.port_enabled = B_FALSE; pl->sm.lacp_enabled = B_FALSE; pl->ActorOperPortState.bit.aggregation = B_FALSE; } lacp_mux_sm(portp); lacp_periodic_sm(portp); lacp_selection_logic(portp); /* Turn OFF Collector_Distributor */ aggr_set_coll_dist(portp, B_FALSE); lacp_reset_port(portp); mac_perim_exit(mph); } static boolean_t valid_lacp_pdu(aggr_port_t *portp, lacp_t *lacp) { /* * 43.4.12 - "a Receive machine shall not validate * the Version Number, TLV_type, or Reserved fields in received * LACPDUs." * ... "a Receive machine may validate the Actor_Information_Length, * Partner_Information_Length, Collector_Information_Length, * or Terminator_Length fields." */ if ((lacp->actor_info.information_len != sizeof (link_info_t)) || (lacp->partner_info.information_len != sizeof (link_info_t)) || (lacp->collector_len != LACP_COLLECTOR_INFO_LEN) || (lacp->terminator_len != LACP_TERMINATOR_INFO_LEN)) { AGGR_LACP_DBG(("trunk link (%d): Malformed LACPDU: " " Terminator Length = %d \n", portp->lp_linkid, lacp->terminator_len)); return (B_FALSE); } return (B_TRUE); } static void start_current_while_timer(aggr_port_t *portp, uint_t time) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if (pl->current_while_timer.id == 0) { if (time > 0) pl->current_while_timer.val = time; else if (pl->ActorOperPortState.bit.timeout) pl->current_while_timer.val = SHORT_TIMEOUT_TIME; else pl->current_while_timer.val = LONG_TIMEOUT_TIME; pl->current_while_timer.id = timeout(current_while_timer_pop, portp, drv_usectohz((clock_t)1000000 * (clock_t)portp->lp_lacp.current_while_timer.val)); } mutex_exit(&pl->lacp_timer_lock); } static void stop_current_while_timer(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; timeout_id_t id; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); mutex_enter(&pl->lacp_timer_lock); if ((id = pl->current_while_timer.id) != 0) { pl->lacp_timer_bits &= ~LACP_CURRENT_WHILE_TIMEOUT; pl->current_while_timer.id = 0; } mutex_exit(&pl->lacp_timer_lock); if (id != 0) (void) untimeout(id); } static void current_while_timer_pop(void *data) { aggr_port_t *portp = (aggr_port_t *)data; aggr_lacp_port_t *pl = &portp->lp_lacp; mutex_enter(&pl->lacp_timer_lock); pl->lacp_timer_bits |= LACP_CURRENT_WHILE_TIMEOUT; cv_broadcast(&pl->lacp_timer_cv); mutex_exit(&pl->lacp_timer_lock); } static void current_while_timer_pop_handler(aggr_port_t *portp) { ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); AGGR_LACP_DBG(("trunk link:(%d): current_while_timer " "pop id=%p\n", portp->lp_linkid, portp->lp_lacp.current_while_timer.id)); lacp_receive_sm(portp, NULL); } /* * record_Default - Simply copies over administrative values * to the partner operational values, and sets our state to indicate we * are using defaulted values. */ static void record_Default(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); pl->PartnerOperPortNum = pl->PartnerAdminPortNum; pl->PartnerOperPortPriority = pl->PartnerAdminPortPriority; pl->PartnerOperSystem = pl->PartnerAdminSystem; pl->PartnerOperSysPriority = pl->PartnerAdminSysPriority; pl->PartnerOperKey = pl->PartnerAdminKey; pl->PartnerOperPortState.state = pl->PartnerAdminPortState.state; pl->ActorOperPortState.bit.defaulted = B_TRUE; } /* Returns B_TRUE on sync value changing */ static boolean_t record_PDU(aggr_port_t *portp, lacp_t *lacp) { aggr_grp_t *aggrp = portp->lp_grp; aggr_lacp_port_t *pl = &portp->lp_lacp; uint8_t save_sync; ASSERT(MAC_PERIM_HELD(aggrp->lg_mh)); /* * Partner Information */ pl->PartnerOperPortNum = ntohs(lacp->actor_info.port); pl->PartnerOperPortPriority = ntohs(lacp->actor_info.port_priority); pl->PartnerOperSystem = lacp->actor_info.system_id; pl->PartnerOperSysPriority = htons(lacp->actor_info.system_priority); pl->PartnerOperKey = ntohs(lacp->actor_info.key); /* All state info except for Synchronization */ save_sync = pl->PartnerOperPortState.bit.sync; pl->PartnerOperPortState.state = lacp->actor_info.state.state; /* Defaulted set to FALSE */ pl->ActorOperPortState.bit.defaulted = B_FALSE; /* * 43.4.9 - (Partner_Port, Partner_Port_Priority, Partner_system, * Partner_System_Priority, Partner_Key, and * Partner_State.Aggregation) are compared to the * corresponding operations paramters values for * the Actor. If these are equal, or if this is * an individual link, we are synchronized. */ if (((ntohs(lacp->partner_info.port) == pl->ActorPortNumber) && (ntohs(lacp->partner_info.port_priority) == pl->ActorPortPriority) && (ether_cmp(&lacp->partner_info.system_id, (struct ether_addr *)&aggrp->lg_addr) == 0) && (ntohs(lacp->partner_info.system_priority) == aggrp->aggr.ActorSystemPriority) && (ntohs(lacp->partner_info.key) == pl->ActorOperPortKey) && (lacp->partner_info.state.bit.aggregation == pl->ActorOperPortState.bit.aggregation)) || (!lacp->actor_info.state.bit.aggregation)) { pl->PartnerOperPortState.bit.sync = lacp->actor_info.state.bit.sync; } else { pl->PartnerOperPortState.bit.sync = B_FALSE; } if (save_sync != pl->PartnerOperPortState.bit.sync) { AGGR_LACP_DBG(("record_PDU:(%d): partner sync " "%d -->%d\n", portp->lp_linkid, save_sync, pl->PartnerOperPortState.bit.sync)); return (B_TRUE); } else { return (B_FALSE); } } /* * update_selected - If any of the Partner parameters has * changed from a previous value, then * unselect the link from the aggregator. */ static boolean_t update_selected(aggr_port_t *portp, lacp_t *lacp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); if ((pl->PartnerOperPortNum != ntohs(lacp->actor_info.port)) || (pl->PartnerOperPortPriority != ntohs(lacp->actor_info.port_priority)) || (ether_cmp(&pl->PartnerOperSystem, &lacp->actor_info.system_id) != 0) || (pl->PartnerOperSysPriority != ntohs(lacp->actor_info.system_priority)) || (pl->PartnerOperKey != ntohs(lacp->actor_info.key)) || (pl->PartnerOperPortState.bit.aggregation != lacp->actor_info.state.bit.aggregation)) { AGGR_LACP_DBG(("update_selected:(%d): " "selected %d-->%d\n", portp->lp_linkid, pl->sm.selected, AGGR_UNSELECTED)); lacp_port_unselect(portp); return (B_TRUE); } else { return (B_FALSE); } } /* * update_default_selected - If any of the operational Partner parameters * is different than that of the administrative values * then unselect the link from the aggregator. */ static void update_default_selected(aggr_port_t *portp) { aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); if ((pl->PartnerAdminPortNum != pl->PartnerOperPortNum) || (pl->PartnerOperPortPriority != pl->PartnerAdminPortPriority) || (ether_cmp(&pl->PartnerOperSystem, &pl->PartnerAdminSystem) != 0) || (pl->PartnerOperSysPriority != pl->PartnerAdminSysPriority) || (pl->PartnerOperKey != pl->PartnerAdminKey) || (pl->PartnerOperPortState.bit.aggregation != pl->PartnerAdminPortState.bit.aggregation)) { AGGR_LACP_DBG(("update_default_selected:(%d): " "selected %d-->%d\n", portp->lp_linkid, pl->sm.selected, AGGR_UNSELECTED)); lacp_port_unselect(portp); } } /* * update_NTT - If any of the Partner values in the received LACPDU * are different than that of the Actor operational * values then set NTT to true. */ static void update_NTT(aggr_port_t *portp, lacp_t *lacp) { aggr_grp_t *aggrp = portp->lp_grp; aggr_lacp_port_t *pl = &portp->lp_lacp; ASSERT(MAC_PERIM_HELD(aggrp->lg_mh)); if ((pl->ActorPortNumber != ntohs(lacp->partner_info.port)) || (pl->ActorPortPriority != ntohs(lacp->partner_info.port_priority)) || (ether_cmp(&aggrp->lg_addr, &lacp->partner_info.system_id) != 0) || (aggrp->aggr.ActorSystemPriority != ntohs(lacp->partner_info.system_priority)) || (pl->ActorOperPortKey != ntohs(lacp->partner_info.key)) || (pl->ActorOperPortState.bit.activity != lacp->partner_info.state.bit.activity) || (pl->ActorOperPortState.bit.timeout != lacp->partner_info.state.bit.timeout) || (pl->ActorOperPortState.bit.sync != lacp->partner_info.state.bit.sync) || (pl->ActorOperPortState.bit.aggregation != lacp->partner_info.state.bit.aggregation)) { AGGR_LACP_DBG(("update_NTT:(%d): NTT %d-->%d\n", portp->lp_linkid, pl->NTT, B_TRUE)); pl->NTT = B_TRUE; } } /* * lacp_receive_sm - LACP receive state machine * * parameters: * - portp - instance this applies to. * - lacp - pointer in the case of a received LACPDU. * This value is NULL if there is no LACPDU. * * invoked: * - when initialization is needed * - upon reception of an LACPDU. This is the common case. * - every time the current_while_timer pops */ static void lacp_receive_sm(aggr_port_t *portp, lacp_t *lacp) { boolean_t sync_updated, selected_updated, save_activity; aggr_lacp_port_t *pl = &portp->lp_lacp; lacp_receive_state_t oldstate = pl->sm.receive_state; ASSERT(MAC_PERIM_HELD(portp->lp_grp->lg_mh)); /* LACP_OFF state not in specification so check here. */ if (!pl->sm.lacp_on) return; /* figure next state */ if (pl->sm.begin || pl->sm.port_moved) { pl->sm.receive_state = LACP_INITIALIZE; } else if (!pl->sm.port_enabled) { /* DL_NOTE_LINK_DOWN */ pl->sm.receive_state = LACP_PORT_DISABLED; } else if (!pl->sm.lacp_enabled) { /* DL_NOTE_AGGR_UNAVAIL */ pl->sm.receive_state = (pl->sm.receive_state == LACP_PORT_DISABLED) ? LACP_DISABLED : LACP_PORT_DISABLED; } else if (lacp != NULL) { if ((pl->sm.receive_state == LACP_EXPIRED) || (pl->sm.receive_state == LACP_DEFAULTED)) { pl->sm.receive_state = LACP_CURRENT; } } else if ((pl->sm.receive_state == LACP_CURRENT) && (pl->current_while_timer.id == 0)) { pl->sm.receive_state = LACP_EXPIRED; } else if ((pl->sm.receive_state == LACP_EXPIRED) && (pl->current_while_timer.id == 0)) { pl->sm.receive_state = LACP_DEFAULTED; } if (!((lacp && (oldstate == LACP_CURRENT) && (pl->sm.receive_state == LACP_CURRENT)))) { AGGR_LACP_DBG(("lacp_receive_sm(%d):%s--->%s\n", portp->lp_linkid, lacp_receive_str[oldstate], lacp_receive_str[pl->sm.receive_state])); } switch (pl->sm.receive_state) { case LACP_INITIALIZE: lacp_port_unselect(portp); record_Default(portp); pl->ActorOperPortState.bit.expired = B_FALSE; pl->sm.port_moved = B_FALSE; pl->sm.receive_state = LACP_PORT_DISABLED; pl->sm.begin = B_FALSE; lacp_receive_sm(portp, NULL); break; case LACP_PORT_DISABLED: pl->PartnerOperPortState.bit.sync = B_FALSE; /* * Stop current_while_timer in case * we got here from link down */ stop_current_while_timer(portp); if (pl->sm.port_enabled && !pl->sm.lacp_enabled) { pl->sm.receive_state = LACP_DISABLED; lacp_receive_sm(portp, lacp); /* We goto LACP_DISABLED state */ break; } else if (pl->sm.port_enabled && pl->sm.lacp_enabled) { pl->sm.receive_state = LACP_EXPIRED; /* * FALL THROUGH TO LACP_EXPIRED CASE: * We have no way of knowing if we get into * lacp_receive_sm() from a current_while_timer * expiring as it has never been kicked off yet! */ } else { /* We stay in LACP_PORT_DISABLED state */ break; } /* LACP_PORT_DISABLED -> LACP_EXPIRED */ /* FALLTHROUGH */ case LACP_EXPIRED: /* * Arrives here from LACP_PORT_DISABLED state as well as * as well as current_while_timer expiring. */ pl->PartnerOperPortState.bit.sync = B_FALSE; pl->PartnerOperPortState.bit.timeout = B_TRUE; pl->ActorOperPortState.bit.expired = B_TRUE; start_current_while_timer(portp, SHORT_TIMEOUT_TIME); lacp_periodic_sm(portp); break; case LACP_DISABLED: /* * This is the normal state for recv_sm when LACP_OFF * is set or the NIC is in half duplex mode. */ lacp_port_unselect(portp); record_Default(portp); pl->PartnerOperPortState.bit.aggregation = B_FALSE; pl->ActorOperPortState.bit.expired = B_FALSE; break; case LACP_DEFAULTED: /* * Current_while_timer expired a second time. */ update_default_selected(portp); record_Default(portp); /* overwrite Partner Oper val */ pl->ActorOperPortState.bit.expired = B_FALSE; pl->PartnerOperPortState.bit.sync = B_TRUE; lacp_selection_logic(portp); lacp_mux_sm(portp); break; case LACP_CURRENT: /* * Reception of LACPDU */ if (!lacp) /* no LACPDU so current_while_timer popped */ break; AGGR_LACP_DBG(("lacp_receive_sm: (%d): LACPDU received:\n", portp->lp_linkid)); /* * Validate Actor_Information_Length, * Partner_Information_Length, Collector_Information_Length, * and Terminator_Length fields. */ if (!valid_lacp_pdu(portp, lacp)) { AGGR_LACP_DBG(("lacp_receive_sm (%d): " "Invalid LACPDU received\n", portp->lp_linkid)); break; } save_activity = pl->PartnerOperPortState.bit.activity; selected_updated = update_selected(portp, lacp); update_NTT(portp, lacp); sync_updated = record_PDU(portp, lacp); pl->ActorOperPortState.bit.expired = B_FALSE; if (selected_updated) { lacp_selection_logic(portp); lacp_mux_sm(portp); } else if (sync_updated) { lacp_mux_sm(portp); } /* * If the periodic timer value bit has been modified * or the partner activity bit has been changed then * we need to respectively: * - restart the timer with the proper timeout value. * - possibly enable/disable transmission of LACPDUs. */ if ((pl->PartnerOperPortState.bit.timeout && (pl->periodic_timer.val != FAST_PERIODIC_TIME)) || (!pl->PartnerOperPortState.bit.timeout && (pl->periodic_timer.val != SLOW_PERIODIC_TIME)) || (pl->PartnerOperPortState.bit.activity != save_activity)) { lacp_periodic_sm(portp); } stop_current_while_timer(portp); /* Check if we need to transmit an LACPDU */ if (pl->NTT) lacp_xmit_sm(portp); start_current_while_timer(portp, 0); break; } } static void aggr_set_coll_dist(aggr_port_t *portp, boolean_t enable) { mac_perim_handle_t mph; AGGR_LACP_DBG(("AGGR_SET_COLL_DIST_TYPE: (%d) %s\n", portp->lp_linkid, enable ? "ENABLED" : "DISABLED")); mac_perim_enter_by_mh(portp->lp_mh, &mph); if (!enable) { /* * Turn OFF Collector_Distributor. */ portp->lp_collector_enabled = B_FALSE; aggr_send_port_disable(portp); goto done; } /* * Turn ON Collector_Distributor. */ if (!portp->lp_lacp.sm.lacp_on || (portp->lp_lacp.sm.lacp_on && (portp->lp_lacp.sm.mux_state == LACP_COLLECTING_DISTRIBUTING))) { /* Port is compatible and can be aggregated */ portp->lp_collector_enabled = B_TRUE; aggr_send_port_enable(portp); } done: mac_perim_exit(mph); } /* * Because the LACP packet processing needs to enter the aggr's mac perimeter * and that would potentially cause a deadlock with the thread in which the * grp/port is deleted, we defer the packet process to a worker thread. Here * we only enqueue the received Marker or LACPDU for later processing. */ void aggr_lacp_rx_enqueue(aggr_port_t *portp, mblk_t *dmp) { aggr_grp_t *grp = portp->lp_grp; lacp_t *lacp; dmp->b_rptr += sizeof (struct ether_header); if (MBLKL(dmp) < sizeof (lacp_t)) { freemsg(dmp); return; } lacp = (lacp_t *)dmp->b_rptr; if (lacp->subtype != LACP_SUBTYPE && lacp->subtype != MARKER_SUBTYPE) { AGGR_LACP_DBG(("aggr_lacp_rx_enqueue: (%d): " "Unknown Slow Protocol type %d\n", portp->lp_linkid, lacp->subtype)); freemsg(dmp); return; } mutex_enter(&grp->lg_lacp_lock); /* * If the lg_lacp_done is set, this aggregation is in the process of * being deleted, return directly. */ if (grp->lg_lacp_done) { mutex_exit(&grp->lg_lacp_lock); freemsg(dmp); return; } if (grp->lg_lacp_tail == NULL) { grp->lg_lacp_head = grp->lg_lacp_tail = dmp; } else { grp->lg_lacp_tail->b_next = dmp; grp->lg_lacp_tail = dmp; } /* * Hold a reference of the port so that the port won't be freed when it * is removed from the aggr. The b_prev field is borrowed to save the * port information. */ AGGR_PORT_REFHOLD(portp); dmp->b_prev = (mblk_t *)portp; cv_broadcast(&grp->lg_lacp_cv); mutex_exit(&grp->lg_lacp_lock); } static void aggr_lacp_rx(mblk_t *dmp) { aggr_port_t *portp = (aggr_port_t *)dmp->b_prev; mac_perim_handle_t mph; lacp_t *lacp; dmp->b_prev = NULL; mac_perim_enter_by_mh(portp->lp_grp->lg_mh, &mph); if (portp->lp_closing) goto done; lacp = (lacp_t *)dmp->b_rptr; switch (lacp->subtype) { case LACP_SUBTYPE: AGGR_LACP_DBG(("aggr_lacp_rx:(%d): LACPDU received.\n", portp->lp_linkid)); if (!portp->lp_lacp.sm.lacp_on) { break; } lacp_receive_sm(portp, lacp); break; case MARKER_SUBTYPE: AGGR_LACP_DBG(("aggr_lacp_rx:(%d): Marker Packet received.\n", portp->lp_linkid)); if (receive_marker_pdu(portp, dmp) != 0) break; /* Send the packet over the first TX ring */ dmp = mac_hwring_send_priv(portp->lp_mch, portp->lp_tx_rings[0], dmp); if (dmp != NULL) freemsg(dmp); mac_perim_exit(mph); AGGR_PORT_REFRELE(portp); return; } done: mac_perim_exit(mph); AGGR_PORT_REFRELE(portp); freemsg(dmp); } void aggr_lacp_rx_thread(void *arg) { callb_cpr_t cprinfo; aggr_grp_t *grp = (aggr_grp_t *)arg; aggr_port_t *port; mblk_t *mp, *nextmp; CALLB_CPR_INIT(&cprinfo, &grp->lg_lacp_lock, callb_generic_cpr, "aggr_lacp_rx_thread"); mutex_enter(&grp->lg_lacp_lock); /* * Quit the thread if the grp is deleted. */ while (!grp->lg_lacp_done) { if ((mp = grp->lg_lacp_head) == NULL) { CALLB_CPR_SAFE_BEGIN(&cprinfo); cv_wait(&grp->lg_lacp_cv, &grp->lg_lacp_lock); CALLB_CPR_SAFE_END(&cprinfo, &grp->lg_lacp_lock); continue; } grp->lg_lacp_head = grp->lg_lacp_tail = NULL; mutex_exit(&grp->lg_lacp_lock); while (mp != NULL) { nextmp = mp->b_next; mp->b_next = NULL; aggr_lacp_rx(mp); mp = nextmp; } mutex_enter(&grp->lg_lacp_lock); } /* * The grp is being destroyed, simply free all of the LACP messages * left in the queue which did not have the chance to be processed. * We cannot use freemsgchain() here since we need to clear the * b_prev field. */ for (mp = grp->lg_lacp_head; mp != NULL; mp = nextmp) { port = (aggr_port_t *)mp->b_prev; AGGR_PORT_REFRELE(port); nextmp = mp->b_next; mp->b_next = NULL; mp->b_prev = NULL; freemsg(mp); } grp->lg_lacp_head = grp->lg_lacp_tail = NULL; grp->lg_lacp_rx_thread = NULL; cv_broadcast(&grp->lg_lacp_cv); CALLB_CPR_EXIT(&cprinfo); thread_exit(); }