/*
* 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
* 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
*/
/*
*/
/*
* IEEE 802.3ad Link Aggregation - LACP & Marker Protocol processing.
*/
#include <sys/sysmacros.h>
#include <sys/byteorder.h>
#include <sys/isa_defs.h>
#include <sys/aggr_impl.h>
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/*
* Slow_Protocol_Multicast address, as per IEEE 802.3ad spec.
*/
0x01, 0x80, 0xc2, 0x00, 0x00, 0x02
};
#ifdef DEBUG
/* LACP state machine debugging support */
#else
#define AGGR_LACP_DBG(x) {}
#endif /* DEBUG */
/* used by lacp_misconfig_walker() */
typedef struct lacp_misconfig_check_state_s {
/*
* Maintains a list of all ports in ATTACHED state. This information
* is used to detect misconfiguration.
*/
typedef struct lacp_sel_ports {
/* Note: sp_partner_system must be 2-byte aligned */
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 lacp_on(aggr_port_t *);
static void lacp_off(aggr_port_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 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)
{
}
void
aggr_lacp_fini(void)
{
}
/*
* 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
* routines and is checked by a dedicated thread, that executes the real
* timeout operation.
*/
static void
{
"aggr_port_timer_thread");
for (;;) {
continue;
}
pl->lacp_timer_bits = 0;
if (lacp_timer_bits & LACP_THREAD_EXIT)
break;
if (port->lp_closing) {
break;
}
break;
}
pl->lacp_timer_bits = 0;
/* CALLB_CPR_EXIT drops the lock */
/*
* Release the reference of the grp so aggr_grp_delete() can call
* mac_unregister() safely.
*/
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
{
if (!lacp_sel_ports_add(portp))
return (B_FALSE);
return (B_TRUE);
}
/*
* Set the port LACP state to UNSELECTED.
*/
static void
{
}
/*
* Initialize group specific LACP state and parameters.
*/
void
{
}
/*
* Complete LACP info initialization at port creation time.
*/
void
{
/* actor port # */
AGGR_LACP_DBG(("aggr_lacp_init_port(%d): "
pl->ActorPortNumber));
AGGR_LACP_DBG(("aggr_lacp_init_port(%d) "
"ActorAdminPortKey = 0x%x, ActorAdminPortKey = 0x%x\n",
/* Actor admin. port state */
/*
* Partner Administrative Information
* (All initialized to zero except for the following)
* Fast Timeouts.
*/
/*
* State machine information.
*/
/*
* Timer information.
*/
pl->lacp_timer_bits = 0;
/*
* 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().
*/
}
/*
* Port initialization when we need to
* reset like in the above routine.
* Do NOT modify things like link status.
*/
static void
{
/* reset operational port state */
/*
* State machine information.
*/
/*
* Timer information.
*/
}
static void
{
return;
(uchar_t *)&slow_multicast_addr);
}
static void
{
return;
(uchar_t *)&slow_multicast_addr);
}
static void
{
}
}
static void
{
}
if (id != 0)
}
/*
* 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
{
}
/*
* 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
{
/* current timestamp */
/*
* Set Periodic State machine state based on the
* value of the Partner Operation Port State timeout
* bit.
*/
} else {
}
}
/*
* 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_OFF state not in specification so check here. */
/* Stop timer whether it is running or not */
AGGR_LACP_DBG(("lacp_periodic_sm(%d):NO LACP "
return;
}
/* Stop timer whether it is running or not */
AGGR_LACP_DBG(("lacp_periodic_sm(%d):STOP %s--->%s\n",
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 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.
*/
}
/* Rearm timer with value provided by partner */
}
/*
* This routine transmits an LACPDU if lacp_enabled
* is TRUE and if NTT is set.
*/
static void
{
/* LACP_OFF state not in specification so check here. */
return;
/*
* Do nothing if LACP has been turned off or if the
* periodic state machine is not enabled.
*/
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.
*/
/*
* Grab the current time value and see if
* more than 1 second has passed. If so,
* reset the timestamp and clear the count.
*/
if (elapsed > NSECS_PER_SEC) {
} else {
return;
}
}
return;
/* Send the packet over the first TX ring */
}
/*
* Initialize the ethernet header of a LACP packet sent from the specified
* port.
*/
static void
{
}
static void
{
/*
* Actor Information
*/
/*
* Partner Information
*/
/* Collector Information */
/* Termination Information */
}
/*
* 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_OFF state not in specification so check here. */
AGGR_LACP_DBG(("trunk link: (%d): "
"Collector_Distributor Disabled.\n",
}
return;
}
/* determine next state, or return if state unchanged */
case LACP_DETACHED:
break;
}
break;
}
return;
case LACP_WAITING:
break;
}
break;
}
return;
case LACP_ATTACHED:
break;
}
break;
}
return;
break;
}
return;
}
AGGR_LACP_DBG(("lacp_mux_sm(%d):%s--->%s\n",
/* perform actions on entering a new state */
case LACP_DETACHED:
AGGR_LACP_DBG(("trunk link: (%d): "
"Collector_Distributor Disabled.\n",
}
/* Turn OFF Collector_Distributor */
break;
case LACP_WAITING:
break;
case LACP_ATTACHED:
AGGR_LACP_DBG(("trunk link: (%d): "
"Collector_Distributor Disabled.\n",
}
/* Turn OFF Collector_Distributor */
/*
* We had already received an updated sync from
* the partner. Attempt to transition to
* collecting/distributing now.
*/
goto again;
}
break;
AGGR_LACP_DBG(("trunk link: (%d): "
"Collector_Distributor Enabled.\n",
}
/* Turn Collector_Distributor back ON */
break;
}
/*
* If we updated the state of the NTT variable, then
* initiate a LACPDU transmission.
*/
if (NTT_updated) {
}
} /* lacp_mux_sm */
static int
{
AGGR_LACP_DBG(("trunk link: (%d): MARKER PDU received:\n",
/* LACP_OFF state not in specification so check here. */
return (-1);
return (-1);
AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: "
"version = %d does not match s/w version %d\n",
return (-1);
}
/* 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",
return (-1);
}
AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: "
markerp->tlv_marker));
return (-1);
}
AGGR_LACP_DBG(("trunk link (%d): Malformed MARKER PDU: "
markerp->marker_len));
return (-1);
}
AGGR_LACP_DBG(("trunk link (%d): MARKER PDU: "
" MARKER Port %d not equal to Partner port %d\n",
return (-1);
}
AGGR_LACP_DBG(("trunk link (%d): MARKER PDU: "
" MARKER MAC not equal to Partner MAC\n",
return (-1);
}
/*
* Turn into Marker Response PDU
* and return mblk to sending system
*/
/* reuse the space that was used by received ethernet header */
return (0);
}
/*
* Update the LACP mode (off, active, or passive) of the specified group.
*/
void
{
return;
(mode == AGGR_LACP_ACTIVE);
if (old_mode == AGGR_LACP_OFF) {
/* OFF -> {PASSIVE,ACTIVE} */
/* turn OFF Collector_Distributor */
} else if (mode == AGGR_LACP_OFF) {
/* {PASSIVE,ACTIVE} -> OFF */
/* Turn ON Collector_Distributor */
} else {
/* PASSIVE->ACTIVE or ACTIVE->PASSIVE */
/* kick off state machines */
}
}
}
/*
* Update the LACP timer (short or long) of the specified group.
*/
void
{
return;
(timer == AGGR_LACP_TIMER_SHORT);
}
}
void
{
(mode == AGGR_LACP_ACTIVE);
(timer == AGGR_LACP_TIMER_SHORT);
if (mode == AGGR_LACP_OFF) {
/* Turn ON Collector_Distributor */
} else { /* LACP_ACTIVE/PASSIVE */
}
}
/*
* Sets the initial LACP mode (off, active, passive) and LACP timer
* (short, long) of the specified group.
*/
void
{
}
/*
* 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
{
/* skip entries of the group of the port being checked */
continue;
/*
* The Partner port information is already in use
* by ports in another aggregation so disable this
* port.
*/
"%x:%x:%x:%x:%x:%x",
"MAC %s and key %d in use on aggregation %d "
break;
}
}
}
/*
* Remove the specified port from the list of selected ports.
*/
static void
{
break;
}
return;
}
}
/*
* 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
{
/* check if port is already in the list */
return (B_TRUE);
}
}
/* create and initialize new entry */
return (B_FALSE);
}
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
*/
static void
{
int ports_waiting;
/* LACP_OFF state not in specification so check here. */
return;
}
AGGR_LACP_DBG(("lacp_selection_logic:(%d): "
"selected %d-->%d (begin=%d, lacp_enabled = %d, "
return;
}
/*
* If LACP is not enabled then selected is never set.
*/
AGGR_LACP_DBG(("lacp_selection_logic:(%d): selected %d-->%d\n",
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.
*/
(pl->PartnerOperKey == 0)) {
ðerzeroaddr) != 0 &&
break;
}
/*
* If all ports have no key or aggregation address,
* then clear the negotiated Partner MAC and key.
*/
/* Clear the aggregation Partner MAC and key */
}
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.
*/
/* Set aggregation Partner MAC and key */
break;
}
}
/* Clear the aggregation Partner MAC and key */
}
}
/*
* If our Actor MAC is found in the Partner MAC
* on this port then we have a loopback misconfiguration.
*/
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)) {
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.
*/
/* Set aggregation Partner MAC and key */
/*
* If we reset Partner aggregation MAC, then restart
* selection_logic on ports that match new MAC address.
*/
if (reset_mac) {
continue;
}
}
/*
* The Partner port information does not match
* that of the other ports in the aggregation
* so disable this port.
*/
"or key (%d) incompatible with Aggregation Partner "
return;
}
/* If we get to here, automatically set selected */
AGGR_LACP_DBG(("lacp_selection_logic:(%d): "
if (!lacp_port_select(portp))
return;
}
/*
* 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 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.
*/
/* Add up ports uninitialized or waiting */
aggr_port_t *, tpp);
return;
}
}
}
}
AGGR_LACP_DBG(("lacp_selection_logic:(%d): "
} else {
AGGR_LACP_DBG(("lacp_selection_logic:(%d): Ready %d-->%d\n",
}
}
/*
* 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_handler - When the timer pops, we arrive here to
* set ready_n and trigger the selection logic.
*/
static void
{
AGGR_LACP_DBG(("trunk link:(%d): wait_while_timer pop \n",
}
static void
{
drv_usectohz(1000000 *
}
}
static void
{
}
if (id != 0)
}
/*
* 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_DBG(("aggr_lacp_port_attached: port %d\n",
return;
/* Enable Multicast Slow Protocol address */
/* periodic_sm is started up from the receive machine */
}
/*
* Invoked when a port has been detached from a group. Turn off
* LACP processing if it was enabled.
*/
void
{
AGGR_LACP_DBG(("aggr_lacp_port_detached: port %d\n",
return;
/*
* Disable Slow Protocol Timers.
*/
/* Disable Multicast Slow Protocol address */
}
/*
* Enable Slow Protocol LACP and Marker PDUs.
*/
static void
{
/*
* Reset the state machines and Partner operational
* information. Careful to not reset things like
* our link state.
*/
}
/* Enable Multicast Slow Protocol address */
/* periodic_sm is started up from the receive machine */
}
done:
} /* lacp_on */
/* Disable Slow Protocol LACP and Marker PDUs */
static void
{
/*
* Disable Slow Protocol Timers.
*/
/* Disable Multicast Slow Protocol address */
}
/* Turn OFF Collector_Distributor */
}
static boolean_t
{
/*
* 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."
*/
AGGR_LACP_DBG(("trunk link (%d): Malformed LACPDU: "
lacp->terminator_len));
return (B_FALSE);
}
return (B_TRUE);
}
static void
{
if (time > 0)
else
}
}
static void
{
}
if (id != 0)
}
static void
{
}
static void
{
AGGR_LACP_DBG(("trunk link:(%d): current_while_timer "
}
/*
* 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
{
}
/* Returns B_TRUE on sync value changing */
static boolean_t
{
/*
* Partner Information
*/
/* All state info except for Synchronization */
/* Defaulted set to 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.
*/
pl->ActorPortPriority) &&
} else {
}
AGGR_LACP_DBG(("record_PDU:(%d): partner 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
{
(pl->PartnerOperSysPriority !=
AGGR_LACP_DBG(("update_selected:(%d): "
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
{
AGGR_LACP_DBG(("update_default_selected:(%d): "
}
}
/*
* 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
{
(pl->ActorPortPriority !=
AGGR_LACP_DBG(("update_NTT:(%d): NTT %d-->%d\n",
}
}
/*
* 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_OFF state not in specification so check here. */
return;
/* figure next state */
}
}
AGGR_LACP_DBG(("lacp_receive_sm(%d):%s--->%s\n",
}
case LACP_INITIALIZE:
break;
case LACP_PORT_DISABLED:
/*
* Stop current_while_timer in case
* we got here from link down
*/
/* We goto LACP_DISABLED state */
break;
/*
* 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.
*/
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.
*/
break;
case LACP_DEFAULTED:
/*
* Current_while_timer expired a second time.
*/
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",
/*
* Validate Actor_Information_Length,
* Partner_Information_Length, Collector_Information_Length,
* and Terminator_Length fields.
*/
AGGR_LACP_DBG(("lacp_receive_sm (%d): "
"Invalid LACPDU received\n",
break;
}
if (selected_updated) {
} else if (sync_updated) {
}
/*
* 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.
*/
save_activity)) {
}
/* Check if we need to transmit an LACPDU */
break;
}
}
static void
{
AGGR_LACP_DBG(("AGGR_SET_COLL_DIST_TYPE: (%d) %s\n",
if (!enable) {
/*
* Turn OFF Collector_Distributor.
*/
goto done;
}
/*
* Turn ON Collector_Distributor.
*/
/* Port is compatible and can be aggregated */
}
done:
}
/*
* 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
* we only enqueue the received Marker or LACPDU for later processing.
*/
void
{
return;
}
AGGR_LACP_DBG(("aggr_lacp_rx_enqueue: (%d): "
"Unknown Slow Protocol type %d\n",
return;
}
/*
* If the lg_lacp_done is set, this aggregation is in the process of
* being deleted, return directly.
*/
if (grp->lg_lacp_done) {
return;
}
} else {
}
/*
* 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.
*/
}
static void
{
if (portp->lp_closing)
goto done;
case LACP_SUBTYPE:
AGGR_LACP_DBG(("aggr_lacp_rx:(%d): LACPDU received.\n",
break;
}
break;
case MARKER_SUBTYPE:
AGGR_LACP_DBG(("aggr_lacp_rx:(%d): Marker Packet received.\n",
break;
/* Send the packet over the first TX ring */
return;
}
done:
}
void
{
"aggr_lacp_rx_thread");
/*
* Quit the thread if the grp is deleted.
*/
while (!grp->lg_lacp_done) {
continue;
}
}
}
/*
* 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.
*/
}
thread_exit();
}