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