ethernet.c revision 219706
1145519Sdarrenr/************************************************************************* 2145510SdarrenrCopyright (c) 2003-2007 Cavium Networks (support@cavium.com). All rights 322514Sdarrenrreserved. 453024Sguido 522514Sdarrenr 680486SdarrenrRedistribution and use in source and binary forms, with or without 722514Sdarrenrmodification, are permitted provided that the following conditions are 8145510Sdarrenrmet: 9145510Sdarrenr 10255332Scy * Redistributions of source code must retain the above copyright 1192686Sdarrenr notice, this list of conditions and the following disclaimer. 1222514Sdarrenr 1322514Sdarrenr * Redistributions in binary form must reproduce the above 14153881Sguido copyright notice, this list of conditions and the following 15170268Sdarrenr disclaimer in the documentation and/or other materials provided 1631183Speter with the distribution. 1722514Sdarrenr 18145510Sdarrenr * Neither the name of Cavium Networks nor the names of 1922514Sdarrenr its contributors may be used to endorse or promote products 2022514Sdarrenr derived from this software without specific prior written 2153024Sguido permission. 2231183Speter 23170268SdarrenrThis Software, including technical data, may be subject to U.S. export control laws, including the U.S. Export Administration Act and its associated regulations, and may be subject to export or import regulations in other countries. 2431183Speter 2524583SdarrenrTO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" 26145510SdarrenrAND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. 2724583Sdarrenr*************************************************************************/ 2824583Sdarrenr 29145510Sdarrenr#include <sys/cdefs.h> 30145510Sdarrenr__FBSDID("$FreeBSD: head/sys/mips/cavium/octe/ethernet.c 219706 2011-03-16 22:51:34Z jmallett $"); 31145510Sdarrenr 3224583Sdarrenr#include <sys/param.h> 3353024Sguido#include <sys/systm.h> 3422514Sdarrenr#include <sys/bus.h> 3522514Sdarrenr#include <sys/conf.h> 3622514Sdarrenr#include <sys/endian.h> 3722514Sdarrenr#include <sys/kernel.h> 3822514Sdarrenr#include <sys/rman.h> 3922514Sdarrenr#include <sys/mbuf.h> 4022514Sdarrenr#include <sys/socket.h> 41145510Sdarrenr#include <sys/module.h> 42255332Scy#include <sys/smp.h> 4322514Sdarrenr#include <sys/taskqueue.h> 4422514Sdarrenr 4522514Sdarrenr#include <net/ethernet.h> 4622514Sdarrenr#include <net/if.h> 4722514Sdarrenr#include <net/if_types.h> 4822514Sdarrenr 4922514Sdarrenr#include "wrapper-cvmx-includes.h" 5022514Sdarrenr#include "ethernet-headers.h" 5122514Sdarrenr 5222514Sdarrenr#include "octebusvar.h" 5322514Sdarrenr 5422514Sdarrenr/* 5522514Sdarrenr * XXX/juli 5622514Sdarrenr * Convert 0444 to tunables, 0644 to sysctls. 5722514Sdarrenr */ 5822514Sdarrenr#if defined(CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS) && CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS 5922514Sdarrenrint num_packet_buffers = CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS; 6022514Sdarrenr#else 6122514Sdarrenrint num_packet_buffers = 1024; 6222514Sdarrenr#endif 6322514SdarrenrTUNABLE_INT("hw.octe.num_packet_buffers", &num_packet_buffers); 6422514Sdarrenr/* 6522514Sdarrenr "\t\tNumber of packet buffers to allocate and store in the\n" 6622514Sdarrenr "\t\tFPA. By default, 1024 packet buffers are used unless\n" 6722514Sdarrenr "\t\tCONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS is defined." */ 68255332Scy 69255332Scyint pow_receive_group = 15; 7022514SdarrenrTUNABLE_INT("hw.octe.pow_receive_group", &pow_receive_group); 7122514Sdarrenr/* 7222514Sdarrenr "\t\tPOW group to receive packets from. All ethernet hardware\n" 7322514Sdarrenr "\t\twill be configured to send incomming packets to this POW\n" 7422514Sdarrenr "\t\tgroup. Also any other software can submit packets to this\n" 7522514Sdarrenr "\t\tgroup for the kernel to process." */ 7622514Sdarrenr 7722514Sdarrenrextern int octeon_is_simulation(void); 7853024Sguido 7953024Sguido/** 8053024Sguido * Exported from the kernel so we can determine board information. It is 8153024Sguido * passed by the bootloader to the kernel. 8222514Sdarrenr */ 8322514Sdarrenrextern cvmx_bootinfo_t *octeon_bootinfo; 8422514Sdarrenr 8522514Sdarrenr/** 8622514Sdarrenr * Periodic timer to check auto negotiation 8722514Sdarrenr */ 8822514Sdarrenrstatic struct callout cvm_oct_poll_timer; 8922514Sdarrenr 9022514Sdarrenr/** 9131183Speter * Array of every ethernet device owned by this driver indexed by 9222514Sdarrenr * the ipd input port number. 93145510Sdarrenr */ 94145510Sdarrenrstruct ifnet *cvm_oct_device[TOTAL_NUMBER_OF_PORTS]; 95145510Sdarrenr 9622514Sdarrenr/** 97145510Sdarrenr * Task to handle link status changes. 9822514Sdarrenr */ 9931183Speterstatic struct taskqueue *cvm_oct_link_taskq; 10022514Sdarrenr 10122514Sdarrenr/* 10222514Sdarrenr * Number of buffers in output buffer pool. 10322514Sdarrenr */ 10422514Sdarrenrstatic int cvm_oct_num_output_buffers; 10522514Sdarrenr 10622514Sdarrenr/* 10722514Sdarrenr * The offset from mac_addr_base that should be used for the next port 10822514Sdarrenr * that is configured. By convention, if any mgmt ports exist on the 10922514Sdarrenr * chip, they get the first mac addresses. The ports controlled by 11022514Sdarrenr * this driver are numbered sequencially following any mgmt addresses 11122514Sdarrenr * that may exist. 11222514Sdarrenr */ 11322514Sdarrenrunsigned int cvm_oct_mac_addr_offset; 11422514Sdarrenr 11522514Sdarrenr/** 11622514Sdarrenr * Function to update link status. 11722514Sdarrenr */ 11822514Sdarrenrstatic void cvm_oct_update_link(void *context, int pending) 11922514Sdarrenr{ 12022514Sdarrenr cvm_oct_private_t *priv = (cvm_oct_private_t *)context; 12122514Sdarrenr struct ifnet *ifp = priv->ifp; 12222514Sdarrenr cvmx_helper_link_info_t link_info; 12322514Sdarrenr 12422514Sdarrenr link_info.u64 = priv->link_info; 12522514Sdarrenr 12622514Sdarrenr if (link_info.s.link_up) { 12722514Sdarrenr if_link_state_change(ifp, LINK_STATE_UP); 12822514Sdarrenr DEBUGPRINT("%s: %u Mbps %s duplex, port %2d, queue %2d\n", 129145510Sdarrenr if_name(ifp), link_info.s.speed, 130145510Sdarrenr (link_info.s.full_duplex) ? "Full" : "Half", 131145510Sdarrenr priv->port, priv->queue); 132145510Sdarrenr } else { 133145510Sdarrenr if_link_state_change(ifp, LINK_STATE_DOWN); 134145510Sdarrenr DEBUGPRINT("%s: Link down\n", if_name(ifp)); 135145510Sdarrenr } 13622514Sdarrenr priv->need_link_update = 0; 13722514Sdarrenr} 13822514Sdarrenr 13922514Sdarrenr/** 14022514Sdarrenr * Periodic timer tick for slow management operations 14122514Sdarrenr * 142 * @param arg Device to check 143 */ 144static void cvm_do_timer(void *arg) 145{ 146 static int port; 147 static int updated; 148 if (port < CVMX_PIP_NUM_INPUT_PORTS) { 149 if (cvm_oct_device[port]) { 150 int queues_per_port; 151 int qos; 152 cvm_oct_private_t *priv = (cvm_oct_private_t *)cvm_oct_device[port]->if_softc; 153 154 cvm_oct_common_poll(priv->ifp); 155 if (priv->need_link_update) { 156 updated++; 157 taskqueue_enqueue(cvm_oct_link_taskq, &priv->link_task); 158 } 159 160 queues_per_port = cvmx_pko_get_num_queues(port); 161 /* Drain any pending packets in the free list */ 162 for (qos = 0; qos < queues_per_port; qos++) { 163 if (_IF_QLEN(&priv->tx_free_queue[qos]) > 0) { 164 IF_LOCK(&priv->tx_free_queue[qos]); 165 while (_IF_QLEN(&priv->tx_free_queue[qos]) > cvmx_fau_fetch_and_add32(priv->fau+qos*4, 0)) { 166 struct mbuf *m; 167 168 _IF_DEQUEUE(&priv->tx_free_queue[qos], m); 169 m_freem(m); 170 } 171 IF_UNLOCK(&priv->tx_free_queue[qos]); 172 173 /* 174 * XXX locking! 175 */ 176 priv->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; 177 } 178 } 179 } 180 port++; 181 /* Poll the next port in a 50th of a second. 182 This spreads the polling of ports out a little bit */ 183 callout_reset(&cvm_oct_poll_timer, hz / 50, cvm_do_timer, NULL); 184 } else { 185 port = 0; 186 /* If any updates were made in this run, continue iterating at 187 * 1/50th of a second, so that if a link has merely gone down 188 * temporarily (e.g. because of interface reinitialization) it 189 * will not be forced to stay down for an entire second. 190 */ 191 if (updated > 0) { 192 updated = 0; 193 callout_reset(&cvm_oct_poll_timer, hz / 50, cvm_do_timer, NULL); 194 } else { 195 /* All ports have been polled. Start the next iteration through 196 the ports in one second */ 197 callout_reset(&cvm_oct_poll_timer, hz, cvm_do_timer, NULL); 198 } 199 } 200} 201 202/** 203 * Configure common hardware for all interfaces 204 */ 205static void cvm_oct_configure_common_hw(device_t bus) 206{ 207 struct octebus_softc *sc; 208 int pko_queues; 209 int error; 210 int rid; 211 212 sc = device_get_softc(bus); 213 214 /* Setup the FPA */ 215 cvmx_fpa_enable(); 216 cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, CVMX_FPA_PACKET_POOL_SIZE, 217 num_packet_buffers); 218 cvm_oct_mem_fill_fpa(CVMX_FPA_WQE_POOL, CVMX_FPA_WQE_POOL_SIZE, 219 num_packet_buffers); 220 if (CVMX_FPA_OUTPUT_BUFFER_POOL != CVMX_FPA_PACKET_POOL) { 221 /* 222 * If the FPA uses different pools for output buffers and 223 * packets, size the output buffer pool based on the number 224 * of PKO queues. 225 */ 226 if (OCTEON_IS_MODEL(OCTEON_CN38XX)) 227 pko_queues = 128; 228 else if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) 229 pko_queues = 32; 230 else if (OCTEON_IS_MODEL(OCTEON_CN50XX)) 231 pko_queues = 32; 232 else 233 pko_queues = 256; 234 235 cvm_oct_num_output_buffers = 4 * pko_queues; 236 cvm_oct_mem_fill_fpa(CVMX_FPA_OUTPUT_BUFFER_POOL, 237 CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, 238 cvm_oct_num_output_buffers); 239 } 240 241 if (USE_RED) 242 cvmx_helper_setup_red(num_packet_buffers/4, 243 num_packet_buffers/8); 244 245 /* Enable the MII interface */ 246 if (!octeon_is_simulation()) 247 cvmx_write_csr(CVMX_SMI_EN, 1); 248 249 /* Register an IRQ hander for to receive POW interrupts */ 250 rid = 0; 251 sc->sc_rx_irq = bus_alloc_resource(bus, SYS_RES_IRQ, &rid, 252 CVMX_IRQ_WORKQ0 + pow_receive_group, 253 CVMX_IRQ_WORKQ0 + pow_receive_group, 254 1, RF_ACTIVE); 255 if (sc->sc_rx_irq == NULL) { 256 device_printf(bus, "could not allocate workq irq"); 257 return; 258 } 259 260 error = bus_setup_intr(bus, sc->sc_rx_irq, INTR_TYPE_NET | INTR_MPSAFE, 261 cvm_oct_do_interrupt, NULL, cvm_oct_device, 262 &sc->sc_rx_intr_cookie); 263 if (error != 0) { 264 device_printf(bus, "could not setup workq irq"); 265 return; 266 } 267 268 269#ifdef SMP 270 { 271 cvmx_ciu_intx0_t en; 272 int core; 273 274 CPU_FOREACH(core) { 275 if (core == PCPU_GET(cpuid)) 276 continue; 277 278 en.u64 = cvmx_read_csr(CVMX_CIU_INTX_EN0(core*2)); 279 en.s.workq |= (1<<pow_receive_group); 280 cvmx_write_csr(CVMX_CIU_INTX_EN0(core*2), en.u64); 281 } 282 } 283#endif 284} 285 286 287/** 288 * Free a work queue entry received in a intercept callback. 289 * 290 * @param work_queue_entry 291 * Work queue entry to free 292 * @return Zero on success, Negative on failure. 293 */ 294int cvm_oct_free_work(void *work_queue_entry) 295{ 296 cvmx_wqe_t *work = work_queue_entry; 297 298 int segments = work->word2.s.bufs; 299 cvmx_buf_ptr_t segment_ptr = work->packet_ptr; 300 301 while (segments--) { 302 cvmx_buf_ptr_t next_ptr = *(cvmx_buf_ptr_t *)cvmx_phys_to_ptr(segment_ptr.s.addr-8); 303 if (__predict_false(!segment_ptr.s.i)) 304 cvmx_fpa_free(cvm_oct_get_buffer_ptr(segment_ptr), segment_ptr.s.pool, DONT_WRITEBACK(CVMX_FPA_PACKET_POOL_SIZE/128)); 305 segment_ptr = next_ptr; 306 } 307 cvmx_fpa_free(work, CVMX_FPA_WQE_POOL, DONT_WRITEBACK(1)); 308 309 return 0; 310} 311 312 313/** 314 * Module/ driver initialization. Creates the linux network 315 * devices. 316 * 317 * @return Zero on success 318 */ 319int cvm_oct_init_module(device_t bus) 320{ 321 device_t dev; 322 int ifnum; 323 int num_interfaces; 324 int interface; 325 int fau = FAU_NUM_PACKET_BUFFERS_TO_FREE; 326 int qos; 327 328 printf("cavium-ethernet: %s\n", OCTEON_SDK_VERSION_STRING); 329 330 /* 331 * MAC addresses for this driver start after the management 332 * ports. 333 * 334 * XXX Would be nice if __cvmx_mgmt_port_num_ports() were 335 * not static to cvmx-mgmt-port.c. 336 */ 337 if (OCTEON_IS_MODEL(OCTEON_CN56XX)) 338 cvm_oct_mac_addr_offset = 1; 339 else if (OCTEON_IS_MODEL(OCTEON_CN52XX) || OCTEON_IS_MODEL(OCTEON_CN63XX)) 340 cvm_oct_mac_addr_offset = 2; 341 else 342 cvm_oct_mac_addr_offset = 0; 343 344 cvm_oct_rx_initialize(); 345 cvm_oct_configure_common_hw(bus); 346 347 cvmx_helper_initialize_packet_io_global(); 348 349 /* Change the input group for all ports before input is enabled */ 350 num_interfaces = cvmx_helper_get_number_of_interfaces(); 351 for (interface = 0; interface < num_interfaces; interface++) { 352 int num_ports = cvmx_helper_ports_on_interface(interface); 353 int port; 354 355 for (port = 0; port < num_ports; port++) { 356 cvmx_pip_prt_tagx_t pip_prt_tagx; 357 int pkind = cvmx_helper_get_ipd_port(interface, port); 358 359 pip_prt_tagx.u64 = cvmx_read_csr(CVMX_PIP_PRT_TAGX(pkind)); 360 pip_prt_tagx.s.grp = pow_receive_group; 361 cvmx_write_csr(CVMX_PIP_PRT_TAGX(pkind), pip_prt_tagx.u64); 362 } 363 } 364 365 cvmx_helper_ipd_and_packet_input_enable(); 366 367 memset(cvm_oct_device, 0, sizeof(cvm_oct_device)); 368 369 cvm_oct_link_taskq = taskqueue_create("octe link", M_NOWAIT, 370 taskqueue_thread_enqueue, &cvm_oct_link_taskq); 371 taskqueue_start_threads(&cvm_oct_link_taskq, 1, PI_NET, 372 "octe link taskq"); 373 374 /* Initialize the FAU used for counting packet buffers that need to be freed */ 375 cvmx_fau_atomic_write32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); 376 377 ifnum = 0; 378 num_interfaces = cvmx_helper_get_number_of_interfaces(); 379 for (interface = 0; interface < num_interfaces; interface++) { 380 cvmx_helper_interface_mode_t imode = cvmx_helper_interface_get_mode(interface); 381 int num_ports = cvmx_helper_ports_on_interface(interface); 382 int port; 383 384 for (port = cvmx_helper_get_ipd_port(interface, 0); port < cvmx_helper_get_ipd_port(interface, num_ports); port++) { 385 cvm_oct_private_t *priv; 386 struct ifnet *ifp; 387 388 dev = BUS_ADD_CHILD(bus, 0, "octe", ifnum++); 389 if (dev != NULL) 390 ifp = if_alloc(IFT_ETHER); 391 if (dev == NULL || ifp == NULL) { 392 printf("\t\tFailed to allocate ethernet device for port %d\n", port); 393 continue; 394 } 395 396 /* Initialize the device private structure. */ 397 device_probe(dev); 398 priv = device_get_softc(dev); 399 priv->dev = dev; 400 priv->ifp = ifp; 401 priv->imode = imode; 402 priv->port = port; 403 priv->queue = cvmx_pko_get_base_queue(priv->port); 404 priv->fau = fau - cvmx_pko_get_num_queues(port) * 4; 405 for (qos = 0; qos < cvmx_pko_get_num_queues(port); qos++) 406 cvmx_fau_atomic_write32(priv->fau+qos*4, 0); 407 TASK_INIT(&priv->link_task, 0, cvm_oct_update_link, priv); 408 409 switch (priv->imode) { 410 411 /* These types don't support ports to IPD/PKO */ 412 case CVMX_HELPER_INTERFACE_MODE_DISABLED: 413 case CVMX_HELPER_INTERFACE_MODE_PCIE: 414 case CVMX_HELPER_INTERFACE_MODE_PICMG: 415 break; 416 417 case CVMX_HELPER_INTERFACE_MODE_NPI: 418 priv->init = cvm_oct_common_init; 419 priv->uninit = cvm_oct_common_uninit; 420 device_set_desc(dev, "Cavium Octeon NPI Ethernet"); 421 break; 422 423 case CVMX_HELPER_INTERFACE_MODE_XAUI: 424 priv->init = cvm_oct_xaui_init; 425 priv->uninit = cvm_oct_common_uninit; 426 device_set_desc(dev, "Cavium Octeon XAUI Ethernet"); 427 break; 428 429 case CVMX_HELPER_INTERFACE_MODE_LOOP: 430 priv->init = cvm_oct_common_init; 431 priv->uninit = cvm_oct_common_uninit; 432 device_set_desc(dev, "Cavium Octeon LOOP Ethernet"); 433 break; 434 435 case CVMX_HELPER_INTERFACE_MODE_SGMII: 436 priv->init = cvm_oct_sgmii_init; 437 priv->uninit = cvm_oct_common_uninit; 438 device_set_desc(dev, "Cavium Octeon SGMII Ethernet"); 439 break; 440 441 case CVMX_HELPER_INTERFACE_MODE_SPI: 442 priv->init = cvm_oct_spi_init; 443 priv->uninit = cvm_oct_spi_uninit; 444 device_set_desc(dev, "Cavium Octeon SPI Ethernet"); 445 break; 446 447 case CVMX_HELPER_INTERFACE_MODE_RGMII: 448 priv->init = cvm_oct_rgmii_init; 449 priv->uninit = cvm_oct_rgmii_uninit; 450 device_set_desc(dev, "Cavium Octeon RGMII Ethernet"); 451 break; 452 453 case CVMX_HELPER_INTERFACE_MODE_GMII: 454 priv->init = cvm_oct_rgmii_init; 455 priv->uninit = cvm_oct_rgmii_uninit; 456 device_set_desc(dev, "Cavium Octeon GMII Ethernet"); 457 break; 458 } 459 460 ifp->if_softc = priv; 461 462 if (!priv->init) { 463 panic("%s: unsupported device type, need to free ifp.", __func__); 464 } else 465 if (priv->init(ifp) < 0) { 466 printf("\t\tFailed to register ethernet device for interface %d, port %d\n", 467 interface, priv->port); 468 panic("%s: init failed, need to free ifp.", __func__); 469 } else { 470 cvm_oct_device[priv->port] = ifp; 471 fau -= cvmx_pko_get_num_queues(priv->port) * sizeof(uint32_t); 472 } 473 } 474 } 475 476 if (INTERRUPT_LIMIT) { 477 /* Set the POW timer rate to give an interrupt at most INTERRUPT_LIMIT times per second */ 478 cvmx_write_csr(CVMX_POW_WQ_INT_PC, octeon_bootinfo->eclock_hz/(INTERRUPT_LIMIT*16*256)<<8); 479 480 /* Enable POW timer interrupt. It will count when there are packets available */ 481 cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), 0x1ful<<24); 482 } else { 483 /* Enable POW interrupt when our port has at least one packet */ 484 cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), 0x1001); 485 } 486 487 callout_init(&cvm_oct_poll_timer, CALLOUT_MPSAFE); 488 callout_reset(&cvm_oct_poll_timer, hz, cvm_do_timer, NULL); 489 490 return 0; 491} 492 493 494/** 495 * Module / driver shutdown 496 * 497 * @return Zero on success 498 */ 499void cvm_oct_cleanup_module(device_t bus) 500{ 501 int port; 502 struct octebus_softc *sc = device_get_softc(bus); 503 504 /* Disable POW interrupt */ 505 cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), 0); 506 507 /* Free the interrupt handler */ 508 bus_teardown_intr(bus, sc->sc_rx_irq, sc->sc_rx_intr_cookie); 509 510 callout_stop(&cvm_oct_poll_timer); 511 cvm_oct_rx_shutdown(); 512 513 cvmx_helper_shutdown_packet_io_global(); 514 515 /* Free the ethernet devices */ 516 for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { 517 if (cvm_oct_device[port]) { 518 cvm_oct_tx_shutdown(cvm_oct_device[port]); 519#if 0 520 unregister_netdev(cvm_oct_device[port]); 521 kfree(cvm_oct_device[port]); 522#else 523 panic("%s: need to detach and free interface.", __func__); 524#endif 525 cvm_oct_device[port] = NULL; 526 } 527 } 528 /* Free the HW pools */ 529 cvm_oct_mem_empty_fpa(CVMX_FPA_PACKET_POOL, CVMX_FPA_PACKET_POOL_SIZE, num_packet_buffers); 530 cvm_oct_mem_empty_fpa(CVMX_FPA_WQE_POOL, CVMX_FPA_WQE_POOL_SIZE, num_packet_buffers); 531 532 if (CVMX_FPA_OUTPUT_BUFFER_POOL != CVMX_FPA_PACKET_POOL) 533 cvm_oct_mem_empty_fpa(CVMX_FPA_OUTPUT_BUFFER_POOL, CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, cvm_oct_num_output_buffers); 534 535 /* Disable FPA, all buffers are free, not done by helper shutdown. */ 536 cvmx_fpa_disable(); 537} 538