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 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * Copyright 2018 Joyent, Inc. 25 * Copyright 2026 Oxide Computer Company 26 */ 27 28 #include <sys/types.h> 29 #include <sys/sysmacros.h> 30 #include <sys/conf.h> 31 #include <sys/cmn_err.h> 32 #include <sys/list.h> 33 #include <sys/kmem.h> 34 #include <sys/stream.h> 35 #include <sys/modctl.h> 36 #include <sys/ddi.h> 37 #include <sys/sunddi.h> 38 #include <sys/atomic.h> 39 #include <sys/stat.h> 40 #include <sys/modhash.h> 41 #include <sys/strsubr.h> 42 #include <sys/strsun.h> 43 #include <sys/sdt.h> 44 #include <sys/mac.h> 45 #include <sys/mac_impl.h> 46 #include <sys/mac_client_impl.h> 47 #include <sys/mac_client_priv.h> 48 #include <sys/mac_flow_impl.h> 49 50 /* 51 * Broadcast and multicast traffic must be distributed to the MAC clients 52 * that are defined on top of the same MAC. The set of 53 * destinations to which a multicast packet must be sent is a subset 54 * of all MAC clients defined on top of the MAC. A MAC client can be member 55 * of more than one such subset. 56 * 57 * To accomodate these requirements, we introduce broadcast groups. 58 * A broadcast group is associated with a broadcast or multicast 59 * address. The members of a broadcast group consist of the MAC clients 60 * that should received copies of packets sent to the address 61 * associated with the group, and are defined on top of the 62 * same MAC. 63 * 64 * The broadcast groups defined on top of a MAC are chained, 65 * hanging off the mac_impl_t. The broadcast group id's are 66 * unique globally (tracked by mac_bcast_id). 67 */ 68 69 static kmem_cache_t *mac_bcast_grp_cache; 70 static uint32_t mac_bcast_id = 0; 71 72 void 73 mac_bcast_init(void) 74 { 75 mac_bcast_grp_cache = kmem_cache_create("mac_bcast_grp_cache", 76 sizeof (mac_bcast_grp_t), 0, NULL, NULL, NULL, NULL, NULL, 0); 77 } 78 79 void 80 mac_bcast_fini(void) 81 { 82 kmem_cache_destroy(mac_bcast_grp_cache); 83 } 84 85 mac_impl_t * 86 mac_bcast_grp_mip(void *grp) 87 { 88 mac_bcast_grp_t *bcast_grp = grp; 89 90 return (bcast_grp->mbg_mac_impl); 91 } 92 93 /* 94 * Free the specific broadcast group. Invoked when the last reference 95 * to the group is released. 96 */ 97 void 98 mac_bcast_grp_free(void *bcast_grp) 99 { 100 mac_bcast_grp_t *grp = bcast_grp; 101 mac_impl_t *mip = grp->mbg_mac_impl; 102 103 ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); 104 105 ASSERT(grp->mbg_addr != NULL); 106 kmem_free(grp->mbg_addr, mip->mi_type->mt_addr_length); 107 kmem_free(grp->mbg_clients, 108 grp->mbg_nclients_alloc * sizeof (mac_bcast_grp_mcip_t)); 109 mip->mi_bcast_ngrps--; 110 kmem_cache_free(mac_bcast_grp_cache, grp); 111 } 112 113 /* 114 * arg1: broadcast group 115 * arg2: sender MAC client if it is being sent by a MAC client, 116 * NULL if it was received from the wire. 117 */ 118 void 119 mac_bcast_send(void *arg1, void *arg2, mblk_t *mp_chain, boolean_t is_loopback) 120 { 121 mac_bcast_grp_t *grp = arg1; 122 mac_client_impl_t *src_mcip = arg2, *dst_mcip; 123 mac_impl_t *mip = grp->mbg_mac_impl; 124 uint64_t gen; 125 uint_t i; 126 mblk_t *mp_chain1; 127 flow_entry_t *flent; 128 int err; 129 130 rw_enter(&mip->mi_rw_lock, RW_READER); 131 132 /* 133 * Pass a copy of the mp chain to every MAC client except the sender 134 * MAC client, if the packet was not received from the underlying NIC. 135 * 136 * The broadcast group lock should not be held across calls to 137 * the flow's callback function, since the same group could 138 * potentially be accessed from the same context. When the lock 139 * is reacquired, changes to the broadcast group while the lock 140 * was released are caught using a generation counter incremented 141 * each time the list of MAC clients associated with the broadcast 142 * group is changed. 143 */ 144 for (i = 0; i < grp->mbg_nclients_alloc; i++) { 145 dst_mcip = grp->mbg_clients[i].mgb_client; 146 if (dst_mcip == NULL) 147 continue; 148 flent = dst_mcip->mci_flent; 149 if (flent == NULL || dst_mcip == src_mcip) { 150 /* 151 * Don't send a copy of the packet back to 152 * its sender. 153 */ 154 continue; 155 } 156 157 /* 158 * It is important to hold a reference on the 159 * flow_ent here. 160 */ 161 if ((mp_chain1 = copymsgchain(mp_chain)) == NULL) 162 break; 163 164 FLOW_TRY_REFHOLD(flent, err); 165 if (err != 0) { 166 freemsgchain(mp_chain1); 167 continue; 168 } 169 170 gen = grp->mbg_clients_gen; 171 172 rw_exit(&mip->mi_rw_lock); 173 174 DTRACE_PROBE4(mac__bcast__send__to, mac_client_impl_t *, 175 src_mcip, flow_fn_t, dst_mcip->mci_flent->fe_cb_fn, 176 void *, dst_mcip->mci_flent->fe_cb_arg1, 177 void *, dst_mcip->mci_flent->fe_cb_arg2); 178 179 (dst_mcip->mci_flent->fe_cb_fn)(dst_mcip->mci_flent->fe_cb_arg1, 180 dst_mcip->mci_flent->fe_cb_arg2, mp_chain1, is_loopback); 181 FLOW_REFRELE(flent); 182 183 rw_enter(&mip->mi_rw_lock, RW_READER); 184 185 /* update stats */ 186 if (grp->mbg_addrtype == MAC_ADDRTYPE_MULTICAST) { 187 MCIP_STAT_UPDATE(dst_mcip, multircv, 1); 188 MCIP_STAT_UPDATE(dst_mcip, multircvbytes, 189 msgdsize(mp_chain)); 190 } else { 191 MCIP_STAT_UPDATE(dst_mcip, brdcstrcv, 1); 192 MCIP_STAT_UPDATE(dst_mcip, brdcstrcvbytes, 193 msgdsize(mp_chain)); 194 } 195 196 if (grp->mbg_clients_gen != gen) { 197 /* 198 * The list of MAC clients associated with the group 199 * was changed while the lock was released. 200 * Give up on the current packet. 201 */ 202 rw_exit(&mip->mi_rw_lock); 203 freemsgchain(mp_chain); 204 return; 205 } 206 } 207 rw_exit(&mip->mi_rw_lock); 208 209 if (src_mcip != NULL) { 210 /* 211 * The packet was sent from one of the MAC clients, 212 * so we need to send a copy of the packet to the 213 * underlying NIC so that it can be sent on the wire. 214 */ 215 MCIP_STAT_UPDATE(src_mcip, multixmt, 1); 216 MCIP_STAT_UPDATE(src_mcip, multixmtbytes, msgdsize(mp_chain)); 217 MCIP_STAT_UPDATE(src_mcip, brdcstxmt, 1); 218 MCIP_STAT_UPDATE(src_mcip, brdcstxmtbytes, msgdsize(mp_chain)); 219 220 mp_chain = mac_provider_tx(mip, mip->mi_default_tx_ring, 221 mp_chain, src_mcip); 222 if (mp_chain != NULL) 223 freemsgchain(mp_chain); 224 } else { 225 freemsgchain(mp_chain); 226 } 227 } 228 229 /* 230 * Add the specified MAC client to the group corresponding to the specified 231 * broadcast or multicast address. 232 * Return 0 on success, or an errno value on failure. 233 */ 234 int 235 mac_bcast_add(mac_client_impl_t *mcip, const uint8_t *addr, uint16_t vid, 236 mac_addrtype_t addrtype) 237 { 238 mac_impl_t *mip = mcip->mci_mip; 239 mac_bcast_grp_t *grp = NULL, **last_grp; 240 size_t addr_len = mip->mi_type->mt_addr_length; 241 int rc = 0; 242 int i, index = -1; 243 mac_mcast_addrs_t **prev_mi_addr = NULL; 244 mac_mcast_addrs_t **prev_mci_addr = NULL; 245 246 ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); 247 248 ASSERT(addrtype == MAC_ADDRTYPE_MULTICAST || 249 addrtype == MAC_ADDRTYPE_BROADCAST); 250 251 /* 252 * Add the MAC client to the list of MAC clients associated 253 * with the group. 254 */ 255 if (addrtype == MAC_ADDRTYPE_MULTICAST) { 256 mac_mcast_addrs_t *maddr; 257 258 /* 259 * In case of a driver (say aggr), we need this information 260 * on a per MAC instance basis. 261 */ 262 prev_mi_addr = &mip->mi_mcast_addrs; 263 for (maddr = *prev_mi_addr; maddr != NULL; 264 prev_mi_addr = &maddr->mma_next, maddr = maddr->mma_next) { 265 if (bcmp(maddr->mma_addr, addr, addr_len) == 0) 266 break; 267 } 268 if (maddr == NULL) { 269 /* 270 * For multicast addresses, have the underlying MAC 271 * join the corresponding multicast group. 272 */ 273 rc = mip->mi_multicst(mip->mi_driver, B_TRUE, addr); 274 if (rc != 0) 275 return (rc); 276 maddr = kmem_zalloc(sizeof (mac_mcast_addrs_t), 277 KM_SLEEP); 278 bcopy(addr, maddr->mma_addr, addr_len); 279 *prev_mi_addr = maddr; 280 } else { 281 prev_mi_addr = NULL; 282 } 283 maddr->mma_ref++; 284 285 /* 286 * We maintain a separate list for each MAC client. Get 287 * the entry or add, if it is not present. 288 */ 289 prev_mci_addr = &mcip->mci_mcast_addrs; 290 for (maddr = *prev_mci_addr; maddr != NULL; 291 prev_mci_addr = &maddr->mma_next, maddr = maddr->mma_next) { 292 if (bcmp(maddr->mma_addr, addr, addr_len) == 0) 293 break; 294 } 295 if (maddr == NULL) { 296 maddr = kmem_zalloc(sizeof (mac_mcast_addrs_t), 297 KM_SLEEP); 298 bcopy(addr, maddr->mma_addr, addr_len); 299 *prev_mci_addr = maddr; 300 } else { 301 prev_mci_addr = NULL; 302 } 303 maddr->mma_ref++; 304 } 305 306 /* The list is protected by the perimeter */ 307 last_grp = &mip->mi_bcast_grp; 308 for (grp = *last_grp; grp != NULL; 309 last_grp = &grp->mbg_next, grp = grp->mbg_next) { 310 if (bcmp(grp->mbg_addr, addr, addr_len) == 0 && 311 grp->mbg_vid == vid) 312 break; 313 } 314 315 if (grp == NULL) { 316 /* 317 * The group does not yet exist, create it. 318 */ 319 flow_desc_t flow_desc; 320 char flow_name[MAXFLOWNAMELEN]; 321 322 grp = kmem_cache_alloc(mac_bcast_grp_cache, KM_SLEEP); 323 bzero(grp, sizeof (mac_bcast_grp_t)); 324 grp->mbg_next = NULL; 325 grp->mbg_mac_impl = mip; 326 327 DTRACE_PROBE1(mac__bcast__add__new__group, mac_bcast_grp_t *, 328 grp); 329 330 grp->mbg_addr = kmem_zalloc(addr_len, KM_SLEEP); 331 bcopy(addr, grp->mbg_addr, addr_len); 332 grp->mbg_addrtype = addrtype; 333 grp->mbg_vid = vid; 334 335 /* 336 * Add a new flow to the underlying MAC. 337 */ 338 bzero(&flow_desc, sizeof (flow_desc)); 339 bcopy(addr, &flow_desc.fd_dst_mac, addr_len); 340 flow_desc.fd_mac_len = (uint32_t)addr_len; 341 342 flow_desc.fd_mask = FLOW_LINK_DST; 343 if (vid != 0) { 344 flow_desc.fd_vid = vid; 345 flow_desc.fd_mask |= FLOW_LINK_VID; 346 } 347 348 grp->mbg_id = atomic_inc_32_nv(&mac_bcast_id); 349 (void) sprintf(flow_name, 350 "mac/%s/mcast%d", mip->mi_name, grp->mbg_id); 351 352 rc = mac_flow_create(&flow_desc, NULL, flow_name, 353 grp, FLOW_MCAST, &grp->mbg_flow_ent); 354 if (rc != 0) { 355 kmem_free(grp->mbg_addr, addr_len); 356 kmem_cache_free(mac_bcast_grp_cache, grp); 357 goto fail; 358 } 359 grp->mbg_flow_ent->fe_mbg = grp; 360 mip->mi_bcast_ngrps++; 361 362 /* 363 * Initial creation reference on the flow. This is released 364 * in the corresponding delete action i_mac_bcast_delete() 365 */ 366 FLOW_REFHOLD(grp->mbg_flow_ent); 367 368 /* 369 * When the multicast and broadcast packet is received 370 * by the underlying NIC, mac_rx_classify() will invoke 371 * mac_bcast_send() with arg2=NULL, which will cause 372 * mac_bcast_send() to send a copy of the packet(s) 373 * to every MAC client opened on top of the underlying MAC. 374 * 375 * When the mac_bcast_send() function is invoked from 376 * the transmit path of a MAC client, it will specify the 377 * transmitting MAC client as the arg2 value, which will 378 * allow mac_bcast_send() to skip that MAC client and not 379 * send it a copy of the packet. 380 * 381 * We program the classifier to dispatch matching broadcast 382 * packets to mac_bcast_send(). 383 */ 384 385 grp->mbg_flow_ent->fe_cb_fn = mac_bcast_send; 386 grp->mbg_flow_ent->fe_cb_arg1 = grp; 387 grp->mbg_flow_ent->fe_cb_arg2 = NULL; 388 389 rc = mac_flow_add(mip->mi_flow_tab, grp->mbg_flow_ent); 390 if (rc != 0) { 391 FLOW_FINAL_REFRELE(grp->mbg_flow_ent); 392 goto fail; 393 } 394 395 *last_grp = grp; 396 } 397 398 ASSERT(grp->mbg_addrtype == addrtype); 399 400 /* 401 * Add the MAC client to the list of MAC clients associated 402 * with the group. 403 */ 404 rw_enter(&mip->mi_rw_lock, RW_WRITER); 405 for (i = 0; i < grp->mbg_nclients_alloc; i++) { 406 /* 407 * The MAC client was already added, say when we have 408 * different unicast addresses with the same vid. 409 * Just increment the ref and we are done. 410 */ 411 if (grp->mbg_clients[i].mgb_client == mcip) { 412 grp->mbg_clients[i].mgb_client_ref++; 413 rw_exit(&mip->mi_rw_lock); 414 return (0); 415 } else if (grp->mbg_clients[i].mgb_client == NULL && 416 index == -1) { 417 index = i; 418 } 419 } 420 if (grp->mbg_nclients_alloc == grp->mbg_nclients) { 421 mac_bcast_grp_mcip_t *new_clients; 422 uint_t new_size = grp->mbg_nclients+1; 423 424 new_clients = kmem_zalloc(new_size * 425 sizeof (mac_bcast_grp_mcip_t), KM_SLEEP); 426 427 if (grp->mbg_nclients > 0) { 428 ASSERT(grp->mbg_clients != NULL); 429 bcopy(grp->mbg_clients, new_clients, grp->mbg_nclients * 430 sizeof (mac_bcast_grp_mcip_t)); 431 kmem_free(grp->mbg_clients, grp->mbg_nclients * 432 sizeof (mac_bcast_grp_mcip_t)); 433 } 434 435 grp->mbg_clients = new_clients; 436 grp->mbg_nclients_alloc = new_size; 437 index = new_size - 1; 438 } 439 440 ASSERT(index != -1); 441 grp->mbg_clients[index].mgb_client = mcip; 442 grp->mbg_clients[index].mgb_client_ref = 1; 443 grp->mbg_nclients++; 444 /* 445 * Since we're adding to the list of MAC clients using that group, 446 * kick the generation count, which will allow mac_bcast_send() 447 * to detect that condition after re-acquiring the lock. 448 */ 449 grp->mbg_clients_gen++; 450 rw_exit(&mip->mi_rw_lock); 451 return (0); 452 453 fail: 454 if (prev_mi_addr != NULL) { 455 kmem_free(*prev_mi_addr, sizeof (mac_mcast_addrs_t)); 456 *prev_mi_addr = NULL; 457 (void) mip->mi_multicst(mip->mi_driver, B_FALSE, addr); 458 } 459 if (prev_mci_addr != NULL) { 460 kmem_free(*prev_mci_addr, sizeof (mac_mcast_addrs_t)); 461 *prev_mci_addr = NULL; 462 } 463 return (rc); 464 } 465 466 /* 467 * Remove the specified MAC client from the group corresponding to 468 * the specific broadcast or multicast address. 469 * 470 * Note: mac_bcast_delete() calls mac_remove_flow() which 471 * will call cv_wait for fe_refcnt to drop to 0. So this function 472 * should not be called from interrupt or STREAMS context. 473 */ 474 void 475 mac_bcast_delete(mac_client_impl_t *mcip, const uint8_t *addr, uint16_t vid) 476 { 477 mac_impl_t *mip = mcip->mci_mip; 478 mac_bcast_grp_t *grp = NULL, **prev; 479 size_t addr_len = mip->mi_type->mt_addr_length; 480 flow_entry_t *flent; 481 uint_t i; 482 mac_mcast_addrs_t *maddr = NULL; 483 mac_mcast_addrs_t **mprev; 484 485 ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); 486 487 /* find the broadcast group. The list is protected by the perimeter */ 488 prev = &mip->mi_bcast_grp; 489 for (grp = mip->mi_bcast_grp; grp != NULL; prev = &grp->mbg_next, 490 grp = grp->mbg_next) { 491 if (bcmp(grp->mbg_addr, addr, addr_len) == 0 && 492 grp->mbg_vid == vid) 493 break; 494 } 495 ASSERT(grp != NULL); 496 497 /* 498 * Remove the MAC client from the list of MAC clients associated 499 * with that broadcast group. 500 * 501 * We mark the mbg_clients[] location corresponding to the removed MAC 502 * client NULL and reuse that location when we add a new MAC client. 503 */ 504 505 rw_enter(&mip->mi_rw_lock, RW_WRITER); 506 507 for (i = 0; i < grp->mbg_nclients_alloc; i++) { 508 if (grp->mbg_clients[i].mgb_client == mcip) 509 break; 510 } 511 512 ASSERT(i < grp->mbg_nclients_alloc); 513 /* 514 * If there are more references to this MAC client, then we let 515 * it remain till it goes to 0. 516 */ 517 if (--grp->mbg_clients[i].mgb_client_ref > 0) 518 goto update_maddr; 519 520 grp->mbg_clients[i].mgb_client = NULL; 521 grp->mbg_clients[i].mgb_client_ref = 0; 522 523 /* 524 * Since we're removing from the list of MAC clients using that group, 525 * kick the generation count, which will allow mac_bcast_send() 526 * to detect that condition. 527 */ 528 grp->mbg_clients_gen++; 529 530 if (--grp->mbg_nclients == 0) { 531 /* 532 * The last MAC client of the group was just removed. 533 * Unlink the current group from the list of groups 534 * defined on top of the underlying NIC. The group 535 * structure will stay around until the last reference 536 * is dropped. 537 */ 538 *prev = grp->mbg_next; 539 } 540 update_maddr: 541 rw_exit(&mip->mi_rw_lock); 542 543 if (grp->mbg_addrtype == MAC_ADDRTYPE_MULTICAST) { 544 mprev = &mcip->mci_mcast_addrs; 545 for (maddr = mcip->mci_mcast_addrs; maddr != NULL; 546 mprev = &maddr->mma_next, maddr = maddr->mma_next) { 547 if (bcmp(grp->mbg_addr, maddr->mma_addr, 548 mip->mi_type->mt_addr_length) == 0) 549 break; 550 } 551 ASSERT(maddr != NULL); 552 if (--maddr->mma_ref == 0) { 553 *mprev = maddr->mma_next; 554 maddr->mma_next = NULL; 555 kmem_free(maddr, sizeof (mac_mcast_addrs_t)); 556 } 557 558 mprev = &mip->mi_mcast_addrs; 559 for (maddr = mip->mi_mcast_addrs; maddr != NULL; 560 mprev = &maddr->mma_next, maddr = maddr->mma_next) { 561 if (bcmp(grp->mbg_addr, maddr->mma_addr, 562 mip->mi_type->mt_addr_length) == 0) 563 break; 564 } 565 ASSERT(maddr != NULL); 566 if (--maddr->mma_ref == 0) { 567 (void) mip->mi_multicst(mip->mi_driver, B_FALSE, addr); 568 *mprev = maddr->mma_next; 569 maddr->mma_next = NULL; 570 kmem_free(maddr, sizeof (mac_mcast_addrs_t)); 571 } 572 } 573 574 /* 575 * If the group itself is being removed, remove the 576 * corresponding flow from the underlying NIC. 577 */ 578 flent = grp->mbg_flow_ent; 579 if (grp->mbg_nclients == 0) { 580 mac_flow_remove(mip->mi_flow_tab, flent, B_FALSE); 581 mac_flow_wait(flent, FLOW_DRIVER_UPCALL); 582 FLOW_FINAL_REFRELE(flent); 583 } 584 } 585 586 /* 587 * This will be called by a driver, such as aggr, when a port is added/removed 588 * to add/remove the port to/from all the multcast addresses for that aggr. 589 */ 590 void 591 mac_bcast_refresh(mac_impl_t *mip, mac_multicst_t refresh_fn, void *arg, 592 boolean_t add) 593 { 594 mac_mcast_addrs_t *grp, *next; 595 596 ASSERT(refresh_fn != NULL); 597 598 ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); 599 600 /* 601 * Walk the multicast address list and call the refresh function for 602 * each address. 603 */ 604 605 for (grp = mip->mi_mcast_addrs; grp != NULL; grp = next) { 606 /* 607 * Save the next pointer just in case the refresh 608 * function's action causes the group entry to be 609 * freed. 610 * We won't be adding to this list as part of the 611 * refresh. 612 */ 613 next = grp->mma_next; 614 refresh_fn(arg, add, grp->mma_addr); 615 } 616 } 617 618 /* 619 * Walk the MAC client's multicast address list and add/remove the addr/vid 620 * ('arg' is 'flent') to all the addresses. 621 */ 622 void 623 mac_client_bcast_refresh(mac_client_impl_t *mcip, mac_multicst_t refresh_fn, 624 void *arg, boolean_t add) 625 { 626 mac_mcast_addrs_t *grp, *next; 627 mac_impl_t *mip = mcip->mci_mip; 628 629 ASSERT(refresh_fn != NULL); 630 631 ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); 632 /* 633 * Walk the multicast address list and call the refresh function for 634 * each address. 635 * Broadcast addresses are not added or removed through the multicast 636 * entry points, so don't include them as part of the refresh. 637 */ 638 for (grp = mcip->mci_mcast_addrs; grp != NULL; grp = next) { 639 /* 640 * Save the next pointer just in case the refresh 641 * function's action causes the group entry to be 642 * freed. 643 * We won't be adding to this list as part of the 644 * refresh. 645 */ 646 next = grp->mma_next; 647 refresh_fn(arg, add, grp->mma_addr); 648 } 649 } 650