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