/**************************************************************************
 *
 * Device driver for Solarflare Communications EF10 devices
 *
 * Written by Shradha Shah, maintained by <pre-boot-drivers@xilinx.com>
 *
 * Copyright 2012-2019 Solarflare Communications Inc.
 * Copyright 2019-2020 Xilinx Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 *
 ***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/io.h>
#include <ipxe/pci.h>
#include <ipxe/malloc.h>
#include <ipxe/ethernet.h>
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include "efx_hunt.h"
#include "efx_bitfield.h"
#include "ef10_regs.h"
#include "mc_driver_pcol.h"
#include <ipxe/if_ether.h>

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#define	HUNTINGTON_NVRAM_CHUNK 0x80
#define HUNTINGTON_NVS_MAX_LENGTH 0x1000

#define EMCDI_IO(code)	EUNIQ(EINFO_EIO, (code))

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
#endif

struct hunt_nic *primary_nics = NULL;

struct hunt_nic {
	struct efx_nic efx;

	/* PHY information */
	unsigned int phy_cap_mask;
	unsigned int phy_cap;
	unsigned long link_poll_timer;

	/* resource housekeeping */
	uint64_t uc_filter_id;
	uint64_t mc_filter_id;
	u8 mac[ETH_ALEN];

	struct {
		/* Common payload for all MCDI requests */
		unsigned int seqno;

		size_t resp_hdr_len;
		size_t resp_data_len;

		struct io_buffer *iob;
		uint64_t dma_addr;
	} mcdi;

	struct hunt_nic *primary;
	struct hunt_nic *next_primary;
	u32 flags;
};

static int hunt_nic_is_primary(struct hunt_nic *hunt)
{
	return (hunt->flags & (1 << MC_CMD_DRV_ATTACH_EXT_OUT_FLAG_PRIMARY));
}

/*******************************************************************************
 *
 *
 * MCDI transport
 *
 * This has been based on the implementation of MCDI in the common code driver.
 *
 *
 ******************************************************************************/

static int hunt_mcdi_init(struct hunt_nic *hunt)
{
	size_t max_msg_size;
	int rc;

	/* The MCDI message has two 32-bit headers (the MCDI header and the
	 * MCDI v2 extended command) and then up to MCDI_CTL_SDU_LEN_MAX_V2
	 * bytes of payload
	 */
	max_msg_size = 2 * sizeof(efx_dword_t) + MCDI_CTL_SDU_LEN_MAX_V2;

	hunt->mcdi.iob = alloc_iob(max_msg_size);
	if (!hunt->mcdi.iob) {
		rc = -ENOMEM;
		return rc;
	}
	return 0;
}

static void hunt_mcdi_copyin(struct hunt_nic *hunt,
			     unsigned int cmd,
			     uint8_t *inbuf,
			     size_t inlen)
{
	efx_dword_t hdr[2];
	uint32_t seqno;
	unsigned int xflags;
	size_t hdr_len;
	u8 *pdu = hunt->mcdi.iob->data;

	seqno = hunt->mcdi.seqno & MCDI_SEQ_MASK;

	xflags = 0;

	EFX_POPULATE_DWORD_7(hdr[0],
			     MCDI_HEADER_CODE, MC_CMD_V2_EXTN,
			     MCDI_HEADER_RESYNC, 1,
			     MCDI_HEADER_DATALEN, 0,
			     MCDI_HEADER_SEQ, seqno,
			     MCDI_HEADER_ERROR, 0,
			     MCDI_HEADER_RESPONSE, 0,
			     MCDI_HEADER_XFLAGS, xflags);
	EFX_POPULATE_DWORD_2(hdr[1],
			     MC_CMD_V2_EXTN_IN_EXTENDED_CMD, cmd,
			     MC_CMD_V2_EXTN_IN_ACTUAL_LEN, inlen);

	hdr_len = sizeof(hdr);

	memcpy(pdu, &hdr, hdr_len);
	assert(inlen <= MCDI_CTL_SDU_LEN_MAX_V2);
	memcpy(pdu + hdr_len, inbuf, inlen);

	wmb();	/* Sync the data before ringing the doorbell */

	/* Ring the doorbell to post the command DMA address to the MC */
	hunt->mcdi.dma_addr = virt_to_bus(hunt->mcdi.iob->data);

	assert((hunt->mcdi.dma_addr & 0xFF) == 0);

	_efx_writel(&hunt->efx,
		   cpu_to_le32((u64)hunt->mcdi.dma_addr >> 32),
		   ER_DZ_MC_DB_LWRD);

	_efx_writel(&hunt->efx,
		   cpu_to_le32((u32)hunt->mcdi.dma_addr),
		   ER_DZ_MC_DB_HWRD);
}

static void hunt_mcdi_copyout(struct hunt_nic *hunt,
			      uint8_t *outbuf, size_t outlen)
{
	size_t offset;
	const u8 *pdu = hunt->mcdi.iob->data;

	offset =  hunt->mcdi.resp_hdr_len;

	if (outlen > 0)
		memcpy(outbuf, pdu+offset, outlen);
}

static int hunt_mcdi_request_poll(struct hunt_nic *hunt, bool quiet)
{
	unsigned int resplen, respseq, error;
	unsigned long finish;
	efx_dword_t errdword;
	efx_qword_t qword;
	const efx_dword_t *pdu = hunt->mcdi.iob->data;
	const u8 *pdu1 = hunt->mcdi.iob->data;
	int delay, rc;

	/* Spin for up to 5s, polling at intervals of 10us, 20us, ... ~100ms  */
	finish = currticks() + (5 * TICKS_PER_SEC);
	delay = 10;
	while (1) {
		udelay(delay);

		/* Check for an MCDI response */
		if (EFX_DWORD_FIELD(*pdu, MCDI_HEADER_RESPONSE))
			break;

		if (currticks() >= finish)
			return -ETIMEDOUT;

		if (delay < 100000)
			delay *= 2;
	}

	memcpy(&qword, pdu1, 8);

	/* qword.dword[0] is the MCDI header; qword.dword[1] is the MCDI v2
	 * extended command
	 */
	respseq = EFX_DWORD_FIELD(qword.dword[0], MCDI_HEADER_SEQ);
	error = EFX_DWORD_FIELD(qword.dword[0], MCDI_HEADER_ERROR);
	resplen = EFX_DWORD_FIELD(qword.dword[1], MC_CMD_V2_EXTN_IN_ACTUAL_LEN);

	if (error && resplen == 0) {
		if (!quiet)
			DBGC(hunt, "MC rebooted\n");
		return -EIO;
	} else if ((respseq ^ hunt->mcdi.seqno) & MCDI_SEQ_MASK) {
		if (!quiet)
			DBGC(hunt, "MC response mismatch rxseq 0x%x txseq "
			     "0x%x\n", respseq, hunt->mcdi.seqno);
		return -EIO;
	} else if (error) {
		memcpy(&errdword, pdu1 + 8, 4);
		rc = EFX_DWORD_FIELD(errdword, EFX_DWORD_0);
		switch (rc) {
		case MC_CMD_ERR_ENOENT:
			return -ENOENT;
		case MC_CMD_ERR_EINTR:
			return -EINTR;
		case MC_CMD_ERR_EACCES:
			return -EACCES;
		case MC_CMD_ERR_EBUSY:
			return -EBUSY;
		case MC_CMD_ERR_EINVAL:
			return -EINVAL;
		case MC_CMD_ERR_EDEADLK:
			return -EDEADLK;
		case MC_CMD_ERR_ENOSYS:
			return -ENOSYS;
		case MC_CMD_ERR_ETIME:
			return -ETIME;
		case MC_CMD_ERR_EPERM:
			return -EPERM;
		default:
			/* Return the MC error in an I/O error. */
			return EMCDI_IO(rc & 0xff);
		}
	}
	hunt->mcdi.resp_hdr_len = 8;
	hunt->mcdi.resp_data_len = resplen;

	return 0;
}

static void hunt_mcdi_fini(struct hunt_nic *hunt)
{
	free_iob(hunt->mcdi.iob);
}

int _hunt_mcdi(struct efx_nic *efx, unsigned int cmd,
	       const efx_dword_t *inbuf, size_t inlen,
	       efx_dword_t *outbuf, size_t outlen,
	       size_t *outlen_actual, bool quiet)
{
	int rc;
	struct hunt_nic *hunt = (struct hunt_nic *) efx;
	size_t local_outlen_actual;

	if (outlen_actual == NULL)
		outlen_actual = &local_outlen_actual;

	++hunt->mcdi.seqno;
	hunt_mcdi_copyin(hunt, cmd, (uint8_t *) inbuf, inlen);

	rc = hunt_mcdi_request_poll(hunt, quiet);
	if (rc != 0) {
		if (!quiet)
			DBGC(hunt, "MC response to cmd 0x%x: %s\n",
			     cmd, strerror(rc));
		return rc;
	}

	*outlen_actual = hunt->mcdi.resp_data_len;

	hunt_mcdi_copyout(hunt, (uint8_t *) outbuf, outlen);

	return 0;
}

static int hunt_mcdi(struct hunt_nic *hunt, struct efx_mcdi_req_s *req)
{
	return _hunt_mcdi(&hunt->efx, req->emr_cmd,
			  (const efx_dword_t *) req->emr_in_buf,
			  req->emr_in_length,
			  (efx_dword_t *) req->emr_out_buf, req->emr_out_length,
			  &req->emr_out_length_used, false);
}

static int hunt_mcdi_quiet(struct hunt_nic *hunt, struct efx_mcdi_req_s *req)
{
	return _hunt_mcdi(&hunt->efx, req->emr_cmd,
			  (const efx_dword_t *) req->emr_in_buf,
			  req->emr_in_length,
			  (efx_dword_t *) req->emr_out_buf, req->emr_out_length,
			  &req->emr_out_length_used, true);
}

/*******************************************************************************
 *
 *
 * Hardware initialization
 *
 *
 ******************************************************************************/
static int hunt_get_workarounds(struct hunt_nic *hunt, uint32_t *implemented,
				uint32_t *enabled)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_WORKAROUNDS_OUT_LEN);
	int rc;

	*implemented = *enabled = 0;

	req.emr_cmd = MC_CMD_GET_WORKAROUNDS;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi(hunt, &req);

	if (rc)
		return rc;

	if (req.emr_out_length_used < MC_CMD_GET_WORKAROUNDS_OUT_LEN)
		return -EMSGSIZE;

	*implemented = MCDI_DWORD(outbuf, GET_WORKAROUNDS_OUT_IMPLEMENTED);
	*enabled = MCDI_DWORD(outbuf, GET_WORKAROUNDS_OUT_ENABLED);
	return 0;
}

static int hunt_enable_workaround_35388(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(payload, MC_CMD_WORKAROUND_IN_LEN);

	req.emr_cmd = MC_CMD_WORKAROUND;
	req.emr_in_buf = payload;
	req.emr_in_length = MC_CMD_WORKAROUND_IN_LEN;
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, WORKAROUND_IN_TYPE,
		       MC_CMD_WORKAROUND_BUG35388);
	MCDI_SET_DWORD(req.emr_in_buf, WORKAROUND_IN_ENABLED, 1);

	/* If the firmware doesn't support this workaround, hunt_mcdi() will
	 * return -EINVAL from hunt_mcdi_request_poll().
	 */
	return hunt_mcdi(hunt, &req);
}

static int hunt_workaround_35388(struct hunt_nic *hunt)
{
	uint32_t implemented, enabled;
	int rc = hunt_get_workarounds(hunt, &implemented, &enabled);

	if (rc < 0)
		return 0;
	if (!(implemented & MC_CMD_GET_WORKAROUNDS_OUT_BUG35388))
		return 0;
	if (enabled & MC_CMD_GET_WORKAROUNDS_OUT_BUG35388)
		return 1;

	rc = hunt_enable_workaround_35388(hunt);
	if (rc == 0)
		return 1; /* Workaround is enabled */
	else
		return 0;
}

static int hunt_get_port_assignment(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_PORT_ASSIGNMENT_OUT_LEN);
	int rc;

	req.emr_cmd = MC_CMD_GET_PORT_ASSIGNMENT;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	hunt->efx.port = MCDI_DWORD(req.emr_out_buf,
				    GET_PORT_ASSIGNMENT_OUT_PORT);
	return 0;
}

static int hunt_mac_addr(struct hunt_nic *hunt, uint8_t *ll_addr)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_MAC_ADDRESSES_OUT_LEN);
	int rc;

	req.emr_cmd = MC_CMD_GET_MAC_ADDRESSES;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = MC_CMD_GET_MAC_ADDRESSES_OUT_LEN;

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_GET_MAC_ADDRESSES_OUT_LEN)
		return -EMSGSIZE;

	memcpy(ll_addr,
	       MCDI_PTR(req.emr_out_buf, GET_MAC_ADDRESSES_OUT_MAC_ADDR_BASE),
	       ETH_ALEN);

	return 0;
}

static int hunt_get_phy_cfg(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_PHY_CFG_OUT_LEN);
	int rc;

	req.emr_cmd = MC_CMD_GET_PHY_CFG;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_GET_PHY_CFG_OUT_LEN)
		return -EMSGSIZE;

	hunt->phy_cap_mask = hunt->phy_cap =
		MCDI_DWORD(req.emr_out_buf, GET_PHY_CFG_OUT_SUPPORTED_CAP);
	DBGC2(hunt, "GET_PHY_CFG: flags=%x, caps=%x\n", rc, hunt->phy_cap);
	return 0;
}

static int hunt_driver_attach(struct hunt_nic *hunt, int attach)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_DRV_ATTACH_IN_LEN);
	MCDI_DECLARE_BUF(outbuf, MC_CMD_DRV_ATTACH_EXT_OUT_LEN);
	int rc;

	req.emr_cmd = MC_CMD_DRV_ATTACH;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	/* Set the PREBOOT flag to indicate later instances of attach should
	 * force an ENTITY RESET
	 */
	if (attach)
		attach |= 1 << MC_CMD_DRV_PREBOOT_LBN;

	MCDI_SET_DWORD(req.emr_in_buf, DRV_ATTACH_IN_NEW_STATE, attach);
	MCDI_SET_DWORD(req.emr_in_buf, DRV_ATTACH_IN_UPDATE, 1);
	MCDI_SET_DWORD(req.emr_in_buf, DRV_ATTACH_IN_FIRMWARE_ID,
		       MC_CMD_FW_DONT_CARE);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_DRV_ATTACH_OUT_LEN)
		return -EMSGSIZE;

	hunt->flags = MCDI_DWORD(outbuf, DRV_ATTACH_EXT_OUT_FUNC_FLAGS);

	return 0;
}

static int hunt_reset(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_ENTITY_RESET_IN_LEN);

	req.emr_cmd = MC_CMD_ENTITY_RESET;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_POPULATE_DWORD_1(req.emr_in_buf, ENTITY_RESET_IN_FLAG,
			      ENTITY_RESET_IN_FUNCTION_RESOURCE_RESET, 1);
	return hunt_mcdi(hunt, &req);
}

static void hunt_clear_udp_tunnel_ports(struct hunt_nic *hunt)
{
	MCDI_DECLARE_BUF(inbuf, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_LENMAX);
	MCDI_DECLARE_BUF(outbuf, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_OUT_LEN);
	struct efx_mcdi_req_s req;
	int rc;

	memset(inbuf, 0, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_LENMAX);
	MCDI_SET_DWORD(inbuf, SET_TUNNEL_ENCAP_UDP_PORTS_IN_FLAGS,
		(1 << MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_UNLOADING_LBN));

	req.emr_cmd = MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi_quiet(hunt, &req);
	if (rc)
		return;

	if (MCDI_DWORD(outbuf, SET_TUNNEL_ENCAP_UDP_PORTS_OUT_FLAGS) &
	    (1 << MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_OUT_RESETTING_LBN)) {
		DBGC(hunt,
		     "Rebooting MC due to clearing UDP tunnel port list\n");
		/* Delay for the MC reboot to complete. */
		mdelay(100);
	}
}

static int hunt_set_mac(struct hunt_nic *hunt)
{
	struct net_device *netdev = hunt->efx.netdev;
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(payload, MC_CMD_SET_MAC_IN_LEN);
	unsigned int fcntl;
	int rc;

	req.emr_cmd = MC_CMD_SET_MAC;
	req.emr_in_buf = payload;
	req.emr_in_length = MC_CMD_SET_MAC_IN_LEN;
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, SET_MAC_IN_MTU,
		       EFX_MAC_FRAME_LEN(ETH_FRAME_LEN));
	MCDI_SET_DWORD(req.emr_in_buf, SET_MAC_IN_DRAIN, 0);
	memcpy(MCDI_PTR(req.emr_in_buf, SET_MAC_IN_ADDR),
	       netdev->ll_addr, ETH_ALEN);
	MCDI_SET_DWORD(req.emr_in_buf, SET_MAC_IN_REJECT, 0);

	/* If the PHY supports autnegotiation, then configure the MAC to match
	 * the negotiated settings. Otherwise force the MAC to TX and RX flow
	 * control.
	 */
	if (hunt->phy_cap_mask & (1 << MC_CMD_PHY_CAP_AN_LBN))
		fcntl = MC_CMD_FCNTL_AUTO;
	else
		fcntl = MC_CMD_FCNTL_BIDIR;
	MCDI_SET_DWORD(req.emr_in_buf, SET_MAC_IN_FCNTL, fcntl);

	rc = hunt_mcdi(hunt, &req);
	/* Ignore failure for permissions reasons */
	if (rc == -EPERM)
		rc = 0;
	return rc;
}

static int hunt_alloc_vis(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_ALLOC_VIS_IN_LEN);

	req.emr_cmd = MC_CMD_ALLOC_VIS;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, ALLOC_VIS_IN_MIN_VI_COUNT, 1);
	MCDI_SET_DWORD(req.emr_in_buf, ALLOC_VIS_IN_MAX_VI_COUNT, 1);

	return hunt_mcdi(hunt, &req);
}

static void hunt_free_vis(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	int rc;

	req.emr_cmd = MC_CMD_FREE_VIS;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		DBGC(hunt, "MC_CMD_FREE_VIS Failed\n");
}

/*******************************************************************************
 *
 *
 * Link state handling
 *
 *
 ******************************************************************************/
static int hunt_check_link(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_LINK_OUT_LEN);
	unsigned int flags, speed;
	bool up;
	int rc;
	static bool link_state = false;

	req.emr_cmd = MC_CMD_GET_LINK;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_GET_LINK_OUT_LEN)
		return -EMSGSIZE;

	flags = MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_FLAGS);
	up = !!(flags & (1 << MC_CMD_GET_LINK_OUT_LINK_UP_LBN));
	speed = MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_LINK_SPEED);

	/* Set netdev_link_*() based on the link status from the MC */
	if (up && speed)
		netdev_link_up(hunt->efx.netdev);
	else
		netdev_link_down(hunt->efx.netdev);

	if (up != link_state) {
		DBGC(hunt, "Link %s, flags=%x, our caps=%x, lpa=%x, speed=%d, fcntl=%x, mac_fault=%x\n",
		     (up? "up": "down"), flags,
		     MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_CAP),
		     MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_LP_CAP),
		     speed,
		     MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_FCNTL),
		     MCDI_DWORD(req.emr_out_buf, GET_LINK_OUT_MAC_FAULT));
		link_state = up;
	}

	return 0;
}

#define MCDI_PORT_SPEED_CAPS   ((1 << MC_CMD_PHY_CAP_10HDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_10FDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_100HDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_100FDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_1000HDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_1000FDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_10000FDX_LBN) | \
				(1 << MC_CMD_PHY_CAP_40000FDX_LBN))

/*******************************************************************************
 *
 *
 * TX
 *
 *
 ******************************************************************************/
static int
hunt_tx_init(struct net_device *netdev, struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	dma_addr_t dma_addr;
	efx_qword_t *addr;
	MCDI_DECLARE_BUF(inbuf,
			 MC_CMD_INIT_TXQ_IN_LEN(EFX_TXQ_NBUFS(EFX_TXD_SIZE)));
	int rc, npages;

	rc = efx_hunt_tx_init(netdev, &dma_addr);
	if (rc != 0)
		return rc;

	npages = EFX_TXQ_NBUFS(EFX_TXD_SIZE);

	req.emr_cmd = MC_CMD_INIT_TXQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = MC_CMD_INIT_TXQ_IN_LEN(npages);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_SIZE, EFX_TXD_SIZE);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_TARGET_EVQ, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_LABEL, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_INSTANCE, 0);

	MCDI_POPULATE_DWORD_6(req.emr_in_buf, INIT_TXQ_IN_FLAGS,
			      INIT_TXQ_IN_FLAG_BUFF_MODE, 0,
			      INIT_TXQ_IN_FLAG_IP_CSUM_DIS, 1,
			      INIT_TXQ_IN_FLAG_TCP_CSUM_DIS, 1,
			      INIT_TXQ_IN_FLAG_TCP_UDP_ONLY, 0,
			      INIT_TXQ_IN_CRC_MODE, 0,
			      INIT_TXQ_IN_FLAG_TIMESTAMP, 0);

	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_OWNER_ID, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_TXQ_IN_PORT_ID,
		       EVB_PORT_ID_ASSIGNED);

	addr = (efx_qword_t *) MCDI_PTR(req.emr_in_buf, INIT_TXQ_IN_DMA_ADDR);

	EFX_POPULATE_QWORD_2(*addr,
			     EFX_DWORD_1, (uint32_t)(dma_addr >> 32),
			     EFX_DWORD_0, (uint32_t)(dma_addr & 0xffffffff));

	return hunt_mcdi(hunt, &req);
}

static void hunt_tx_fini(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_FINI_TXQ_IN_LEN);
	struct efx_nic *efx = &hunt->efx;
	struct efx_tx_queue *txq = &efx->txq;
	int rc;

	req.emr_cmd = MC_CMD_FINI_TXQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, FINI_TXQ_IN_INSTANCE, 0);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		DBGC(hunt, "MC_CMD_FINI_TXQ Failed\n");

	efx_hunt_free_special_buffer(txq->ring,
				     sizeof(efx_tx_desc_t) * EFX_TXD_SIZE);
	txq->ring = NULL;
}

/*******************************************************************************
 *
 *
 * RX
 *
 *
 ******************************************************************************/
static int hunt_rx_filter_insert(struct net_device *netdev,
				 struct hunt_nic *hunt,
				 int multicast)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_FILTER_OP_IN_LEN);
	MCDI_DECLARE_BUF(outbuf, MC_CMD_FILTER_OP_OUT_LEN);
	int rc;
	uint64_t filter_id;
	(void) netdev;

	req.emr_cmd = MC_CMD_FILTER_OP;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_OP,
		       multicast ? MC_CMD_FILTER_OP_IN_OP_SUBSCRIBE
				 : MC_CMD_FILTER_OP_IN_OP_INSERT);
	MCDI_POPULATE_DWORD_1(req.emr_in_buf, FILTER_OP_IN_MATCH_FIELDS,
			      FILTER_OP_IN_MATCH_DST_MAC, 1);
	if (multicast)
		memset(MCDI_PTR(req.emr_in_buf, FILTER_OP_IN_DST_MAC),
		       0xff, ETH_ALEN);
	else
		memcpy(MCDI_PTR(req.emr_in_buf, FILTER_OP_IN_DST_MAC),
		       hunt->mac, ETH_ALEN);

	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_PORT_ID,
		       EVB_PORT_ID_ASSIGNED);
	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_RX_DEST,
			MC_CMD_FILTER_OP_IN_RX_DEST_HOST);
	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_RX_QUEUE, 0);
	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_RX_MODE, 0);
	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_TX_DEST,
		       MC_CMD_FILTER_OP_IN_TX_DEST_DEFAULT);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used <  MC_CMD_FILTER_OP_OUT_LEN)
		return -EIO;

	filter_id = MCDI_QWORD(req.emr_out_buf, FILTER_OP_OUT_HANDLE);
	if (multicast)
		hunt->mc_filter_id = filter_id;
	else
		hunt->uc_filter_id = filter_id;

	return 0;
}

static int hunt_rx_filter_remove(struct hunt_nic *hunt,
				 int multicast)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(inbuf, MC_CMD_FILTER_OP_IN_LEN);

	req.emr_cmd = MC_CMD_FILTER_OP;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, FILTER_OP_IN_OP,
		       multicast ? MC_CMD_FILTER_OP_IN_OP_UNSUBSCRIBE :
		       MC_CMD_FILTER_OP_IN_OP_REMOVE);
	MCDI_SET_QWORD(req.emr_in_buf, FILTER_OP_IN_HANDLE,
			  multicast ? hunt->mc_filter_id :
				      hunt->uc_filter_id);
	return hunt_mcdi(hunt, &req);
}

static int hunt_get_mac(struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_MAC_ADDRESSES_OUT_LEN);
	int rc;

	req.emr_cmd = MC_CMD_GET_MAC_ADDRESSES;
	req.emr_in_buf = NULL;
	req.emr_in_length = 0;
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_INIT_EVQ_OUT_LEN)
		return -EMSGSIZE;

	memcpy(hunt->mac, MCDI_PTR(outbuf, GET_MAC_ADDRESSES_OUT_MAC_ADDR_BASE),
	       ETH_ALEN);
	return 0;
}

static int hunt_rx_filter_init(struct net_device *netdev,
			       struct hunt_nic *hunt)
{
	int rc = hunt_get_mac(hunt);

	if (rc != 0)
		return rc;

	rc = hunt_rx_filter_insert(netdev, hunt, 0);
	if (rc != 0)
		return rc;

	rc = hunt_rx_filter_insert(netdev, hunt, 1);
	if (rc != 0)
		hunt_rx_filter_remove(hunt, 0);

	return rc;
}

static int
hunt_rx_init(struct net_device *netdev,
	     struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	dma_addr_t dma_addr;
	efx_qword_t *addr;
	MCDI_DECLARE_BUF(inbuf,
			 MC_CMD_INIT_RXQ_IN_LEN(EFX_RXQ_NBUFS(EFX_RXD_SIZE)));
	int rc, npages;

	rc = efx_hunt_rx_init(netdev, &dma_addr);
	if (rc != 0)
		return rc;

	npages = EFX_RXQ_NBUFS(EFX_RXD_SIZE);

	req.emr_cmd = MC_CMD_INIT_RXQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = MC_CMD_INIT_RXQ_IN_LEN(npages);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_SIZE, EFX_RXD_SIZE);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_TARGET_EVQ, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_LABEL, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_INSTANCE, 0);
	MCDI_POPULATE_DWORD_5(req.emr_in_buf, INIT_RXQ_IN_FLAGS,
				 INIT_RXQ_IN_FLAG_BUFF_MODE, 0,
				 INIT_RXQ_IN_FLAG_HDR_SPLIT, 0,
				 INIT_RXQ_IN_FLAG_TIMESTAMP, 0,
				 INIT_RXQ_IN_CRC_MODE, 0,
				 INIT_RXQ_IN_FLAG_PREFIX, 1);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_OWNER_ID, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_RXQ_IN_PORT_ID,
		       EVB_PORT_ID_ASSIGNED);

	addr = (efx_qword_t *) MCDI_PTR(req.emr_in_buf, INIT_RXQ_IN_DMA_ADDR);

	EFX_POPULATE_QWORD_2(*addr,
			     EFX_DWORD_1, (uint32_t)(dma_addr >> 32),
			     EFX_DWORD_0, (uint32_t)(dma_addr & 0xffffffff));
	return hunt_mcdi(hunt, &req);
}

static void hunt_rx_filter_fini(struct hunt_nic *hunt)
{
	hunt_rx_filter_remove(hunt, 0);
	hunt_rx_filter_remove(hunt, 1);
}

static void hunt_rx_fini(struct hunt_nic *hunt)
{
	MCDI_DECLARE_BUF(inbuf, MC_CMD_FINI_RXQ_IN_LEN);
	struct efx_mcdi_req_s req;
	struct efx_nic *efx = &hunt->efx;
	struct efx_rx_queue *rxq = &efx->rxq;
	int rc;

	req.emr_cmd = MC_CMD_FINI_RXQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = MC_CMD_FINI_RXQ_IN_LEN;
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, FINI_RXQ_IN_INSTANCE, 0);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		DBGC(hunt, "MC_CMD_FINI_RXQ Failed\n");

	efx_hunt_free_special_buffer(rxq->ring,
				     sizeof(efx_rx_desc_t) * EFX_RXD_SIZE);
	rxq->ring = NULL;
}

/*******************************************************************************
 *
 *
 * Event queues and interrupts
 *
 *
 ******************************************************************************/
static int
hunt_ev_init(struct net_device *netdev,
	     struct hunt_nic *hunt)
{
	struct efx_mcdi_req_s req;
	dma_addr_t dma_addr;
	efx_qword_t *addr;
	MCDI_DECLARE_BUF(inbuf,
			 MC_CMD_INIT_EVQ_IN_LEN(EFX_EVQ_NBUFS(EFX_EVQ_SIZE)));
	MCDI_DECLARE_BUF(outbuf, MC_CMD_INIT_EVQ_OUT_LEN);
	int rc, npages;

	rc = efx_hunt_ev_init(netdev, &dma_addr);
	if (rc != 0)
		return rc;

	npages = EFX_EVQ_NBUFS(EFX_EVQ_SIZE);

	req.emr_cmd = MC_CMD_INIT_EVQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = MC_CMD_INIT_EVQ_IN_LEN(npages);
	req.emr_out_buf = outbuf;
	req.emr_out_length = sizeof(outbuf);

	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_SIZE, EFX_EVQ_SIZE);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_INSTANCE, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_IRQ_NUM, 0);

	MCDI_POPULATE_DWORD_6(req.emr_in_buf, INIT_EVQ_IN_FLAGS,
				 INIT_EVQ_IN_FLAG_INTERRUPTING, 1,
				 INIT_EVQ_IN_FLAG_RPTR_DOS, 0,
				 INIT_EVQ_IN_FLAG_INT_ARMD, 0,
				 INIT_EVQ_IN_FLAG_CUT_THRU, 0,
				 INIT_EVQ_IN_FLAG_RX_MERGE, 0,
				 INIT_EVQ_IN_FLAG_TX_MERGE, 0);

	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_TMR_MODE,
			  MC_CMD_INIT_EVQ_IN_TMR_MODE_DIS);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_TMR_LOAD, 0);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_TMR_RELOAD, 0);

	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_COUNT_MODE,
			  MC_CMD_INIT_EVQ_IN_COUNT_MODE_DIS);
	MCDI_SET_DWORD(req.emr_in_buf, INIT_EVQ_IN_COUNT_THRSHLD, 0);

	addr = (efx_qword_t *) MCDI_PTR(req.emr_in_buf, INIT_EVQ_IN_DMA_ADDR);

	EFX_POPULATE_QWORD_2(*addr,
			     EFX_DWORD_1, (uint32_t)(dma_addr >> 32),
			     EFX_DWORD_0, (uint32_t)(dma_addr & 0xffffffff));
	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		return rc;

	if (req.emr_out_length_used < MC_CMD_INIT_EVQ_OUT_LEN)
		return -EMSGSIZE;

	return 0;
}

static void hunt_ev_fini(struct hunt_nic *hunt)
{
	MCDI_DECLARE_BUF(inbuf, MC_CMD_FINI_EVQ_IN_LEN);
	struct efx_mcdi_req_s req;
	struct efx_nic *efx = &hunt->efx;
	struct efx_ev_queue *evq = &efx->evq;
	int rc;

	req.emr_cmd = MC_CMD_FINI_EVQ;
	req.emr_in_buf = inbuf;
	req.emr_in_length = sizeof(inbuf);
	req.emr_out_buf = NULL;
	req.emr_out_length = 0;

	MCDI_SET_DWORD(req.emr_in_buf, FINI_EVQ_IN_INSTANCE, 0);

	rc = hunt_mcdi(hunt, &req);
	if (rc != 0)
		DBGC(hunt, "MC_CMD_FINI_EVQ Failed\n");

	efx_hunt_free_special_buffer(evq->ring,
				     sizeof(efx_event_t) * EFX_EVQ_SIZE);
	evq->ring = NULL;
}

static void
hunt_poll(struct net_device *netdev)
{
	struct hunt_nic *hunt = netdev->priv;

	/* If called while already polling, return immediately */
	if (hunt->efx.state & EFX_STATE_POLLING)
		return;
	hunt->efx.state |= EFX_STATE_POLLING;

	/* Poll link state */
	if (hunt->link_poll_timer + TICKS_PER_SEC < currticks()) {
		hunt->link_poll_timer = currticks();
		hunt_check_link(hunt);
	}

	/* Poll data path */
	efx_hunt_poll(netdev);

	hunt->efx.state &= ~EFX_STATE_POLLING;
}

/*******************************************************************************
 *
 *
 * Netdevice operations
 *
 *
 ******************************************************************************/
static int hunt_open(struct net_device *netdev)
{
	struct hunt_nic *hunt = netdev->priv;
	int rc;

	/* Allocate VIs */
	rc = hunt_alloc_vis(hunt);
	if (rc != 0)
		goto fail2;

	/* Initialize data path */
	rc = hunt_ev_init(netdev, hunt);
	if (rc != 0)
		goto fail3;

	rc = hunt_rx_init(netdev, hunt);
	if (rc != 0)
		goto fail4;

	rc = hunt_rx_filter_init(netdev, hunt);
	if (rc != 0)
		goto fail5;

	rc = hunt_tx_init(netdev, hunt);
	if (rc != 0)
		goto fail6;

	rc = efx_hunt_open(netdev);
	if (rc)
		goto fail7;

	rc = hunt_set_mac(hunt);
	if (rc)
		goto fail8;

	/* Mark the link as down before checking the link state because the
	 * latter might fail.
	 */
	netdev_link_down(netdev);
	hunt_check_link(hunt);

	DBGC2(hunt, "%s: open ok\n", netdev->name);
	return 0;

fail8:
	efx_hunt_close(netdev);
fail7:
	hunt_tx_fini(hunt);
fail6:
	hunt_rx_filter_fini(hunt);
fail5:
	hunt_rx_fini(hunt);
fail4:
	hunt_ev_fini(hunt);
fail3:
	hunt_free_vis(hunt);
fail2:
	DBGC2(hunt, "%s: %s\n", netdev->name, strerror(rc));
	return rc;
}


static void hunt_close(struct net_device *netdev)
{
	struct hunt_nic *hunt = netdev->priv;

	/* Stop datapath */
	efx_hunt_close(netdev);

	hunt_tx_fini(hunt);
	hunt_rx_fini(hunt);
	hunt_rx_filter_fini(hunt);
	hunt_ev_fini(hunt);

	hunt_free_vis(hunt);

	/* Reset hardware and detach */
	hunt_reset(hunt);
}


/*******************************************************************************
 *
 *
 * Public operations
 *
 *
 ******************************************************************************/

static struct net_device_operations hunt_operations = {
	.open                  = hunt_open,
	.close                 = hunt_close,
	.transmit              = efx_hunt_transmit,
	.poll                  = hunt_poll,
	.irq                   = efx_hunt_irq,
};

static int
hunt_probe(struct pci_device *pci)
{
	struct net_device *netdev;
	struct hunt_nic *hunt;
	struct efx_nic *efx;
	int rc = 0;

	/* Create the network adapter */
	netdev = alloc_etherdev(sizeof(struct hunt_nic));
	if (!netdev) {
		rc = -ENOMEM;
		goto fail1;
	}

	/* Initialise the network adapter, and initialise private storage */
	netdev_init(netdev, &hunt_operations);
	pci_set_drvdata(pci, netdev);
	netdev->dev = &pci->dev;
	netdev->state |= NETDEV_IRQ_UNSUPPORTED;

	hunt = netdev->priv;
	memset(hunt, 0, sizeof(*hunt));
	efx = &hunt->efx;

	efx->type = &hunt_nic_type;

	/* Initialise efx datapath */
	efx_probe(netdev, EFX_HUNTINGTON);

	/* Initialise MCDI.  In case we are recovering from a crash, first
	 * cancel any outstanding request by sending a special message using the
	 * least significant bits of the 'high' (doorbell) register.
	 */
	_efx_writel(efx, cpu_to_le32(1), ER_DZ_MC_DB_HWRD);
	rc = hunt_mcdi_init(hunt);
	if (rc != 0)
		goto fail2;

	/* Reset (most) configuration for this function */
	rc = hunt_reset(hunt);
	if (rc != 0)
		goto fail3;

	/* Medford has a list of UDP tunnel ports that is populated by the
	 * driver. Avoid dropping any unencapsulated packets. This may cause
	 * an MC reboot.
	 */
	hunt_clear_udp_tunnel_ports(hunt);

	/* Enable the workaround for bug35388, if supported */
	efx->workaround_35388 = hunt_workaround_35388(hunt);

	/* Set the RX packet prefix size */
	efx->rx_prefix_size = ES_DZ_RX_PREFIX_SIZE;

	rc = hunt_get_port_assignment(hunt);
	if (rc != 0)
		goto fail3;

	rc = hunt_mac_addr(hunt, netdev->ll_addr);
	if (rc != 0)
		goto fail4;

	rc = hunt_get_phy_cfg(hunt);
	if (rc != 0)
		goto fail5;

	rc = hunt_driver_attach(hunt, 1);
	if (rc != 0)
		goto fail5;

	/* If not exposing this network device, return successfully here */
	if (hunt->flags & (1 << MC_CMD_DRV_ATTACH_EXT_OUT_FLAG_NO_ACTIVE_PORT))
		return 0;

	if (hunt_nic_is_primary(hunt)) {
		hunt->next_primary = primary_nics;
		primary_nics = hunt;
		hunt->primary = hunt;
	} else {
		struct hunt_nic *other_hunt = primary_nics;

		while (other_hunt && !hunt->primary) {
			struct pci_device *other_pci = (struct pci_device *)
				other_hunt->efx.netdev->dev;
			/* Check if the seg:bus:dev parts match. */
			if (PCI_FIRST_FUNC(other_pci->busdevfn) ==
			    PCI_FIRST_FUNC(pci->busdevfn))
				hunt->primary = other_hunt;

			other_hunt = other_hunt->next_primary;
		}
		if (!hunt->primary) {
			rc = -EIO;
			goto fail6;
		}
	}

	rc = register_netdev(netdev);
	if (rc != 0)
		goto fail8;

	DBG2("%s " PCI_FMT " ok\n", __func__, PCI_ARGS(pci));
	return 0;

fail8:
fail6:
	(void) hunt_driver_attach(hunt, 0);
fail5:
fail4:
fail3:
	hunt_mcdi_fini(hunt);
fail2:
	efx_remove(netdev);
	netdev_put(netdev);
fail1:
	DBG2("%s " PCI_FMT " rc=%d\n", __func__, PCI_ARGS(pci), rc);
	return rc;
}

static void hunt_remove(struct pci_device *pci)
{
	struct net_device *netdev = pci_get_drvdata(pci);
	struct hunt_nic *hunt = netdev->priv;

	if (!(hunt->flags &
	      (1 << MC_CMD_DRV_ATTACH_EXT_OUT_FLAG_NO_ACTIVE_PORT))) {
		/* The netdevice might still be open, so unregister it now
		 * before ripping stuff out from underneath.
		 */
		unregister_netdev(netdev);
	}

	(void)hunt_driver_attach(hunt, 0);
	hunt_mcdi_fini(hunt);

	/* Destroy data path */
	efx_remove(netdev);

	netdev_nullify(netdev);
	netdev_put(netdev);
}

const struct efx_nic_type hunt_nic_type = {
	.mcdi_rpc = _hunt_mcdi,
};

static struct pci_device_id hunt_nics[] = {
	PCI_ROM(0x1924, 0x0903, "SFC9120", "Solarflare SFC9120 Adapter", 0),
	PCI_ROM(0x1924, 0x0923, "SFC9140", "Solarflare SFC9140 Adapter", 0),
	PCI_ROM(0x1924, 0x0a03, "SFC9220", "Solarflare SFN8xxx Adapter", 0),
	PCI_ROM(0x1924, 0x0b03, "SFC9250", "Solarflare X25xx Adapter", 0),
};

struct pci_driver hunt_driver __pci_driver = {
	.ids = hunt_nics,
	.id_count = ARRAY_SIZE(hunt_nics),
	.probe = hunt_probe,
	.remove = hunt_remove,
};
