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