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