xref: /illumos-gate/usr/src/uts/common/io/aggr/aggr_port.c (revision 437220cd296f6d8b6654d6d52508b40b1e2d1ac7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * IEEE 802.3ad Link Aggregation - Link Aggregation MAC ports.
30  *
31  * Implements the functions needed to manage the MAC ports that are
32  * part of Link Aggregation groups.
33  */
34 
35 #include <sys/types.h>
36 #include <sys/sysmacros.h>
37 #include <sys/conf.h>
38 #include <sys/cmn_err.h>
39 #include <sys/list.h>
40 #include <sys/ksynch.h>
41 #include <sys/kmem.h>
42 #include <sys/stream.h>
43 #include <sys/modctl.h>
44 #include <sys/ddi.h>
45 #include <sys/sunddi.h>
46 #include <sys/atomic.h>
47 #include <sys/stat.h>
48 #include <sys/sdt.h>
49 #include <sys/dlpi.h>
50 
51 #include <sys/aggr.h>
52 #include <sys/aggr_impl.h>
53 
54 static kmem_cache_t *aggr_port_cache;
55 static void aggr_port_notify_cb(void *, mac_notify_type_t);
56 
57 /*ARGSUSED*/
58 static int
59 aggr_port_constructor(void *buf, void *arg, int kmflag)
60 {
61 	aggr_port_t *port = buf;
62 
63 	bzero(buf, sizeof (aggr_port_t));
64 	rw_init(&port->lp_lock, NULL, RW_DRIVER, NULL);
65 
66 	return (0);
67 }
68 
69 /*ARGSUSED*/
70 static void
71 aggr_port_destructor(void *buf, void *arg)
72 {
73 	aggr_port_t *port = buf;
74 
75 	rw_destroy(&port->lp_lock);
76 }
77 
78 void
79 aggr_port_init(void)
80 {
81 	aggr_port_cache = kmem_cache_create("aggr_port_cache",
82 	    sizeof (aggr_port_t), 0, aggr_port_constructor,
83 	    aggr_port_destructor, NULL, NULL, NULL, 0);
84 }
85 
86 void
87 aggr_port_fini(void)
88 {
89 	/*
90 	 * This function is called only after all groups have been
91 	 * freed. This ensures that there are no remaining allocated
92 	 * ports when this function is invoked.
93 	 */
94 	kmem_cache_destroy(aggr_port_cache);
95 }
96 
97 mac_resource_handle_t
98 aggr_port_resource_add(void *arg, mac_resource_t *mrp)
99 {
100 	aggr_port_t *port = (aggr_port_t *)arg;
101 	aggr_grp_t *grp = port->lp_grp;
102 
103 	return (mac_resource_add(grp->lg_mh, mrp));
104 }
105 
106 void
107 aggr_port_init_callbacks(aggr_port_t *port)
108 {
109 	/* add the port's receive callback */
110 	port->lp_mnh = mac_notify_add(port->lp_mh, aggr_port_notify_cb,
111 	    (void *)port);
112 
113 	/* set port's resource_add callback */
114 	mac_resource_set(port->lp_mh, aggr_port_resource_add, (void *)port);
115 }
116 
117 int
118 aggr_port_create(const char *name, aggr_port_t **pp)
119 {
120 	int err;
121 	mac_handle_t mh;
122 	aggr_port_t *port;
123 	uint_t i;
124 	const mac_info_t *mip;
125 	char driver[MAXNAMELEN];
126 	uint_t ddi_instance;
127 
128 	*pp = NULL;
129 
130 	if (ddi_parse(name, driver, &ddi_instance) != DDI_SUCCESS)
131 		return (EINVAL);
132 
133 	if ((err = mac_open(name, ddi_instance, &mh)) != 0)
134 		return (err);
135 
136 	mip = mac_info(mh);
137 	if (mip->mi_media != DL_ETHER || mip->mi_nativemedia != DL_ETHER) {
138 		mac_close(mh);
139 		return (EINVAL);
140 	}
141 
142 	if (!mac_active_set(mh)) {
143 		mac_close(mh);
144 		return (EBUSY);
145 	}
146 
147 	port = kmem_cache_alloc(aggr_port_cache, KM_SLEEP);
148 
149 	port->lp_refs = 1;
150 	port->lp_next = NULL;
151 	port->lp_mh = mh;
152 	port->lp_mip = mip;
153 	(void) strlcpy(port->lp_devname, name, sizeof (port->lp_devname));
154 	port->lp_closing = 0;
155 
156 	/* get the port's original MAC address */
157 	mac_unicst_get(port->lp_mh, port->lp_addr);
158 
159 	/* set port's transmit information */
160 	port->lp_txinfo = mac_tx_get(port->lp_mh);
161 
162 	/* initialize state */
163 	port->lp_state = AGGR_PORT_STATE_STANDBY;
164 	port->lp_link_state = LINK_STATE_UNKNOWN;
165 	port->lp_ifspeed = 0;
166 	port->lp_link_duplex = LINK_DUPLEX_UNKNOWN;
167 	port->lp_started = B_FALSE;
168 	port->lp_tx_enabled = B_FALSE;
169 	port->lp_promisc_on = B_FALSE;
170 
171 	/*
172 	 * Save the current statistics of the port. They will be used
173 	 * later by aggr_m_stats() when aggregating the stastics of
174 	 * the consistituent ports.
175 	 */
176 	for (i = 0; i < MAC_NSTAT; i++) {
177 		port->lp_stat[i] =
178 		    aggr_port_stat(port, i + MAC_STAT_MIN);
179 	}
180 	for (i = 0; i < ETHER_NSTAT; i++) {
181 		port->lp_ether_stat[i] =
182 		    aggr_port_stat(port, i + MACTYPE_STAT_MIN);
183 	}
184 
185 	/* LACP related state */
186 	port->lp_collector_enabled = B_FALSE;
187 
188 	*pp = port;
189 	return (0);
190 }
191 
192 void
193 aggr_port_delete(aggr_port_t *port)
194 {
195 	mac_rx_remove_wait(port->lp_mh);
196 	mac_resource_set(port->lp_mh, NULL, NULL);
197 	mac_notify_remove(port->lp_mh, port->lp_mnh);
198 	mac_active_clear(port->lp_mh);
199 
200 	/*
201 	 * Restore the port MAC address. Note it is called after the
202 	 * port's notification callback being removed. This prevent
203 	 * port's MAC_NOTE_UNICST notify callback function being called.
204 	 */
205 	(void) mac_unicst_set(port->lp_mh, port->lp_addr);
206 
207 	mac_close(port->lp_mh);
208 	AGGR_PORT_REFRELE(port);
209 }
210 
211 void
212 aggr_port_free(aggr_port_t *port)
213 {
214 	ASSERT(port->lp_refs == 0);
215 	if (port->lp_grp != NULL)
216 		AGGR_GRP_REFRELE(port->lp_grp);
217 	port->lp_grp = NULL;
218 	kmem_cache_free(aggr_port_cache, port);
219 }
220 
221 /*
222  * Invoked upon receiving a MAC_NOTE_LINK notification for
223  * one of the consistuent ports.
224  */
225 boolean_t
226 aggr_port_notify_link(aggr_grp_t *grp, aggr_port_t *port, boolean_t dolock)
227 {
228 	boolean_t do_attach = B_FALSE;
229 	boolean_t do_detach = B_FALSE;
230 	boolean_t link_state_changed = B_TRUE;
231 	uint64_t ifspeed;
232 	link_state_t link_state;
233 	link_duplex_t link_duplex;
234 
235 	if (dolock) {
236 		AGGR_LACP_LOCK(grp);
237 		rw_enter(&grp->lg_lock, RW_WRITER);
238 	} else {
239 		ASSERT(AGGR_LACP_LOCK_HELD(grp));
240 		ASSERT(RW_WRITE_HELD(&grp->lg_lock));
241 	}
242 
243 	rw_enter(&port->lp_lock, RW_WRITER);
244 
245 	/* link state change? */
246 	link_state = mac_link_get(port->lp_mh);
247 	if (port->lp_link_state != link_state) {
248 		if (link_state == LINK_STATE_UP)
249 			do_attach = (port->lp_link_state != LINK_STATE_UP);
250 		else
251 			do_detach = (port->lp_link_state == LINK_STATE_UP);
252 	}
253 	port->lp_link_state = link_state;
254 
255 	/* link duplex change? */
256 	link_duplex = aggr_port_stat(port, ETHER_STAT_LINK_DUPLEX);
257 	if (port->lp_link_duplex != link_duplex) {
258 		if (link_duplex == LINK_DUPLEX_FULL)
259 			do_attach |= (port->lp_link_duplex != LINK_DUPLEX_FULL);
260 		else
261 			do_detach |= (port->lp_link_duplex == LINK_DUPLEX_FULL);
262 	}
263 	port->lp_link_duplex = link_duplex;
264 
265 	/* link speed changes? */
266 	ifspeed = aggr_port_stat(port, MAC_STAT_IFSPEED);
267 	if (port->lp_ifspeed != ifspeed) {
268 		if (port->lp_state == AGGR_PORT_STATE_ATTACHED)
269 			do_detach |= (ifspeed != grp->lg_ifspeed);
270 		else
271 			do_attach |= (ifspeed == grp->lg_ifspeed);
272 	}
273 	port->lp_ifspeed = ifspeed;
274 
275 	if (do_attach) {
276 		/* attempt to attach the port to the aggregation */
277 		link_state_changed = aggr_grp_attach_port(grp, port);
278 	} else if (do_detach) {
279 		/* detach the port from the aggregation */
280 		link_state_changed = aggr_grp_detach_port(grp, port);
281 	}
282 
283 	rw_exit(&port->lp_lock);
284 
285 	if (dolock) {
286 		rw_exit(&grp->lg_lock);
287 		AGGR_LACP_UNLOCK(grp);
288 	}
289 
290 	return (link_state_changed);
291 }
292 
293 /*
294  * Invoked upon receiving a MAC_NOTE_UNICST for one of the constituent
295  * ports of a group.
296  */
297 static void
298 aggr_port_notify_unicst(aggr_grp_t *grp, aggr_port_t *port,
299     boolean_t *mac_addr_changedp, boolean_t *link_state_changedp)
300 {
301 	boolean_t mac_addr_changed = B_FALSE;
302 	boolean_t link_state_changed = B_FALSE;
303 	uint8_t mac_addr[ETHERADDRL];
304 
305 	ASSERT(mac_addr_changedp != NULL);
306 	ASSERT(link_state_changedp != NULL);
307 
308 	AGGR_LACP_LOCK(grp);
309 	rw_enter(&grp->lg_lock, RW_WRITER);
310 
311 	rw_enter(&port->lp_lock, RW_WRITER);
312 
313 	/*
314 	 * If it is called when setting the MAC address to the
315 	 * aggregation group MAC address, do nothing.
316 	 */
317 	mac_unicst_get(port->lp_mh, mac_addr);
318 	if (bcmp(mac_addr, grp->lg_addr, ETHERADDRL) == 0) {
319 		rw_exit(&port->lp_lock);
320 		goto done;
321 	}
322 
323 	/* save the new port MAC address */
324 	bcopy(mac_addr, port->lp_addr, ETHERADDRL);
325 
326 	aggr_grp_port_mac_changed(grp, port, &mac_addr_changed,
327 	    &link_state_changed);
328 
329 	rw_exit(&port->lp_lock);
330 
331 	if (grp->lg_closing)
332 		goto done;
333 
334 	/*
335 	 * If this port was used to determine the MAC address of
336 	 * the group, update the MAC address of the constituent
337 	 * ports.
338 	 */
339 	if (mac_addr_changed) {
340 		link_state_changed = link_state_changed ||
341 		    aggr_grp_update_ports_mac(grp);
342 	}
343 
344 done:
345 	*mac_addr_changedp = mac_addr_changed;
346 	*link_state_changedp = link_state_changed;
347 	rw_exit(&grp->lg_lock);
348 	AGGR_LACP_UNLOCK(grp);
349 }
350 
351 /*
352  * Notification callback invoked by the MAC service module for
353  * a particular MAC port.
354  */
355 static void
356 aggr_port_notify_cb(void *arg, mac_notify_type_t type)
357 {
358 	aggr_port_t *port = arg;
359 	aggr_grp_t *grp = port->lp_grp;
360 	boolean_t mac_addr_changed, link_state_changed;
361 
362 	/*
363 	 * Do nothing if the aggregation or the port is in the deletion
364 	 * process. Note that this is necessary to avoid deadlock.
365 	 */
366 	if ((grp->lg_closing) || (port->lp_closing))
367 		return;
368 
369 	AGGR_PORT_REFHOLD(port);
370 
371 	switch (type) {
372 	case MAC_NOTE_TX:
373 		mac_tx_update(grp->lg_mh);
374 		break;
375 	case MAC_NOTE_LINK:
376 		if (aggr_port_notify_link(grp, port, B_TRUE))
377 			mac_link_update(grp->lg_mh, grp->lg_link_state);
378 		break;
379 	case MAC_NOTE_UNICST:
380 		aggr_port_notify_unicst(grp, port, &mac_addr_changed,
381 		    &link_state_changed);
382 		if (mac_addr_changed)
383 			mac_unicst_update(grp->lg_mh, grp->lg_addr);
384 		if (link_state_changed)
385 			mac_link_update(grp->lg_mh, grp->lg_link_state);
386 		break;
387 	case MAC_NOTE_PROMISC:
388 		port->lp_txinfo = mac_tx_get(port->lp_mh);
389 		break;
390 	default:
391 		break;
392 	}
393 
394 	AGGR_PORT_REFRELE(port);
395 }
396 
397 int
398 aggr_port_start(aggr_port_t *port)
399 {
400 	int rc;
401 
402 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
403 
404 	if (port->lp_started)
405 		return (0);
406 
407 	if ((rc = mac_start(port->lp_mh)) != 0)
408 		return (rc);
409 
410 	/* update the port state */
411 	port->lp_started = B_TRUE;
412 
413 	return (rc);
414 }
415 
416 void
417 aggr_port_stop(aggr_port_t *port)
418 {
419 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
420 
421 	if (!port->lp_started)
422 		return;
423 
424 	aggr_grp_multicst_port(port, B_FALSE);
425 
426 	mac_stop(port->lp_mh);
427 
428 	/* update the port state */
429 	port->lp_started = B_FALSE;
430 }
431 
432 int
433 aggr_port_promisc(aggr_port_t *port, boolean_t on)
434 {
435 	int rc;
436 
437 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
438 
439 	if (on == port->lp_promisc_on)
440 		/* already in desired promiscous mode */
441 		return (0);
442 
443 	rc = mac_promisc_set(port->lp_mh, on, MAC_DEVPROMISC);
444 
445 	if (rc == 0)
446 		port->lp_promisc_on = on;
447 
448 	return (rc);
449 }
450 
451 /*
452  * Set the MAC address of a port.
453  */
454 int
455 aggr_port_unicst(aggr_port_t *port, uint8_t *macaddr)
456 {
457 	int rc;
458 
459 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
460 
461 	rc = mac_unicst_set(port->lp_mh, macaddr);
462 
463 	return (rc);
464 }
465 
466 /*
467  * Add or remove a multicast address to/from a port.
468  */
469 int
470 aggr_port_multicst(void *arg, boolean_t add, const uint8_t *addrp)
471 {
472 	aggr_port_t *port = arg;
473 
474 	return (add ? mac_multicst_add(port->lp_mh, addrp) :
475 	    mac_multicst_remove(port->lp_mh, addrp));
476 }
477 
478 uint64_t
479 aggr_port_stat(aggr_port_t *port, uint_t stat)
480 {
481 	return (mac_stat_get(port->lp_mh, stat));
482 }
483