xref: /illumos-gate/usr/src/uts/common/io/aggr/aggr_port.c (revision 0dee7919e2f2a6479d16b370af93747b9416b242)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * IEEE 802.3ad Link Aggregation - Link Aggregation MAC ports.
31  *
32  * Implements the functions needed to manage the MAC ports that are
33  * part of Link Aggregation groups.
34  */
35 
36 #include <sys/types.h>
37 #include <sys/sysmacros.h>
38 #include <sys/conf.h>
39 #include <sys/cmn_err.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 
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 int
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 
95 	kmem_cache_destroy(aggr_port_cache);
96 	return (0);
97 }
98 
99 mac_resource_handle_t
100 aggr_port_resource_add(void *arg, mac_resource_t *mrp)
101 {
102 	aggr_port_t *port = (aggr_port_t *)arg;
103 	aggr_grp_t *grp = port->lp_grp;
104 
105 	return (mac_resource_add(&grp->lg_mac, mrp));
106 }
107 
108 int
109 aggr_port_create(const char *name, uint_t portnum, aggr_port_t **pp)
110 {
111 	int err;
112 	mac_handle_t mh;
113 	aggr_port_t *port;
114 	uint_t i;
115 
116 	*pp = NULL;
117 
118 	if ((err = mac_open(name, portnum, &mh)) != 0)
119 		return (err);
120 
121 	if (!mac_active_set(mh)) {
122 		mac_close(mh);
123 		return (EBUSY);
124 	}
125 
126 	port = kmem_cache_alloc(aggr_port_cache, KM_SLEEP);
127 
128 	port->lp_refs = 1;
129 	port->lp_next = NULL;
130 	port->lp_mh = mh;
131 	port->lp_mip = mac_info(mh);
132 	port->lp_port = portnum;
133 	(void) strlcpy(port->lp_devname, name, sizeof (port->lp_devname));
134 	port->lp_closing = B_FALSE;
135 	port->lp_set_grpmac = B_FALSE;
136 
137 	/* get the port's original MAC address */
138 	mac_unicst_get(port->lp_mh, port->lp_addr);
139 
140 	/* add the port's receive callback */
141 	port->lp_mnh = mac_notify_add(mh, aggr_port_notify_cb, (void *)port);
142 
143 	/* set port's receive callback */
144 	port->lp_mrh = mac_rx_add(mh, aggr_recv_cb, (void *)port);
145 
146 	/* set port's transmit information */
147 	port->lp_txinfo = mac_tx_get(port->lp_mh);
148 
149 	/* set port's ring add and ring remove callbacks */
150 	mac_resource_set(mh, aggr_port_resource_add, (void *)port);
151 
152 	/* initialize state */
153 	port->lp_state = AGGR_PORT_STATE_STANDBY;
154 	port->lp_link_state = LINK_STATE_UNKNOWN;
155 	port->lp_ifspeed = 0;
156 	port->lp_link_duplex = LINK_DUPLEX_UNKNOWN;
157 	port->lp_started = B_FALSE;
158 	port->lp_tx_enabled = B_FALSE;
159 	port->lp_promisc_on = B_FALSE;
160 
161 	/*
162 	 * Save the current statistics of the port. They will be used
163 	 * later by aggr_m_stats() when aggregating the stastics of
164 	 * the consistituent ports.
165 	 */
166 	for (i = 0; i < MAC_NSTAT; i++) {
167 		/* avoid non-counter stats */
168 		if (i == MAC_STAT_IFSPEED || i == MAC_STAT_LINK_DUPLEX)
169 			continue;
170 		port->lp_stat[i] = aggr_port_stat(port, i);
171 	}
172 
173 	/* LACP related state */
174 	port->lp_collector_enabled = B_FALSE;
175 
176 	*pp = port;
177 	return (0);
178 }
179 
180 void
181 aggr_port_delete(aggr_port_t *port)
182 {
183 	mac_resource_set(port->lp_mh, NULL, NULL);
184 	mac_rx_remove(port->lp_mh, port->lp_mrh);
185 	mac_notify_remove(port->lp_mh, port->lp_mnh);
186 	mac_active_clear(port->lp_mh);
187 
188 	/*
189 	 * Restore the port MAC address. Note it is called after the
190 	 * port's notification callback being removed. This prevent
191 	 * port's MAC_NOTE_UNICST notify callback function being called.
192 	 */
193 	(void) mac_unicst_set(port->lp_mh, port->lp_addr);
194 
195 	mac_close(port->lp_mh);
196 	AGGR_PORT_REFRELE(port);
197 }
198 
199 void
200 aggr_port_free(aggr_port_t *port)
201 {
202 	ASSERT(port->lp_refs == 0);
203 	if (port->lp_grp != NULL)
204 		AGGR_GRP_REFRELE(port->lp_grp);
205 	port->lp_grp = NULL;
206 	kmem_cache_free(aggr_port_cache, port);
207 }
208 
209 /*
210  * Invoked upon receiving a MAC_NOTE_LINK notification for
211  * one of the consistuent ports.
212  */
213 static boolean_t
214 aggr_port_notify_link(aggr_grp_t *grp, aggr_port_t *port)
215 {
216 	boolean_t do_attach = B_FALSE;
217 	boolean_t do_detach = B_FALSE;
218 	boolean_t grp_link_changed = B_TRUE;
219 	uint64_t ifspeed;
220 	link_state_t link_state;
221 	link_duplex_t link_duplex;
222 
223 	AGGR_LACP_LOCK(grp);
224 	rw_enter(&grp->lg_lock, RW_WRITER);
225 	rw_enter(&port->lp_lock, RW_WRITER);
226 
227 	/* link state change? */
228 	link_state = mac_link_get(port->lp_mh);
229 	if (port->lp_link_state != link_state) {
230 		if (link_state == LINK_STATE_UP)
231 			do_attach = (port->lp_link_state != LINK_STATE_UP);
232 		else
233 			do_detach = (port->lp_link_state == LINK_STATE_UP);
234 	}
235 	port->lp_link_state = link_state;
236 
237 	/* link duplex change? */
238 	link_duplex = aggr_port_stat(port, MAC_STAT_LINK_DUPLEX);
239 	if (port->lp_link_duplex != link_duplex) {
240 		if (link_duplex == LINK_DUPLEX_FULL)
241 			do_attach |= (port->lp_link_duplex != LINK_DUPLEX_FULL);
242 		else
243 			do_detach |= (port->lp_link_duplex == LINK_DUPLEX_FULL);
244 	}
245 	port->lp_link_duplex = link_duplex;
246 
247 	/* link speed changes? */
248 	ifspeed = aggr_port_stat(port, MAC_STAT_IFSPEED);
249 	if (port->lp_ifspeed != ifspeed) {
250 		if (port->lp_state == AGGR_PORT_STATE_ATTACHED)
251 			do_detach |= (ifspeed != grp->lg_ifspeed);
252 		else
253 			do_attach |= (ifspeed == grp->lg_ifspeed);
254 	}
255 	port->lp_ifspeed = ifspeed;
256 
257 	if (do_attach) {
258 		/* attempt to attach the port to the aggregation */
259 		grp_link_changed = aggr_grp_attach_port(grp, port);
260 	} else if (do_detach) {
261 		/* detach the port from the aggregation */
262 		grp_link_changed = aggr_grp_detach_port(grp, port);
263 	}
264 
265 	rw_exit(&port->lp_lock);
266 	rw_exit(&grp->lg_lock);
267 	AGGR_LACP_UNLOCK(grp);
268 
269 	return (grp_link_changed);
270 }
271 
272 /*
273  * Invoked upon receiving a MAC_NOTE_UNICST for one of the constituent
274  * ports of a group.
275  */
276 static boolean_t
277 aggr_port_notify_unicst(aggr_grp_t *grp, aggr_port_t *port)
278 {
279 	boolean_t grp_mac_changed;
280 
281 	/*
282 	 * If it is called when setting the MAC address to the
283 	 * aggregation group MAC address, do nothing.
284 	 */
285 	if (port->lp_set_grpmac)
286 		return (B_FALSE);
287 
288 	AGGR_LACP_LOCK(grp);
289 	rw_enter(&grp->lg_lock, RW_WRITER);
290 	rw_enter(&port->lp_lock, RW_WRITER);
291 
292 	/* save the new port MAC address */
293 	mac_unicst_get(port->lp_mh, port->lp_addr);
294 
295 	grp_mac_changed = aggr_grp_port_mac_changed(grp, port);
296 
297 	rw_exit(&port->lp_lock);
298 
299 	if (grp->lg_closing) {
300 		rw_exit(&grp->lg_lock);
301 		AGGR_LACP_UNLOCK(grp);
302 		return (B_FALSE);
303 	}
304 
305 	/*
306 	 * If this port was used to determine the MAC address of
307 	 * the group, update the MAC address of the constituent
308 	 * ports.
309 	 */
310 	if (grp_mac_changed)
311 		aggr_grp_update_ports_mac(grp);
312 
313 	rw_exit(&grp->lg_lock);
314 	AGGR_LACP_UNLOCK(grp);
315 
316 	return (grp_mac_changed);
317 }
318 
319 /*
320  * Notification callback invoked by the MAC service module for
321  * a particular MAC port.
322  */
323 static void
324 aggr_port_notify_cb(void *arg, mac_notify_type_t type)
325 {
326 	aggr_port_t *port = arg;
327 	aggr_grp_t *grp = port->lp_grp;
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) && !grp->lg_closing)
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