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 rw_enter(&grp->lg_lock, RW_WRITER); 289 rw_enter(&port->lp_lock, RW_WRITER); 290 291 /* save the new port MAC address */ 292 mac_unicst_get(port->lp_mh, port->lp_addr); 293 294 grp_mac_changed = aggr_grp_port_mac_changed(grp, port); 295 296 rw_exit(&port->lp_lock); 297 298 if (grp->lg_closing) { 299 rw_exit(&grp->lg_lock); 300 return (B_FALSE); 301 } 302 303 /* 304 * If this port was used to determine the MAC address of 305 * the group, update the MAC address of the constituent 306 * ports. 307 */ 308 if (grp_mac_changed) 309 aggr_grp_update_ports_mac(grp); 310 311 rw_exit(&grp->lg_lock); 312 313 return (grp_mac_changed); 314 } 315 316 /* 317 * Notification callback invoked by the MAC service module for 318 * a particular MAC port. 319 */ 320 static void 321 aggr_port_notify_cb(void *arg, mac_notify_type_t type) 322 { 323 aggr_port_t *port = arg; 324 aggr_grp_t *grp = port->lp_grp; 325 326 AGGR_PORT_REFHOLD(port); 327 328 switch (type) { 329 case MAC_NOTE_TX: 330 mac_tx_update(&grp->lg_mac); 331 break; 332 case MAC_NOTE_LINK: 333 if (aggr_port_notify_link(grp, port) && !grp->lg_closing) 334 mac_link_update(&grp->lg_mac, grp->lg_link_state); 335 break; 336 case MAC_NOTE_UNICST: 337 if (aggr_port_notify_unicst(grp, port)) 338 mac_unicst_update(&grp->lg_mac, grp->lg_addr); 339 break; 340 case MAC_NOTE_PROMISC: 341 port->lp_txinfo = mac_tx_get(port->lp_mh); 342 break; 343 default: 344 break; 345 } 346 347 AGGR_PORT_REFRELE(port); 348 } 349 350 int 351 aggr_port_start(aggr_port_t *port) 352 { 353 int rc; 354 355 ASSERT(RW_WRITE_HELD(&port->lp_lock)); 356 357 if (port->lp_started) 358 return (0); 359 360 if ((rc = mac_start(port->lp_mh)) != 0) 361 return (rc); 362 363 /* update the port state */ 364 port->lp_started = B_TRUE; 365 366 return (rc); 367 } 368 369 void 370 aggr_port_stop(aggr_port_t *port) 371 { 372 ASSERT(RW_WRITE_HELD(&port->lp_lock)); 373 374 if (!port->lp_started) 375 return; 376 377 aggr_grp_multicst_port(port, B_FALSE); 378 379 mac_stop(port->lp_mh); 380 381 /* update the port state */ 382 port->lp_started = B_FALSE; 383 } 384 385 int 386 aggr_port_promisc(aggr_port_t *port, boolean_t on) 387 { 388 int rc; 389 390 ASSERT(RW_WRITE_HELD(&port->lp_lock)); 391 392 if (on == port->lp_promisc_on) 393 /* already in desired promiscous mode */ 394 return (0); 395 396 rc = mac_promisc_set(port->lp_mh, on, MAC_DEVPROMISC); 397 398 if (rc == 0) 399 port->lp_promisc_on = on; 400 401 return (rc); 402 } 403 404 /* 405 * Set the MAC address of a port. 406 */ 407 int 408 aggr_port_unicst(aggr_port_t *port, uint8_t *macaddr) 409 { 410 int rc; 411 412 ASSERT(RW_WRITE_HELD(&port->lp_lock)); 413 414 /* 415 * set lp_set_grpmac to indicate it is going to set the MAC 416 * address to the aggregation MAC address. 417 */ 418 port->lp_set_grpmac = B_TRUE; 419 420 rc = mac_unicst_set(port->lp_mh, macaddr); 421 422 port->lp_set_grpmac = B_FALSE; 423 424 return (rc); 425 } 426 427 /* 428 * Add or remove a multicast address to/from a port. 429 */ 430 int 431 aggr_port_multicst(void *arg, boolean_t add, const uint8_t *addrp) 432 { 433 aggr_port_t *port = arg; 434 435 return (add ? mac_multicst_add(port->lp_mh, addrp) : 436 mac_multicst_remove(port->lp_mh, addrp)); 437 } 438 439 uint64_t 440 aggr_port_stat(aggr_port_t *port, enum mac_stat stat) 441 { 442 if (!port->lp_mip->mi_stat[stat]) 443 return (0); 444 445 return (mac_stat_get(port->lp_mh, stat)); 446 } 447