tx.c (20639B)
1 /* 2 * 3 * This license is set out in https://raw.githubusercontent.com/Broadcom-Network-Switching-Software/OpenBCM/master/Legal/LICENSE file. 4 * 5 * Copyright 2007-2019 Broadcom Inc. All rights reserved. 6 * 7 * File: tx.c 8 * Purpose: 9 * Requires: 10 * 11 * Notes: Does not yet support packet gather at all. 12 * Still issues with data buffer transfer 13 */ 14 15 #include <shared/bsl.h> 16 17 #include <sal/core/libc.h> 18 19 #include <bcm/types.h> 20 #include <bcm/tx.h> 21 #include <bcm/pkt.h> 22 #include <bcm/error.h> 23 #include <bcm/topo.h> 24 #include <bcm_int/control.h> 25 26 #include <bcmx/tx.h> 27 #include <bcmx/l2.h> 28 #include <bcmx/vlan.h> 29 #include <bcmx/bcmx.h> 30 #include <bcmx/trunk.h> 31 32 #include "bcmx_int.h" 33 34 #if defined(BROADCOM_DEBUG) 35 #endif /* BROADCOM_DEBUG */ 36 37 #define BCMX_TX_INIT_CHECK BCMX_READY_CHECK 38 39 /* 40 * For now, flood on unit 0. This will support systems 41 * where unit 0 is a 567x such as white knight and lancelot. 42 */ 43 44 int _bcmx_tx_unit = 0; 45 int _bcmx_tx_flood_unit = 0; 46 47 /* Actions based on L2 map, etc. */ 48 49 #define _BCMX_TX_UC 1 /* Unicast based on given LPort */ 50 #define _BCMX_TX_FLOOD 2 /* Flood the packet on its vlan */ 51 #define _BCMX_TX_DISCARD 3 /* Discard the packet */ 52 #define _BCMX_TX_BCM 4 /* Send using bcm_tx */ 53 #define _BCMX_TX_MCAST 5 /* L2 multicast; not implemented */ 54 55 #define _BCMX_UNPACK_U16(_buf, _var) \ 56 _var = *_buf++ << 8; \ 57 _var |= *_buf++; 58 59 /**************************************************************** 60 * 61 * Internal static functions 62 */ 63 64 /* Get the VID from a bcm_pkt structure */ 65 STATIC INLINE bcm_vlan_t 66 pkt_vid_get(bcm_pkt_t *pkt) 67 { 68 int vblk = 0; 69 int voffset = 12; 70 uint16 vtag; 71 uint8 *ptr; 72 73 /* First, find offset of vlan tag */ 74 if (pkt->pkt_data[0].len < 16) { 75 if (pkt->pkt_data[1].len < 10) { 76 vblk = 2; 77 voffset = 0; 78 } else { 79 vblk = 1; 80 voffset = 6; 81 } 82 } 83 84 ptr = &pkt->pkt_data[vblk].data[voffset + 2]; 85 _BCMX_UNPACK_U16(ptr, vtag); 86 87 return BCM_VLAN_CTRL_ID(vtag); 88 } 89 90 /* Get protocol tag from the packet */ 91 STATIC INLINE uint16 92 pkt_tag_proto_get(bcm_pkt_t *pkt) 93 { 94 int vblk = 0; 95 int voffset = 12; 96 uint16 vtag; 97 uint8 *ptr; 98 99 /* First, find offset of vlan tag */ 100 if (pkt->pkt_data[0].len < 16) { 101 if (pkt->pkt_data[1].len < 10) { 102 vblk = 2; 103 voffset = 0; 104 } else { 105 vblk = 1; 106 voffset = 6; 107 } 108 } 109 110 ptr = &pkt->pkt_data[vblk].data[voffset]; 111 _BCMX_UNPACK_U16(ptr, vtag); 112 113 return vtag; 114 } 115 116 /* Get the VID of the packet if tagged, or default VID */ 117 STATIC INLINE bcm_vlan_t 118 pkt_vid_resolve(bcm_pkt_t *pkt) 119 { 120 bcm_vlan_t vid = 1; 121 122 if (pkt_tag_proto_get(pkt) == 0x8100) { /* pkt is tagged */ 123 vid = pkt_vid_get(pkt); 124 } else { 125 bcmx_vlan_default_get(&vid); 126 } 127 128 return vid; 129 } 130 131 132 /* 133 * Check if packet needs L2 lookup and carry out if it does. 134 * Returns BCMX_TX_ return values OK, FLOODED, DISCARDED. 135 * 136 * TO DO: Implement multicast support. 137 */ 138 139 STATIC INLINE int 140 pkt_addr_resolve(bcm_pkt_t *pkt, uint32 flags, bcmx_lport_t *d_port) 141 { 142 bcm_vlan_t vid; 143 bcmx_l2_addr_t l2addr; 144 int rv = BCM_E_NONE; 145 146 if (flags & BCMX_TX_F_FLOOD_PKT) { 147 return _BCMX_TX_FLOOD; 148 } 149 150 /* Check if L2 lookup required */ 151 if (flags & BCMX_TX_F_L2_LOOKUP) { 152 *d_port = BCMX_LPORT_INVALID; 153 154 if ((pkt->pkt_data[0].data[0] & 0x1) == 0) { 155 /* Only look up unicast packets */ 156 vid = pkt_vid_resolve(pkt); 157 158 sal_memset(&l2addr, 0, sizeof(l2addr)); 159 if (bcmx_l2_addr_get((void *)pkt->pkt_data[0].data, 160 vid, &l2addr, NULL) == 0) { 161 if (l2addr.flags & BCM_L2_TRUNK_MEMBER) { 162 bcmx_trunk_add_info_t t_data; 163 164 if (bcmx_trunk_get(l2addr.tgid, &t_data) >= 0) { 165 bcmx_lport_t lport; 166 int count, local_tx = 0, unit = 0; 167 168 if (BCMX_LPLIST_IS_EMPTY(&t_data.ports)) { 169 /* 170 * L2 table is programmed with tgid, but 171 * no trunk members found 172 */ 173 rv = BCM_E_INTERNAL; 174 } 175 176 if(BCM_SUCCESS(rv)) { 177 BCMX_LPLIST_IDX_ITER(&t_data.ports, lport, count) { 178 unit = bcmx_lport_bcm_unit(lport); 179 180 if (unit < 0) { 181 rv = BCM_E_PORT; 182 break; 183 } 184 185 if ((BCM_SUCCESS(rv)) && (BCM_IS_LOCAL(unit))) { 186 local_tx = 1; 187 *d_port = lport; 188 break; 189 } 190 } 191 } 192 193 if (BCM_SUCCESS(rv) && (!local_tx)) { 194 *d_port = bcmx_lplist_first(&t_data.ports); 195 } 196 } else { 197 /* 198 * bcmx_trunk_get failed; Non-existent tid probably 199 */ 200 rv = BCM_E_INTERNAL; 201 } 202 bcmx_trunk_add_info_t_free(&t_data); 203 if(BCM_FAILURE(rv)) { 204 return rv; 205 } 206 } else { 207 /* l2addr.port is overloaded with logical port */ 208 *d_port = l2addr.lport; 209 } 210 } 211 } else { 212 return BCM_E_UNAVAIL; 213 } 214 215 if (*d_port == BCMX_LPORT_INVALID) { 216 if (pkt->flags & BCMX_TX_F_DLF_DISCARD) { 217 return _BCMX_TX_DISCARD; 218 } else { 219 return _BCMX_TX_FLOOD; 220 } 221 } 222 223 return _BCMX_TX_UC; 224 } 225 226 return _BCMX_TX_BCM; 227 } 228 229 /* 230 * Function: 231 * bcmx_tx 232 * Purpose: 233 * Transmit a packet according to flags 234 * Parameters: 235 * pkt - The packet to transmit 236 * flags - Flags as defined in include/bcmx/tx.h 237 * Returns: 238 * BCMX_E_XXX < 0 on error. 239 * Notes: 240 * If the L2 map flag is set, an L2 LOOKUP is performed on 241 * the packet. If this fails and the DLF_DISCARD flag is set 242 * then the packet is not transmitted. Otherwise it is flooded 243 * to the packet's VLAN. This is indicated by the return value 244 * (see include/bcmx/tx.h). 245 * 246 * If the L2 map flag is not set, the packet is sent on the 247 * unit given by pkt->unit with the port bitmaps given by 248 * pkt->tx_pbmp, etc. 249 * 250 * The semantics match those of bcm_tx. In particular, 251 * the packet callback being non-null indicates a user callback 252 * should be made and the transmit will be done asynchronously. 253 * 254 * The packet's L3 and untagged bitmaps are AND-ed with the 255 * tx bitmap. The caller can use bcmx_tx_pkt_l3_set or 256 * bcmx_tx_pkt_untagged_set to set all ports in the bitmap 257 */ 258 259 int 260 bcmx_tx(bcm_pkt_t *pkt, uint32 flags) 261 { 262 bcmx_lport_t d_port; 263 int rv; 264 265 BCMX_TX_INIT_CHECK; 266 267 if (pkt == NULL || pkt->pkt_data == NULL || pkt->blk_count <= 0) { 268 return BCM_E_PARAM; 269 } 270 271 /* Try to L2 map if needed; if pkt discarded, return */ 272 d_port = BCMX_LPORT_INVALID; 273 switch ((rv = pkt_addr_resolve(pkt, flags, &d_port))) { 274 case _BCMX_TX_UC: /* Okay */ 275 return bcmx_tx_uc(pkt, d_port, flags); 276 277 case _BCMX_TX_FLOOD: /* Packet to be flooded */ 278 return bcmx_tx_flood(pkt, flags); 279 280 case _BCMX_TX_DISCARD: /* Discarded */ 281 return BCMX_TX_DISCARDED; 282 283 /* TO DO: Implement multicast support here */ 284 case _BCMX_TX_BCM: 285 return bcm_tx(pkt->unit, pkt, NULL); 286 287 case _BCMX_TX_MCAST: 288 return BCM_E_UNAVAIL; /* Not yet implemented */ 289 290 default: 291 /* Error */ 292 LOG_WARN(BSL_LS_BCMX_COMMON, 293 (BSL_META("bcmx_tx: packet resolve error (%d): %s\n"), 294 rv, bcm_errmsg(rv))); 295 break; 296 } 297 298 /* Error if we get here */ 299 return rv; 300 } 301 302 303 /* 304 * Function: 305 * bcmx_tx_uc 306 * Purpose: 307 * Transmit a packet out a specific front panel port 308 * Parameters: 309 * pkt - The packet to transmit 310 * d_port - The front panel destination port 311 * Returns: 312 * BCM_E_XXX 313 * Notes: 314 * The semantics match those of bcm_tx. In particular, 315 * the packet callback being non-null indicates a user callback 316 * should be made and the transmit will be done asynchronously. 317 * 318 * The packet's L3 and untagged bitmaps are AND-ed with the 319 * tx bitmap. The caller can use bcmx_tx_pkt_l3_set or 320 * bcmx_tx_pkt_untagged_set to set all ports in the bitmap 321 */ 322 323 int 324 bcmx_tx_uc(bcm_pkt_t *pkt, bcmx_lport_t d_port, uint32 flags) 325 { 326 int rv = BCM_E_NONE; 327 bcm_port_t port, modport; 328 int unit; 329 bcm_module_t modid; 330 331 BCMX_TX_INIT_CHECK; 332 333 if (pkt == NULL || pkt->pkt_data == NULL || pkt->blk_count <= 0) { 334 return BCM_E_PARAM; 335 } 336 337 BCM_IF_ERROR_RETURN 338 (_bcmx_dest_to_unit_port(d_port, &unit, &port, 339 BCMX_DEST_CONVERT_NON_GPORT)); 340 BCM_IF_ERROR_RETURN 341 (_bcmx_dest_to_modid_port(d_port, &modid, &modport, 342 BCMX_DEST_CONVERT_DEFAULT)); 343 344 /* First, try to send the packet on a local unit, directing 345 * the packet using the module ID. 346 */ 347 if (modid < 0) { /* Bad modid */ 348 return BCM_E_BADID; 349 } 350 351 if (!BCM_UNIT_VALID(unit)) { 352 return BCM_E_UNIT; 353 } 354 355 pkt->dest_mod = modid; 356 pkt->dest_port = modport; 357 pkt->opcode = BCM_HG_OPCODE_UC; 358 359 if (!BCM_IS_LOCAL(unit) && BCM_IS_LOCAL(_bcmx_tx_unit)) { 360 /* Try to send directly; if unable to map, tunnel via bcm_tx. */ 361 bcm_port_t local_tx_port; 362 363 rv = bcm_topo_port_get(_bcmx_tx_unit, (int)modid, &local_tx_port); 364 if (!(flags & BCMX_TX_F_CPU_TUNNEL) && (rv == BCM_E_NONE) && 365 (local_tx_port != -1)) { 366 /* Can transmit locally to port; otherwise tunneled */ 367 unit = _bcmx_tx_unit; 368 port = local_tx_port; 369 } 370 } 371 372 BCM_PBMP_PORT_SET(pkt->tx_pbmp, port); 373 pkt->unit = unit; 374 return bcm_tx(unit, pkt, NULL); 375 } 376 377 378 /* 379 * Function: 380 * bcmx_tx_lplist 381 * Purpose: 382 * Transmit a packet to a list of ports (efficiently) 383 * Parameters: 384 * pkt -- Pointer to packet to send 385 * tx_ports -- List of ports to which to send; if NULL, 386 * use untagged port list only 387 * untagged_ports -- Egress ports which should be untagged 388 * flags -- For future enhancements (unused) 389 * Returns: 390 * BCM_E_XXX 391 * Notes: 392 * Packet will be sent tagged unless: 393 * tx_ports is NULL/empty, in which case the packet is sent 394 * out untagged on all ports specified in the untagged_ports list; 395 * OR both tx_ports and untagged_ports params are non-NULL, 396 * in which case the packet is sent out untagged on the ports 397 * specified in untagged_ports. If both are specified, 398 * untagged_ports must be a subset of tx_ports. 399 * 400 * The port list is scanned first and the devices that appear 401 * on the list are extracted. The code then iterates over 402 * those units and creates a bitmap for each unit. 403 * 404 * Async transmit is supported, but it won't really take place 405 * until the last unit transmit is set up. 406 */ 407 408 int 409 bcmx_tx_lplist(bcm_pkt_t *pkt, bcmx_lplist_t *tx_ports, 410 bcmx_lplist_t *untagged_ports, uint32 flags) 411 { 412 bcm_unit_bmp_t tx_units; 413 int unit; 414 bcmx_lport_t lport; 415 bcmx_lport_t cb_lport = BCMX_LPORT_INVALID; 416 int i; 417 bcm_pbmp_t tx_pbmp, ut_pbmp; 418 int rv, final_rv = BCM_E_NONE; 419 int unit_iter=0, unit_count = 0; 420 bcm_pkt_cb_f pkt_callback = NULL; 421 int all_untagged = FALSE; 422 int local_unit_found = FALSE; 423 int bcm_unit; 424 bcm_port_t bcm_port; 425 426 BCMX_TX_INIT_CHECK; 427 428 if (tx_ports == NULL || BCMX_LPLIST_IS_EMPTY(tx_ports)) { 429 if (untagged_ports == NULL || BCMX_LPLIST_IS_EMPTY(untagged_ports)) { 430 LOG_VERBOSE(BSL_LS_BCMX_COMMON, 431 (BSL_META("bcmx_tx_lplist: No TX ports given\n"))); 432 return BCM_E_NONE; 433 } 434 all_untagged = TRUE; 435 tx_ports = untagged_ports; 436 } 437 438 /* Only set callback on transmit of last packet. Note that 439 * we aren't really sending async until transmitting to the 440 * last unit. 441 */ 442 pkt_callback = pkt->call_back; 443 pkt->call_back = NULL; 444 445 /* Find which units being transmitted to, and which remote lport 446 may require a callback. 447 448 Only the last lport on a remote unit is eligible for a 449 callback, and the presence of any local port will cancel such 450 eligibility. */ 451 452 BCM_UNIT_BMP_CLEAR(tx_units); 453 BCMX_LPLIST_IDX_ITER(tx_ports, lport, i) { 454 unit = bcmx_lport_bcm_unit(lport); 455 if (unit < 0) { 456 /* Port got disconnected */ 457 return BCM_E_INTERNAL; 458 } 459 460 BCM_UNIT_BMP_ADD(tx_units, unit); 461 462 /* See if there needs to be a callback for a remote port */ 463 if (!local_unit_found) { 464 if (BCM_IS_LOCAL(unit)) { 465 /* Local port found, so discontinue looking for a 466 callback port */ 467 cb_lport = BCMX_LPORT_INVALID; 468 local_unit_found = TRUE; 469 } else { 470 cb_lport = lport; 471 } 472 } 473 } 474 475 if (!(flags & BCMX_TX_F_CPU_TUNNEL)) { 476 /* 477 * For the logical ports that reside on remote units, let us try and 478 * switch directly if a path is available (using bcmx_tx_uc()) 479 * 480 * Note: the logical ports that are handled here using bcmx_tx_uc() 481 * will be excluded from the unit iterator further below (that takes 482 * care of logical ports residing on local units only) 483 */ 484 BCMX_LPLIST_IDX_ITER(tx_ports, lport, i) { 485 unit = bcmx_lport_bcm_unit(lport); 486 if (unit < 0) { 487 return BCM_E_INTERNAL; 488 } 489 if (BCM_IS_LOCAL(unit)) { 490 continue; 491 } 492 493 if (BCM_UNIT_BMP_TEST(tx_units, unit)) { 494 BCM_UNIT_BMP_REMOVE(tx_units, unit); 495 } 496 497 if (lport == cb_lport) { 498 pkt->call_back = pkt_callback; 499 } 500 501 rv = bcmx_tx_uc(pkt, lport, flags); 502 if (rv < 0) { 503 LOG_ERROR(BSL_LS_BCMX_COMMON, 504 (BSL_META_U(unit, 505 "BCMX tx_lplist: Failed on unit %d port 0x%x: %s\n"), 506 unit, lport, bcm_errmsg(rv))); 507 final_rv = rv; 508 } 509 } 510 511 } 512 513 /* Count the units for setting up callback if present */ 514 for (unit = 0; unit < BCM_UNITS_MAX; unit++) { 515 if (BCM_UNIT_BMP_TEST(tx_units, unit)) { 516 ++unit_count; 517 } 518 } 519 520 /* Send out packet on units */ 521 for (unit = 0; unit < BCM_UNITS_MAX; unit++) { 522 if (BCM_UNIT_BMP_TEST(tx_units, unit)) { 523 if (++unit_iter == unit_count) { 524 /* Restore callback for async operation on last pkt */ 525 pkt->call_back = pkt_callback; 526 } 527 528 /* Generate the transmit bitmaps for this device */ 529 BCM_PBMP_CLEAR(tx_pbmp); 530 BCM_PBMP_CLEAR(ut_pbmp); 531 BCMX_LPLIST_IDX_ITER(tx_ports, lport, i) { 532 if (BCM_SUCCESS 533 (_bcmx_dest_to_unit_port(lport, &bcm_unit, &bcm_port, 534 BCMX_DEST_CONVERT_NON_GPORT))) { 535 if (bcm_unit == unit) { 536 BCM_PBMP_PORT_ADD(tx_pbmp, bcm_port); 537 } 538 } 539 } 540 if ((!all_untagged) && 541 (untagged_ports != NULL) && 542 (!BCMX_LPLIST_IS_EMPTY(untagged_ports))) { 543 BCMX_LPLIST_IDX_ITER(untagged_ports, lport, i) { 544 if (BCM_SUCCESS 545 (_bcmx_dest_to_unit_port(lport, 546 &bcm_unit, &bcm_port, 547 BCMX_DEST_CONVERT_NON_GPORT))) { 548 if (bcm_unit == unit) { 549 BCM_PBMP_PORT_ADD(ut_pbmp, bcm_port); 550 } 551 } 552 } 553 } 554 555 BCM_PBMP_ASSIGN(pkt->tx_pbmp, tx_pbmp); 556 if (all_untagged) { 557 BCM_PBMP_ASSIGN(pkt->tx_upbmp, tx_pbmp); 558 } else { 559 BCM_PBMP_ASSIGN(pkt->tx_upbmp, ut_pbmp); 560 } 561 pkt->unit = unit; 562 rv = bcm_tx(unit, pkt, NULL); 563 564 if (rv < 0) { 565 LOG_ERROR(BSL_LS_BCMX_COMMON, 566 (BSL_META_U(unit, 567 "BCMX tx_lplist: Failed on unit %d:%s\n"), 568 unit, bcm_errmsg(rv))); 569 final_rv = rv; 570 } 571 } 572 } 573 574 return final_rv; 575 576 } 577 578 579 /* 580 * Function: 581 * bcmx_tx_flood 582 * Purpose: 583 * Flood a packet on its VLAN 584 * Parameters: 585 * pkt - The packet to transmit 586 * Returns: 587 * BCM_E_XXX 588 * Notes: 589 * The CRC field in pkt->flags must be set before calling. 590 */ 591 592 int 593 bcmx_tx_flood(bcm_pkt_t *pkt, uint32 flags) 594 { 595 int rv; 596 bcm_port_config_t config; 597 598 COMPILER_REFERENCE(flags); 599 600 BCMX_TX_INIT_CHECK; 601 602 /* To do: detect tunnel and forward to each CPU in system */ 603 604 if (pkt == NULL || pkt->pkt_data == NULL || pkt->blk_count <= 0) { 605 return BCM_E_PARAM; 606 } 607 608 rv = bcm_port_config_get(_bcmx_tx_flood_unit, &config); 609 if (rv < 0) { 610 return rv; 611 } 612 613 BCM_PBMP_ASSIGN(pkt->tx_pbmp, config.port); 614 pkt->unit = _bcmx_tx_flood_unit; 615 pkt->opcode = BCM_HG_OPCODE_BC; 616 rv = bcm_tx(_bcmx_tx_flood_unit, pkt, NULL); 617 618 if (rv < 0) { 619 return rv; 620 } else { 621 return BCMX_TX_FLOODED; 622 } 623 } 624 625 626 /* 627 * Function: 628 * bcmx_tx_pkt_l3_set 629 * Purpose: 630 * Set l3 characteristic for a bcm pkt sent thru bcmx 631 * Parameters: 632 * pkt - the pkt to affect 633 * l3 - boolean; should pkt be sent as l3 634 * Returns: 635 * BCM_E_XXX 636 */ 637 638 int 639 bcmx_tx_pkt_l3_set(bcm_pkt_t *pkt, int l3) 640 { 641 bcm_pbmp_t tmp_pbm; 642 643 if (pkt) { 644 if (l3) { 645 BCM_PBMP_CLEAR(tmp_pbm); 646 BCM_PBMP_NEGATE(pkt->tx_l3pbmp, tmp_pbm); 647 } else { 648 BCM_PBMP_CLEAR(pkt->tx_l3pbmp); 649 } 650 } 651 652 return BCM_E_NONE; 653 } 654 655 656 /* 657 * Function: 658 * bcmx_tx_pkt_untagged_set 659 * Purpose: 660 * Set untagged characteristic for a bcm pkt sent thru bcmx 661 * Parameters: 662 * pkt - the pkt to affect 663 * untagged - boolean; should pkt be tagged 664 * Returns: 665 * BCM_E_XXX 666 */ 667 668 int 669 bcmx_tx_pkt_untagged_set(bcm_pkt_t *pkt, int untagged) 670 { 671 bcm_pbmp_t tmp_pbm; 672 673 if (pkt) { 674 if (untagged) { 675 BCM_PBMP_CLEAR(tmp_pbm); 676 BCM_PBMP_NEGATE(pkt->tx_upbmp, tmp_pbm); 677 } else { 678 BCM_PBMP_CLEAR(pkt->tx_upbmp); 679 } 680 } 681 682 return BCM_E_NONE; 683 } 684 685 686 687 /**************************************************************** 688 * 689 * The following calls are DEPRECATED. 690 */ 691 692 /* 693 * Function: 694 * bcmx_tx_port_list DEPRECATED: Use bcmx_tx_lplist. 695 * Purpose: 696 * Transmit a packet to a list of ports 697 * Parameters: 698 * lplist - lport list of destinations 699 * pkt - pointer to packet to send 700 * Returns: 701 * BCM_E_XXX 702 * Notes: 703 * Returns last error seen if one occurs and continues attempting 704 * to send. 705 * 706 * Not sophisticated yet; just sends out to each port individually. 707 * This could generate the bitmaps per unit and send them out 708 * once per unit. 709 */ 710 711 int 712 bcmx_tx_port_list(bcmx_lplist_t *lplist, bcm_pkt_t *pkt) 713 { 714 bcmx_lport_t lport; 715 int count; 716 int rv = BCM_E_NONE; 717 int rv_keep = BCM_E_NONE; 718 719 BCMX_TX_INIT_CHECK; 720 721 if (pkt->call_back != NULL) { 722 LOG_WARN(BSL_LS_BCMX_COMMON, 723 (BSL_META("WARNING: bcmx_tx_port_list: async not supported\n"))); 724 } 725 BCMX_LPLIST_ITER(*lplist, lport, count) { 726 rv = bcmx_tx_uc(pkt, lport, 0); 727 if (rv < 0) { 728 rv_keep = rv; 729 } 730 } 731 732 return rv_keep; 733 }