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