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