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