/*
 * Copyright (c) 2012 Qualcomm Atheros, Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_vlan.h>
#include <linux/mii.h>
#include <linux/mdio.h>
#include <linux/aer.h>
#include <linux/bitops.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

#include "alx_reg.h"
#include "alx_hw.h"
#include "alx.h"

#define DRV_MAJ		1
#define DRV_MIN		2
#define DRV_PATCH	2
#define DRV_MODULE_VER	\
	__stringify(DRV_MAJ) "." __stringify(DRV_MIN) "." \
	__stringify(DRV_PATCH)

char alx_drv_name[] = "alx";
char alx_drv_version[] = DRV_MODULE_VER;
static const char alx_drv_desc[] =
"Qualcomm Atheros(R) AR816x/AR817x PCI-E Ethernet Network Driver";

/* alx_pci_tbl - PCI Device ID Table
 *
 * Wildcard entries (PCI_ANY_ID) should come last
 * Last entry must be all 0s
 *
 * { Vendor ID, Device ID, SubVendor ID, SubDevice ID,
 *   Class, Class Mask, private data (not used) }
 */
#define ALX_ETHER_DEVICE(device_id) {\
	PCI_DEVICE(ALX_VENDOR_ID, device_id)}
static DEFINE_PCI_DEVICE_TABLE(alx_pci_tbl) = {
	ALX_ETHER_DEVICE(ALX_DEV_ID_AR8161),
	ALX_ETHER_DEVICE(ALX_DEV_ID_AR8162),
	ALX_ETHER_DEVICE(ALX_DEV_ID_AR8171),
	ALX_ETHER_DEVICE(ALX_DEV_ID_AR8172),
	{0,}
};

MODULE_DEVICE_TABLE(pci, alx_pci_tbl);
MODULE_AUTHOR("Qualcomm Corporation, <nic-devel@qualcomm.com>");
MODULE_DESCRIPTION("Qualcomm Atheros Gigabit Ethernet Driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION(DRV_MODULE_VER);

static int alx_poll(struct napi_struct *napi, int budget);
static irqreturn_t alx_msix_ring(int irq, void *data);
static irqreturn_t alx_intr_msix_misc(int irq, void *data);
static irqreturn_t alx_intr_msi(int irq, void *data);
static irqreturn_t alx_intr_legacy(int irq, void *data);
static void alx_init_ring_ptrs(struct alx_adapter *adpt);
static int alx_reinit_rings(struct alx_adapter *adpt);

static inline void alx_schedule_work(struct alx_adapter *adpt)
{
	if (!ALX_FLAG(adpt, HALT))
		schedule_work(&adpt->task);
}

static inline void alx_cancel_work(struct alx_adapter *adpt)
{
	cancel_work_sync(&adpt->task);
}


static void __alx_set_rx_mode(struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	struct alx_hw *hw = &adpt->hw;
	struct netdev_hw_addr *ha;


	/* comoute mc addresses' hash value ,and put it into hash table */
	netdev_for_each_mc_addr(ha, netdev)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35))
		alx_add_mc_addr(hw, ha->addr);
#else
		alx_add_mc_addr(hw, ha->dmi_addr);
#endif

	ALX_MEM_W32(hw, ALX_HASH_TBL0, hw->mc_hash[0]);
	ALX_MEM_W32(hw, ALX_HASH_TBL1, hw->mc_hash[1]);

	/* check for Promiscuous and All Multicast modes */
	hw->rx_ctrl &= ~(ALX_MAC_CTRL_MULTIALL_EN | ALX_MAC_CTRL_PROMISC_EN);
	if (netdev->flags & IFF_PROMISC)
		hw->rx_ctrl |= ALX_MAC_CTRL_PROMISC_EN;
	if (netdev->flags & IFF_ALLMULTI)
		hw->rx_ctrl |= ALX_MAC_CTRL_MULTIALL_EN;

	ALX_MEM_W32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
}

/* alx_set_rx_mode - Multicast and Promiscuous mode set */
static void alx_set_rx_mode(struct net_device *netdev)
{
	__alx_set_rx_mode(netdev);
}


/* alx_set_mac - Change the Ethernet Address of the NIC */
static int alx_set_mac_address(struct net_device *netdev, void *data)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	struct alx_hw *hw = &adpt->hw;
	struct sockaddr *addr = data;

	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36))
	if (netdev->addr_assign_type & NET_ADDR_RANDOM)
		netdev->addr_assign_type ^= NET_ADDR_RANDOM;
#endif

	memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
	memcpy(hw->mac_addr, addr->sa_data, netdev->addr_len);
	alx_set_macaddr(hw, hw->mac_addr);

	return 0;
}

static void alx_free_napis(struct alx_adapter *adpt)
{
	struct alx_napi *np;
	int i;

	for (i = 0; i < adpt->nr_napi; i++) {
		np = adpt->qnapi[i];
		if (!np)
			continue;

		netif_napi_del(&np->napi);
		kfree(np->txq);
		np->txq = NULL;
		kfree(np->rxq);
		np->rxq = NULL;
		adpt->qnapi[i] = NULL;
	}
}
static u16 tx_pidx_reg[] = {ALX_TPD_PRI0_PIDX, ALX_TPD_PRI1_PIDX,
			    ALX_TPD_PRI2_PIDX, ALX_TPD_PRI3_PIDX};
static u16 tx_cidx_reg[] = {ALX_TPD_PRI0_CIDX, ALX_TPD_PRI1_CIDX,
			    ALX_TPD_PRI2_CIDX, ALX_TPD_PRI3_CIDX};
static u32 tx_vect_mask[] = {ALX_ISR_TX_Q0, ALX_ISR_TX_Q1,
			     ALX_ISR_TX_Q2, ALX_ISR_TX_Q3};
static u32 rx_vect_mask[] = {ALX_ISR_RX_Q0, ALX_ISR_RX_Q1,
			     ALX_ISR_RX_Q2, ALX_ISR_RX_Q3,
			     ALX_ISR_RX_Q4, ALX_ISR_RX_Q5,
			     ALX_ISR_RX_Q6, ALX_ISR_RX_Q7};
static int alx_alloc_napis(struct alx_adapter *adpt)
{
	struct alx_hw		*hw = &adpt->hw;
	struct alx_napi		*np;
	struct alx_rx_queue	*rxq;
	struct alx_tx_queue	*txq;
	int i;

	hw->imask &= ~ALX_ISR_ALL_QUEUES;

	/* alloc alx_napi */
	for (i = 0; i < adpt->nr_napi; i++) {
		np = kzalloc(sizeof(struct alx_napi), GFP_KERNEL);
		if (!np)
			goto err_out;

		np->adpt = adpt;
		netif_napi_add(adpt->netdev, &np->napi, alx_poll, 64);
		adpt->qnapi[i] = np;
	}

	/* alloc tx queue */
	for (i = 0; i < adpt->nr_txq; i++) {
		np = adpt->qnapi[i];
		txq = kzalloc(sizeof(struct alx_tx_queue), GFP_KERNEL);
		if (!txq)
			goto err_out;
		np->txq = txq;
		txq->p_reg = tx_pidx_reg[i];
		txq->c_reg = tx_cidx_reg[i];
		txq->count = adpt->tx_ringsz;
		txq->qidx = (u16)i;
		np->vec_mask |= tx_vect_mask[i];
		hw->imask |= tx_vect_mask[i];
	}

	/* alloc rx queue */
	for (i = 0; i < adpt->nr_rxq; i++) {
		np = adpt->qnapi[i];
		rxq = kzalloc(sizeof(struct alx_rx_queue), GFP_KERNEL);
		if (!rxq)
			goto err_out;
		np->rxq = rxq;
		rxq->p_reg = ALX_RFD_PIDX;
		rxq->c_reg = ALX_RFD_CIDX;
		rxq->count = adpt->rx_ringsz;
		rxq->qidx = (u16)i;
		__skb_queue_head_init(&rxq->list);
		np->vec_mask |= rx_vect_mask[i];
		hw->imask |= rx_vect_mask[i];
	}

	return 0;

err_out:
	alx_free_napis(adpt);
	return -ENOMEM;
}

static int alx_alloc_rings(struct alx_adapter *adpt)
{
	struct alx_buffer *bf;
	u8 *desc;
	dma_addr_t  dma;
	int i, size, offset;

	/* alx_buffer */
	size = sizeof(struct alx_buffer) * adpt->nr_txq * adpt->tx_ringsz +
	       sizeof(struct alx_buffer) * adpt->nr_hwrxq * adpt->rx_ringsz;

	bf = vzalloc(size);
	if (!bf)
		goto err_out;

	/* physical rx rings */
	size = sizeof(struct tpd_desc) * adpt->tx_ringsz * adpt->nr_txq +
	       (sizeof(struct rrd_desc) + sizeof(struct rfd_desc)) *
	       adpt->rx_ringsz * adpt->nr_hwrxq +
	       adpt->nr_txq * 8 +
	       adpt->nr_hwrxq * 8;
	desc = dma_alloc_coherent(&adpt->pdev->dev, size, &dma, GFP_KERNEL);
	if (!desc)
		goto err_out;

	memset(desc, 0, size);
	adpt->ring_header.desc = desc;
	adpt->ring_header.dma = dma;
	adpt->ring_header.size = size;

	size = sizeof(struct tpd_desc) * adpt->tx_ringsz;
	for (i = 0; i < adpt->nr_txq; i++) {
		offset = ALIGN(dma, 8) - dma;
		desc += offset;
		dma += offset;
		adpt->qnapi[i]->txq->netdev = adpt->netdev;
		adpt->qnapi[i]->txq->dev = &adpt->pdev->dev;
		adpt->qnapi[i]->txq->tpd_hdr = (struct tpd_desc *)desc;
		adpt->qnapi[i]->txq->tpd_dma = dma;
		adpt->qnapi[i]->txq->count = adpt->tx_ringsz;
		adpt->qnapi[i]->txq->bf_info = bf;
		desc += size;
		dma += size;
		bf += adpt->tx_ringsz;
	}
	size = sizeof(struct rrd_desc) * adpt->rx_ringsz;
	for (i = 0; i < adpt->nr_hwrxq; i++) {
		offset = ALIGN(dma, 8) - dma;
		desc += offset;
		dma += offset;
		adpt->qnapi[i]->rxq->rrd_hdr = (struct rrd_desc *)desc;
		adpt->qnapi[i]->rxq->rrd_dma = dma;
		adpt->qnapi[i]->rxq->bf_info = bf;
		desc += size;
		dma += size;
		bf += adpt->rx_ringsz;
	}
	size = sizeof(struct rfd_desc) * adpt->rx_ringsz;
	for (i = 0; i < adpt->nr_hwrxq; i++) {
		offset = ALIGN(dma, 8) - dma;
		desc += offset;
		dma += offset;
		adpt->qnapi[i]->rxq->rfd_hdr = (struct rfd_desc *)desc;
		adpt->qnapi[i]->rxq->rfd_dma = dma;
		desc += size;
		dma += size;
	}
	for (i = 0; i < adpt->nr_rxq; i++) {
		adpt->qnapi[i]->rxq->netdev = adpt->netdev;
		adpt->qnapi[i]->rxq->dev = &adpt->pdev->dev;
		adpt->qnapi[i]->rxq->count = adpt->rx_ringsz;
	}

	return 0;

err_out:
	if (bf)
		vfree(bf);

	return -ENOMEM;
}

static void alx_free_rings(struct alx_adapter *adpt)
{
	struct alx_buffer *bf;
	struct alx_napi *np;

	/* alx_buffer header is in the 1st tpdq->bf_info */
	np = adpt->qnapi[0];
	if (np) {
		bf = np->txq->bf_info;
		if (bf) {
			vfree(bf);
			np->txq->bf_info = NULL;
		}
	}
	if (adpt->ring_header.desc) {
		dma_free_coherent(&adpt->pdev->dev,
				  adpt->ring_header.size,
				  adpt->ring_header.desc,
				  adpt->ring_header.dma);
		adpt->ring_header.desc = NULL;
	}
}

/* dequeue skb from RXQ, return true if the RXQ is empty */
static inline bool alx_skb_dequeue_n(struct alx_rx_queue *rxq, int max_pkts,
				     struct sk_buff_head *list)
{
	struct alx_adapter *adpt = netdev_priv(rxq->netdev);
	bool use_lock = !ALX_CAP(&adpt->hw, MRQ);
	bool empty;
	struct sk_buff *skb;
	int count = 0;

	if (use_lock)
		spin_lock(&rxq->list.lock);

	while (count < max_pkts || max_pkts == -1) {
		skb = __skb_dequeue(&rxq->list);
		if (skb) {
			__skb_queue_tail(list, skb);
			count++;
		} else
			break;
	}

	empty = skb_queue_empty(&rxq->list);

	if (use_lock)
		spin_unlock(&rxq->list.lock);

	netif_info(adpt, rx_status, adpt->netdev,
		   "RX %d packets\n",
		   count);

	return empty;
}

static inline void alx_skb_queue_tail(struct alx_rx_queue *rxq,
				      struct sk_buff *skb)
{
	struct alx_adapter *adpt = netdev_priv(rxq->netdev);
	bool use_lock = !ALX_CAP(&adpt->hw, MRQ);

	if (use_lock)
		spin_lock(&rxq->list.lock);

	__skb_queue_tail(&rxq->list, skb);

	if (use_lock)
		spin_unlock(&rxq->list.lock);
}

int alx_alloc_rxring_buf(struct alx_adapter *adpt,
			 struct alx_rx_queue *rxq)
{
	struct sk_buff *skb;
	struct alx_buffer *cur_buf;
	struct rfd_desc *rfd;
	dma_addr_t dma;
	u16 cur, next, count = 0;

	next = cur = rxq->pidx;
	if (++next == rxq->count)
		next = 0;
	cur_buf = rxq->bf_info + cur;
	rfd = rxq->rfd_hdr + cur;

	while (!cur_buf->skb && next != rxq->cidx) {
		skb = dev_alloc_skb(adpt->rxbuf_size);
		if (unlikely(!skb)) {
			netdev_warn(adpt->netdev, "alloc skb failed\n");
			break;
		}
		skb_reserve(skb, NET_IP_ALIGN);
		dma = dma_map_single(rxq->dev,
				     skb->data,
				     adpt->rxbuf_size,
				     DMA_FROM_DEVICE);
		if (dma_mapping_error(rxq->dev, dma)) {
			netdev_warn(adpt->netdev, "mapping rx-buffer failed\n");
			dev_kfree_skb(skb);
			break;
		}
		cur_buf->skb = skb;
		dma_unmap_len_set(cur_buf, size, adpt->rxbuf_size);
		dma_unmap_addr_set(cur_buf, dma, dma);
		rfd->addr = cpu_to_le64(dma);

		cur = next;
		if (++next == rxq->count)
			next = 0;
		cur_buf = rxq->bf_info + cur;
		rfd = rxq->rfd_hdr + cur;
		count++;
	}

	if (count) {
		wmb();
		rxq->pidx = cur;
		ALX_MEM_W16(&adpt->hw, rxq->p_reg, (u16)cur);
	}

	return count;
}

static void alx_free_rxring_buf(struct alx_rx_queue *rxq)
{
	struct alx_buffer *cur_buf;
	struct sk_buff_head list;
	u16 i;

	if (rxq == NULL)
		return;

	for (i = 0; i < rxq->count; i++) {
		cur_buf = rxq->bf_info + i;
		if (cur_buf->skb) {
			dma_unmap_single(rxq->dev,
					 dma_unmap_addr(cur_buf, dma),
					 dma_unmap_len(cur_buf, size),
					 DMA_FROM_DEVICE);
			dev_kfree_skb(cur_buf->skb);
			cur_buf->skb = NULL;
			dma_unmap_len_set(cur_buf, size, 0);
			dma_unmap_addr_set(cur_buf, dma, 0);
		}
	}

	/* some skbs might be pending in the list */
	__skb_queue_head_init(&list);
	alx_skb_dequeue_n(rxq, -1, &list);
	while (!skb_queue_empty(&list)) {
		struct sk_buff *skb;

		skb = __skb_dequeue(&list);
		dev_kfree_skb(skb);
	}

	rxq->pidx = 0;
	rxq->cidx = 0;
	rxq->rrd_cidx = 0;
}

int alx_setup_all_ring_resources(struct alx_adapter *adpt)
{
	int err;

	err = alx_alloc_napis(adpt);
	if (err)
		goto out;

	err = alx_alloc_rings(adpt);
	if (err)
		goto out;

	err = alx_reinit_rings(adpt);

out:
	if (unlikely(err)) {
		netif_err(adpt, ifup, adpt->netdev,
			  "setup_all_ring_resources fail %d\n",
			  err);
	}
	return err;
}

static void alx_txbuf_unmap_and_free(struct alx_tx_queue *txq, int entry)
{
	struct alx_buffer *txb = txq->bf_info + entry;

	if (dma_unmap_len(txb, size) &&
	    txb->flags & ALX_BUF_TX_FIRSTFRAG) {
		dma_unmap_single(txq->dev,
				 dma_unmap_addr(txb, dma),
				 dma_unmap_len(txb, size),
				 DMA_TO_DEVICE);
		txb->flags &= ~ALX_BUF_TX_FIRSTFRAG;
	} else if (dma_unmap_len(txb, size)) {
		dma_unmap_page(txq->dev,
			       dma_unmap_addr(txb, dma),
			       dma_unmap_len(txb, size),
			       DMA_TO_DEVICE);
	}
	if (txb->skb) {
		dev_kfree_skb_any(txb->skb);
		txb->skb = NULL;
	}
	dma_unmap_len_set(txb, size, 0);
}

static void alx_free_txring_buf(struct alx_tx_queue *txq)
{
	int i;

	if (!txq->bf_info)
		return;

	for (i = 0; i < txq->count; i++)
		alx_txbuf_unmap_and_free(txq, i);

	memset(txq->bf_info, 0, txq->count * sizeof(struct alx_buffer));
	memset(txq->tpd_hdr, 0, txq->count * sizeof(struct tpd_desc));
	txq->pidx = 0;
	atomic_set(&txq->cidx, 0);

	netdev_tx_reset_queue(netdev_get_tx_queue(txq->netdev, txq->qidx));
}

/* free up pending skb for tx/rx */
static void alx_free_all_rings_buf(struct alx_adapter *adpt)
{
	int i;

	for (i = 0; i < adpt->nr_txq; i++)
		if (adpt->qnapi[i])
			alx_free_txring_buf(adpt->qnapi[i]->txq);

	for (i = 0; i < adpt->nr_hwrxq; i++)
		if (adpt->qnapi[i])
			alx_free_rxring_buf(adpt->qnapi[i]->rxq);
}

void alx_free_all_ring_resources(struct alx_adapter *adpt)
{
	alx_free_all_rings_buf(adpt);
	alx_free_rings(adpt);
	alx_free_napis(adpt);
}

static inline int alx_tpd_avail(struct alx_tx_queue *txq)
{
	u16 cidx = atomic_read(&txq->cidx);

	return txq->pidx >= cidx ?
		txq->count + cidx - txq->pidx - 1 :
		cidx - txq->pidx - 1;
}



static bool alx_clean_tx_irq(struct alx_tx_queue *txq)
{
	struct alx_adapter *adpt = netdev_priv(txq->netdev);
	struct netdev_queue *netque;
	u16 hw_cidx, sw_cidx;
	unsigned int total_bytes = 0, total_packets = 0;
	int budget = ALX_DEFAULT_TX_WORK;

	if (ALX_FLAG(adpt, HALT))
		return true;

	netque = netdev_get_tx_queue(adpt->netdev, txq->qidx);
	sw_cidx = atomic_read(&txq->cidx);

	ALX_MEM_R16(&adpt->hw, txq->c_reg, &hw_cidx);

	if (sw_cidx != hw_cidx) {

		netif_info(adpt, tx_done, adpt->netdev,
			   "TX[Q:%d, Preg:%x]: cons = 0x%x, hw-cons = 0x%x\n",
			   txq->qidx, txq->p_reg, sw_cidx, hw_cidx);

		while (sw_cidx != hw_cidx && budget > 0) {
			struct sk_buff *skb;

			skb = txq->bf_info[sw_cidx].skb;
			if (skb) {
				total_bytes += skb->len;
				total_packets++;
				budget--;
			}
			alx_txbuf_unmap_and_free(txq, sw_cidx);
			if (++sw_cidx == txq->count)
				sw_cidx = 0;
		}
		atomic_set(&txq->cidx, sw_cidx);

		netdev_tx_completed_queue(netque, total_packets, total_bytes);
	}

	if (unlikely(netif_tx_queue_stopped(netque) &&
		     netif_carrier_ok(adpt->netdev) &&
		     alx_tpd_avail(txq) > ALX_TX_WAKEUP_THRESH(txq) &&
		     !ALX_FLAG(adpt, HALT))) {
		netif_tx_wake_queue(netque);
	}

	return sw_cidx == hw_cidx;
}

static bool alx_dispatch_skb(struct alx_rx_queue *rxq)
{
	struct alx_adapter *adpt = netdev_priv(rxq->netdev);
	struct rrd_desc *rrd;
	struct alx_buffer *rxb;
	struct sk_buff *skb;
	u16 length, rfd_cleaned = 0;
	struct alx_rx_queue *tmp_rxq;
	int qnum;

	if (test_and_set_bit(ALX_RQ_USING, &rxq->flag))
		return false;

	while (1) {
		rrd = rxq->rrd_hdr + rxq->rrd_cidx;
		if (!(rrd->word3 & (1 << RRD_UPDATED_SHIFT)))
			break;
		rrd->word3 &= ~(1 << RRD_UPDATED_SHIFT);

		if (unlikely(FIELD_GETX(rrd->word0, RRD_SI) != rxq->cidx ||
			     FIELD_GETX(rrd->word0, RRD_NOR) != 1)) {
			netif_err(adpt, rx_err, adpt->netdev,
				  "wrong SI/NOR packet! rrd->word0= %08x\n",
				  rrd->word0);
			/* reset chip */
			ALX_FLAG_SET(adpt, TASK_RESET);
			alx_schedule_work(adpt);
			return true;
		}
		rxb = rxq->bf_info + rxq->cidx;
		dma_unmap_single(rxq->dev,
				 dma_unmap_addr(rxb, dma),
				 dma_unmap_len(rxb, size),
				 DMA_FROM_DEVICE);
		dma_unmap_len_set(rxb, size, 0);
		skb = rxb->skb;
		rxb->skb = NULL;

		if (unlikely(rrd->word3 & (1 << RRD_ERR_RES_SHIFT) ||
			     rrd->word3 & (1 << RRD_ERR_LEN_SHIFT))) {
			netdev_warn(adpt->netdev,
				   "wrong packet! rrd->word3 is %08x\n",
				   rrd->word3);
			rrd->word3 = 0;
			dev_kfree_skb_any(skb);
			goto next_pkt;
		}
		length = FIELD_GETX(rrd->word3, RRD_PKTLEN) - ETH_FCS_LEN;
		skb_put(skb, length);
		skb->protocol = eth_type_trans(skb, adpt->netdev);
		/* checksum */
		skb_checksum_none_assert(skb);
		if (adpt->netdev->features & NETIF_F_RXCSUM) {
			switch (FIELD_GETX(rrd->word2, RRD_PID)) {
			case RRD_PID_IPV6UDP:
			case RRD_PID_IPV4UDP:
			case RRD_PID_IPV4TCP:
			case RRD_PID_IPV6TCP:
				if (rrd->word3 & ((1 << RRD_ERR_L4_SHIFT) |
						  (1 << RRD_ERR_IPV4_SHIFT))) {
					netdev_warn(
						adpt->netdev,
						"rx-chksum error, w2=%X\n",
						rrd->word2);
					break;
				}
				skb->ip_summed = CHECKSUM_UNNECESSARY;
				break;
			}
		}
		/* vlan tag */
		if (rrd->word3 & (1 << RRD_VLTAGGED_SHIFT)) {
			u16 tag = ntohs(FIELD_GETX(rrd->word2, RRD_VLTAG));
			__vlan_hwaccel_put_tag(skb, ntohs(tag));
		}
		qnum = FIELD_GETX(rrd->word2, RRD_RSSQ) % adpt->nr_rxq;
		tmp_rxq = ALX_CAP(&adpt->hw, MRQ) ?
				rxq : adpt->qnapi[qnum]->rxq;
		alx_skb_queue_tail(tmp_rxq, skb);

next_pkt:

		if (++rxq->cidx == rxq->count)
			rxq->cidx = 0;
		if (++rxq->rrd_cidx == rxq->count)
			rxq->rrd_cidx = 0;

		if (++rfd_cleaned > ALX_RX_ALLOC_THRESH)
			rfd_cleaned -= alx_alloc_rxring_buf(adpt, rxq);
	}

	if (rfd_cleaned)
		alx_alloc_rxring_buf(adpt, rxq);

	clear_bit(ALX_RQ_USING, &rxq->flag);

	return true;
}

static inline struct napi_struct *alx_rxq_to_napi(
	struct alx_rx_queue *rxq)
{
	struct alx_adapter *adpt = netdev_priv(rxq->netdev);

	return &adpt->qnapi[rxq->qidx]->napi;
}

static bool alx_clean_rx_irq(struct alx_rx_queue *rxq, int budget)
{
	struct sk_buff_head list;
	bool empty;

	__skb_queue_head_init(&list);
	alx_dispatch_skb(alx_hw_rxq(rxq));
	empty = alx_skb_dequeue_n(rxq, budget, &list);
	if (!skb_queue_empty(&list)) {
		struct napi_struct *napi;
		struct sk_buff *skb;

		napi = alx_rxq_to_napi(rxq);
		while (!skb_queue_empty(&list)) {
			skb = __skb_dequeue(&list);
			napi_gro_receive(napi, skb);
		}
	} else {
		struct alx_adapter *adpt = netdev_priv(rxq->netdev);

		netif_info(adpt, rx_status, adpt->netdev,
			   "no packet received for this rxQ\n");
	}


	return empty;
}

static int alx_request_msix(struct alx_adapter *adpt)
{
	struct net_device *netdev = adpt->netdev;
	int i, err;
	int vec;

	err = request_irq(adpt->msix_ent[0].vector,
		alx_intr_msix_misc, 0, netdev->name, adpt);
	if (err)
		goto out;

	vec = 1;
	for (i = 0; i < adpt->nr_napi; i++) {
		struct alx_napi *np = adpt->qnapi[i];

		if (np->txq && np->rxq)
			sprintf(np->irq_lbl, "%s-TR-%u", netdev->name, i);
		else if (np->txq)
			sprintf(np->irq_lbl, "%s-T-%u", netdev->name, i);
		else
			sprintf(np->irq_lbl, "%s-R-%u", netdev->name, i);

		np->vec_idx = vec;
		err = request_irq(adpt->msix_ent[vec].vector,
			alx_msix_ring, 0, np->irq_lbl, np);
		if (err) {
			for (i--, vec--; i >= 0; i--) {
				np = adpt->qnapi[i];
				free_irq(adpt->msix_ent[vec].vector, np);
			}
			free_irq(adpt->msix_ent[0].vector, adpt);
			goto out;
		}
		vec++;
	}

out:
	return err;
}

static void alx_disable_msix(struct alx_adapter *adpt)
{
	if (adpt->msix_ent) {
		pci_disable_msix(adpt->pdev);
		kfree(adpt->msix_ent);
		adpt->msix_ent = NULL;
	}
	ALX_FLAG_CLEAR(adpt, USING_MSIX);
}

static void alx_disable_msi(struct alx_adapter *adpt)
{
	if (ALX_FLAG(adpt, USING_MSI)) {
		pci_disable_msi(adpt->pdev);
		ALX_FLAG_CLEAR(adpt, USING_MSI);
	}
}

static int txq_vec_mapping_shift[] = {
	0, ALX_MSI_MAP_TBL1_TXQ0_SHIFT,
	0, ALX_MSI_MAP_TBL1_TXQ1_SHIFT,
	1, ALX_MSI_MAP_TBL2_TXQ2_SHIFT,
	1, ALX_MSI_MAP_TBL2_TXQ3_SHIFT,
};
static int rxq_vec_mapping_shift[] = {
	0, ALX_MSI_MAP_TBL1_RXQ0_SHIFT,
	0, ALX_MSI_MAP_TBL1_RXQ1_SHIFT,
	0, ALX_MSI_MAP_TBL1_RXQ2_SHIFT,
	0, ALX_MSI_MAP_TBL1_RXQ3_SHIFT,
	1, ALX_MSI_MAP_TBL2_RXQ4_SHIFT,
	1, ALX_MSI_MAP_TBL2_RXQ5_SHIFT,
	1, ALX_MSI_MAP_TBL2_RXQ6_SHIFT,
	1, ALX_MSI_MAP_TBL2_RXQ7_SHIFT,
};
static void alx_config_vector_mapping(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	u32 tbl[2];
	int vect, idx, shft;
	int i;

	tbl[0] = tbl[1] = 0;

	if (ALX_FLAG(adpt, USING_MSIX)) {
		for (vect = 1, i = 0; i < adpt->nr_txq; i++, vect++) {
			idx = txq_vec_mapping_shift[i * 2];
			shft = txq_vec_mapping_shift[i * 2 + 1];
			tbl[idx] |= vect << shft;
		}
		for (vect = 1, i = 0; i < adpt->nr_rxq; i++, vect++) {
			idx = rxq_vec_mapping_shift[i * 2];
			shft = rxq_vec_mapping_shift[i * 2 + 1];
			tbl[idx] |= vect << shft;
		}
	}
	ALX_MEM_W32(hw, ALX_MSI_MAP_TBL1, tbl[0]);
	ALX_MEM_W32(hw, ALX_MSI_MAP_TBL2, tbl[1]);
	ALX_MEM_W32(hw, ALX_MSI_ID_MAP, 0);
}

void alx_disable_advanced_intr(struct alx_adapter *adpt)
{
	alx_disable_msix(adpt);
	alx_disable_msi(adpt);

	/* clear vector/intr-event mapping */
	alx_config_vector_mapping(adpt);
}

static void alx_irq_enable(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	int i;

	if (!atomic_dec_and_test(&adpt->irq_sem))
		return;

	/* level-1 interrupt switch */
	ALX_MEM_W32(hw, ALX_ISR, 0);
	ALX_MEM_W32(hw, ALX_IMR, hw->imask);
	ALX_MEM_FLUSH(hw);

	if (!ALX_FLAG(adpt, USING_MSIX))
		return;

	/* enable all individual MSIX IRQs */
	for (i = 0; i < adpt->nr_vec; i++)
		alx_mask_msix(hw, i, false);
}

static void alx_irq_disable(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	int i;

	atomic_inc(&adpt->irq_sem);

	ALX_MEM_W32(hw, ALX_ISR, ALX_ISR_DIS);
	ALX_MEM_W32(hw, ALX_IMR, 0);
	ALX_MEM_FLUSH(hw);

	if (ALX_FLAG(adpt, USING_MSIX)) {
		for (i = 0; i < adpt->nr_vec; i++) {
			alx_mask_msix(hw, i, true);
			synchronize_irq(adpt->msix_ent[i].vector);
		}
	} else {
		synchronize_irq(adpt->pdev->irq);
	}
}

static int alx_request_irq(struct alx_adapter *adpt)
{
	struct pci_dev *pdev = adpt->pdev;
	struct alx_hw *hw = &adpt->hw;
	int err;
	u32 msi_ctrl;

	msi_ctrl = FIELDX(ALX_MSI_RETRANS_TM, hw->imt >> 1);

	if (ALX_FLAG(adpt, USING_MSIX)) {
		ALX_MEM_W32(hw, ALX_MSI_RETRANS_TIMER, msi_ctrl);
		err = alx_request_msix(adpt);
		if (!err)
			goto out;
		/* fall back to MSI or legacy interrupt mode,
		 * re-alloc all resources
		 */
		alx_free_all_ring_resources(adpt);
		alx_disable_msix(adpt);
		adpt->nr_rxq = 1;
		adpt->nr_txq = 1;
		adpt->nr_napi = 1;
		adpt->nr_vec = 1;
		adpt->nr_hwrxq = 1;
		alx_configure_rss(hw, false);
		if (!pci_enable_msi(pdev))
			ALX_FLAG_SET(adpt, USING_MSI);

		err = alx_setup_all_ring_resources(adpt);
		if (err)
			goto out;
	}

	if (ALX_FLAG(adpt, USING_MSI)) {
		ALX_MEM_W32(hw, ALX_MSI_RETRANS_TIMER,
			    msi_ctrl | ALX_MSI_MASK_SEL_LINE);
		err = request_irq(pdev->irq, alx_intr_msi, 0,
				  adpt->netdev->name, adpt);
		if (!err)
			goto out;
		/* fall back to legacy interrupt */
		alx_disable_msi(adpt);
	}

	ALX_MEM_W32(hw, ALX_MSI_RETRANS_TIMER, 0);
	err = request_irq(pdev->irq, alx_intr_legacy, IRQF_SHARED,
			  adpt->netdev->name, adpt);

	if (err)
		netif_err(adpt, intr, adpt->netdev,
			  "request shared irq failed, err = %d\n",
			  err);

out:
	if (likely(!err)) {
		alx_config_vector_mapping(adpt);

		netif_info(adpt, drv, adpt->netdev,
			   "nr_rxq=%d, nr_txq=%d, nr_napi=%d, nr_vec=%d\n",
			   adpt->nr_rxq, adpt->nr_txq,
			   adpt->nr_napi, adpt->nr_vec);
		netif_info(adpt, drv, adpt->netdev,
			   "flags=%lX, Interrupt Mode: %s\n",
			   adpt->flags,
			   ALX_FLAG(adpt, USING_MSIX) ? "MSIX" :
			   ALX_FLAG(adpt, USING_MSI) ? "MSI" : "INTx");
	} else
		netdev_err(adpt->netdev,
			   "register IRQ fail %d\n",
			   err);

	return err;
}

static void alx_free_irq(struct alx_adapter *adpt)
{
	struct pci_dev *pdev = adpt->pdev;
	int i, vec;

	if (ALX_FLAG(adpt, USING_MSIX)) {
		free_irq(adpt->msix_ent[0].vector, adpt);
		vec = 1;
		for (i = 0; i < adpt->nr_napi; i++, vec++)
			free_irq(adpt->msix_ent[vec].vector, adpt->qnapi[i]);
	} else {
		free_irq(pdev->irq, adpt);
	}
	alx_disable_advanced_intr(adpt);
}


static int alx_identify_hw(struct alx_adapter *adpt)
{
	struct pci_dev *pdev = adpt->pdev;
	struct alx_hw *hw = &adpt->hw;
	int rev;
	int err = -EINVAL;

	hw->device_id = pdev->device;
	hw->subdev_id = pdev->subsystem_device;
	hw->subven_id = pdev->subsystem_vendor;
	hw->revision = pdev->revision;
	rev = ALX_REVID(hw);

	switch (ALX_DID(hw)) {
	case ALX_DEV_ID_AR8161:
	case ALX_DEV_ID_AR8162:
	case ALX_DEV_ID_AR8171:
	case ALX_DEV_ID_AR8172:
		if (rev > ALX_REV_C0)
			break;
		err = 0;
		ALX_CAP_SET(hw, L0S);
		ALX_CAP_SET(hw, L1);
		ALX_CAP_SET(hw, MTQ);
		ALX_CAP_SET(hw, RSS);
		ALX_CAP_SET(hw, MSIX);
		ALX_CAP_SET(hw, SWOI);
		hw->max_dma_chnl = rev >= ALX_REV_B0 ? 4 : 2;
		if (rev < ALX_REV_C0) {
			hw->ptrn_ofs = 0x600;
			hw->max_ptrns = 8;
		} else {
			hw->ptrn_ofs = 0x14000;
			hw->max_ptrns = 16;
		}
		break;
	}

	if (!err && ALX_DID(hw) & 1)
		ALX_CAP_SET(hw, GIGA);

	return err;
}


static const u8 def_rss_key[40] = {
	0xE2, 0x91, 0xD7, 0x3D, 0x18, 0x05, 0xEC, 0x6C,
	0x2A, 0x94, 0xB3, 0x0D, 0xA5, 0x4F, 0x2B, 0xEC,
	0xEA, 0x49, 0xAF, 0x7C, 0xE2, 0x14, 0xAD, 0x3D,
	0xB8, 0x55, 0xAA, 0xBE, 0x6A, 0x3E, 0x67, 0xEA,
	0x14, 0x36, 0x4D, 0x17, 0x3B, 0xED, 0x20, 0x0D,
};

void alx_init_def_rss_idt(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	int i, x, y;
	u32 val;

	for (i = 0; i < hw->rss_idt_size; i++) {
		val = ethtool_rxfh_indir_default(i, adpt->nr_rxq);
		x = i >> 3;
		y = i * 4 & 0x1F;
		hw->rss_idt[x] &= ~(0xF << y);
		hw->rss_idt[x] |= (val & 0xF) << y;
	}
}

/* alx_init_adapter -
 *    initialize general software structure (struct alx_adapter).
 *    fields are inited based on PCI device information.
 */
static int alx_init_sw(struct alx_adapter *adpt)
{
	struct pci_dev	*pdev = adpt->pdev;
	struct alx_hw *hw = &adpt->hw;
	int i, err;

	err = alx_identify_hw(adpt);
	if (err) {
		dev_err(&pdev->dev, "unrecognize the chip, aborting\n");
		return err;
	}

	/* assign patch flag for specific platforms */
	alx_patch_assign(hw);

	memcpy(hw->rss_key, def_rss_key, sizeof(def_rss_key));
	hw->rss_idt_size = 128;
	hw->rss_hash_type = ALX_RSS_HASH_TYPE_ALL;
	hw->smb_timer = 400;
	hw->mtu = adpt->netdev->mtu;
	adpt->rxbuf_size = ALIGN(ALX_RAW_MTU(hw->mtu), 8);
	adpt->tx_ringsz = 256;
	adpt->rx_ringsz = 512;
	hw->sleep_ctrl = ALX_SLEEP_WOL_MAGIC | ALX_SLEEP_WOL_PHY;
	hw->imt = 200;
	hw->imask = ALX_ISR_MISC;
	hw->dma_chnl = hw->max_dma_chnl;
	hw->ith_tpd = adpt->tx_ringsz / 3;
	hw->link_up = false;
	hw->link_duplex = 0;
	hw->link_speed = SPEED_0;
	hw->adv_cfg =	ADVERTISED_Autoneg |
			ADVERTISED_10baseT_Half |
			ADVERTISED_10baseT_Full |
			ADVERTISED_100baseT_Full |
			ADVERTISED_100baseT_Half |
			ADVERTISED_1000baseT_Full;
	hw->flowctrl = ALX_FC_ANEG | ALX_FC_RX | ALX_FC_TX;
	hw->wrr_ctrl = ALX_WRR_PRI_RESTRICT_NONE;
	for (i = 0; i < ARRAY_SIZE(hw->wrr); i++)
		hw->wrr[i] = 4;

	hw->rx_ctrl = ALX_MAC_CTRL_WOLSPED_SWEN |
			ALX_MAC_CTRL_MHASH_ALG_HI5B |
			ALX_MAC_CTRL_BRD_EN |
			ALX_MAC_CTRL_PCRCE |
			ALX_MAC_CTRL_CRCE |
			ALX_MAC_CTRL_RXFC_EN |
			ALX_MAC_CTRL_TXFC_EN |
			FIELDX(ALX_MAC_CTRL_PRMBLEN, 7);
	hw->is_fpga = false;

	atomic_set(&adpt->irq_sem, 1);
	ALX_FLAG_SET(adpt, HALT);

	return err;
}


static void alx_set_vlan_mode(struct alx_hw *hw,
			      netdev_features_t features)
{
	if (features & NETIF_F_HW_VLAN_RX)
		hw->rx_ctrl |= ALX_MAC_CTRL_VLANSTRIP;
	else
		hw->rx_ctrl &= ~ALX_MAC_CTRL_VLANSTRIP;

	ALX_MEM_W32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
}


#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39))
static netdev_features_t alx_fix_features(struct net_device *netdev,
					  netdev_features_t features)
{
	/*
	 * Since there is no support for separate rx/tx vlan accel
	 * enable/disable make sure tx flag is always in same state as rx.
	 */
	if (features & NETIF_F_HW_VLAN_RX)
		features |= NETIF_F_HW_VLAN_TX;
	else
		features &= ~NETIF_F_HW_VLAN_TX;

	if (netdev->mtu > ALX_MAX_TSO_PKT_SIZE)
		features &= ~(NETIF_F_TSO | NETIF_F_TSO6);

	return features;
}


static int alx_set_features(struct net_device *netdev,
			    netdev_features_t features)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	netdev_features_t changed = netdev->features ^ features;

	if (!(changed & NETIF_F_HW_VLAN_RX))
		return 0;

	alx_set_vlan_mode(&adpt->hw, features);

	return 0;
}
#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */

/* alx_change_mtu - Change the Maximum Transfer Unit */
static int alx_change_mtu(struct net_device *netdev, int new_mtu)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	int old_mtu   = netdev->mtu;
	int max_frame = new_mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN;

	if ((max_frame < ALX_MIN_FRAME_SIZE) ||
	    (max_frame > ALX_MAX_FRAME_SIZE)) {
		netif_err(adpt, hw, netdev,
			  "invalid MTU setting (%x)\n",
			  new_mtu);
		return -EINVAL;
	}
	/* set MTU */
	if (old_mtu != new_mtu) {
		netif_info(adpt, drv, adpt->netdev,
			   "changing MTU from %d to %d\n",
			   netdev->mtu, new_mtu);
		netdev->mtu = new_mtu;
		adpt->hw.mtu = new_mtu;
		adpt->rxbuf_size = new_mtu > ALX_DEF_RXBUF_SIZE ?
				   ALIGN(max_frame, 8) : ALX_DEF_RXBUF_SIZE;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39))
		if (new_mtu > (7*1024)) {
			netdev->features &= ~NETIF_F_TSO;
			netdev->features &= ~NETIF_F_TSO6;
		} else {
			netdev->features |= NETIF_F_TSO;
			netdev->features |= NETIF_F_TSO6;
		}
#else
		netdev_update_features(netdev);
#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */
		if (netif_running(netdev))
			alx_reinit(adpt);
	}

	return 0;
}

/* configure hardware everything except:
 *  1. interrupt vectors
 *  2. enable control for rx modules
 */
void alx_configure(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;

	alx_configure_basic(hw);
	alx_configure_rss(hw, adpt->nr_rxq > 1);
	__alx_set_rx_mode(adpt->netdev);
	alx_set_vlan_mode(hw, adpt->netdev->features);
}

static void alx_netif_stop(struct alx_adapter *adpt)
{
	int i;

	adpt->netdev->trans_start = jiffies;
	if (netif_carrier_ok(adpt->netdev)) {
		netif_carrier_off(adpt->netdev);
		netif_tx_disable(adpt->netdev);
		for (i = 0; i < adpt->nr_napi; i++)
			napi_disable(&adpt->qnapi[i]->napi);
	}
}

static void alx_netif_start(struct alx_adapter *adpt)
{
	int i;

	netif_tx_wake_all_queues(adpt->netdev);
	for (i = 0; i < adpt->nr_napi; i++)
		napi_enable(&adpt->qnapi[i]->napi);
	netif_carrier_on(adpt->netdev);
}

static bool alx_enable_msix(struct alx_adapter *adpt)
{
	int nr_txq, nr_rxq, vec_req;
	int i, err;

	nr_txq = min_t(int, num_online_cpus(), ALX_MAX_TX_QUEUES);
	nr_rxq = min_t(int, num_online_cpus(), ALX_MAX_RX_QUEUES);
	nr_rxq = rounddown_pow_of_two(nr_rxq);
	/* one more vector for PHY link change & timer & other events */
	vec_req = max_t(int, nr_txq, nr_rxq) + 1;

	if (vec_req  <= 2) {
		netif_info(adpt, intr, adpt->netdev,
			   "cpu core num is less, MSI-X isn't necessary\n");
		return false;
	}

	adpt->msix_ent = kcalloc(vec_req,
				 sizeof(struct msix_entry),
				 GFP_KERNEL);
	if (!adpt->msix_ent) {
		netif_warn(adpt, intr, adpt->netdev,
			   "can't alloc msix entries\n");
		return false;
	}
	for (i = 0; i < vec_req; i++)
		adpt->msix_ent[i].entry = i;

	err = pci_enable_msix(adpt->pdev, adpt->msix_ent, vec_req);
	if (err) {
		kfree(adpt->msix_ent);
		adpt->msix_ent = NULL;
		netif_warn(adpt, intr, adpt->netdev,
			   "can't enable MSI-X interrupt\n");
		return false;
	}

	adpt->nr_txq = nr_txq;
	adpt->nr_rxq = nr_rxq;
	adpt->nr_vec = vec_req;
	adpt->nr_napi = vec_req - 1;
	adpt->nr_hwrxq = ALX_CAP(&adpt->hw, MRQ) ? adpt->nr_rxq : 1;

	return true;
}

void alx_init_intr(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;

	if ((ALX_CAP(hw, MTQ) || ALX_CAP(hw, RSS)) && ALX_CAP(hw, MSIX)) {
		if (alx_enable_msix(adpt))
			ALX_FLAG_SET(adpt, USING_MSIX);
	}
	if (!ALX_FLAG(adpt, USING_MSIX)) {
		adpt->nr_txq = 1;
		adpt->nr_rxq = 1;
		adpt->nr_napi = 1;
		adpt->nr_vec = 1;
		adpt->nr_hwrxq = 1;

		if (!pci_enable_msi(adpt->pdev))
			ALX_FLAG_SET(adpt, USING_MSI);
	}
}

static int __alx_open(struct alx_adapter *adpt, bool resume)
{
	int err;

	/* decide interrupt mode, some resources allocation depend on it */
	alx_init_intr(adpt);

	/* init rss indirection table */
	alx_init_def_rss_idt(adpt);

	if (!resume)
		netif_carrier_off(adpt->netdev);

	/* allocate all memory resources */
	err = alx_setup_all_ring_resources(adpt);
	if (err)
		goto err_out;

	/* make hardware ready before allocate interrupt */
	alx_configure(adpt);

	err = alx_request_irq(adpt);
	if (err)
		goto err_out;

	/* netif_set_real_num_tx/rx_queues need rtnl_lock held */
	if (resume)
		rtnl_lock();
	netif_set_real_num_tx_queues(adpt->netdev, adpt->nr_txq);
	netif_set_real_num_rx_queues(adpt->netdev, adpt->nr_rxq);
	if (resume)
		rtnl_unlock();

	ALX_FLAG_CLEAR(adpt, HALT);

	/* clear old interrupts */
	ALX_MEM_W32(&adpt->hw, ALX_ISR, (u32)~ALX_ISR_DIS);

	alx_irq_enable(adpt);

	if (!resume)
		netif_tx_start_all_queues(adpt->netdev);

	ALX_FLAG_SET(adpt, TASK_CHK_LINK);
	alx_schedule_work(adpt);
	return 0;

err_out:

	alx_free_all_ring_resources(adpt);
	alx_disable_advanced_intr(adpt);
	return err;
}

static void alx_halt(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;

	ALX_FLAG_SET(adpt, HALT);
	alx_cancel_work(adpt);

	alx_netif_stop(adpt);
	hw->link_up = false;
	hw->link_speed = SPEED_0;

	alx_reset_mac(hw);

	/* disable l0s/l1 */
	alx_enable_aspm(hw, false, false);
	alx_irq_disable(adpt);
	alx_free_all_rings_buf(adpt);
}

static void alx_activate(struct alx_adapter *adpt)
{
	/* hardware setting lost, restore it */
	alx_reinit_rings(adpt);
	alx_configure(adpt);

	ALX_FLAG_CLEAR(adpt, HALT);
	/* clear old interrupts */
	ALX_MEM_W32(&adpt->hw, ALX_ISR, (u32)~ALX_ISR_DIS);

	alx_irq_enable(adpt);

	ALX_FLAG_SET(adpt, TASK_CHK_LINK);
	alx_schedule_work(adpt);
}

static void __alx_stop(struct alx_adapter *adpt)
{
	alx_halt(adpt);

	alx_free_irq(adpt);

	alx_free_all_ring_resources(adpt);
}

static void alx_init_ring_ptrs(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	struct alx_napi *np;
	int i, tx_idx, rx_idx;
	u32 addr_hi;
	u16 txring_header_reg[] = {ALX_TPD_PRI0_ADDR_LO, ALX_TPD_PRI1_ADDR_LO,
				   ALX_TPD_PRI2_ADDR_LO, ALX_TPD_PRI3_ADDR_LO};
	u16 rfdring_header_reg[] = {ALX_RFD_ADDR_LO};
	u16 rrdring_header_reg[] = {ALX_RRD_ADDR_LO};

	tx_idx = 0;
	rx_idx = 0;
	for (i = 0; i < adpt->nr_napi; i++) {
		np = adpt->qnapi[i];
		if (np->rxq) {
			np->rxq->pidx = 0;
			np->rxq->cidx = 0;
			np->rxq->rrd_cidx = 0;
			if (!ALX_CAP(hw, MRQ) && rx_idx == 0) {
				ALX_MEM_W32(hw, rfdring_header_reg[0],
					    np->rxq->rfd_dma);
				ALX_MEM_W32(hw, rrdring_header_reg[0],
					    np->rxq->rrd_dma);
			}
			rx_idx++;
		}
		if (np->txq) {
			np->txq->pidx = 0;
			atomic_set(&np->txq->cidx, 0);
			ALX_MEM_W32(hw, txring_header_reg[tx_idx],
				np->txq->tpd_dma);
			tx_idx++;
		}
	}

	addr_hi = ((u64)adpt->ring_header.dma) >> 32;
	ALX_MEM_W32(hw, ALX_TX_BASE_ADDR_HI, addr_hi);
	ALX_MEM_W32(hw, ALX_RX_BASE_ADDR_HI, addr_hi);
	ALX_MEM_W32(hw, ALX_TPD_RING_SZ, adpt->tx_ringsz);
	ALX_MEM_W32(hw, ALX_RRD_RING_SZ, adpt->rx_ringsz);
	ALX_MEM_W32(hw, ALX_RFD_RING_SZ, adpt->rx_ringsz);
	ALX_MEM_W32(hw, ALX_RFD_BUF_SZ, adpt->rxbuf_size);

	/* load these ptrs into chip internal */
	ALX_MEM_W32(hw, ALX_SRAM9, ALX_SRAM_LOAD_PTR);
}

static void alx_show_speed(struct alx_adapter *adpt, u16 speed)
{
	netif_info(adpt, link, adpt->netdev,
		   "NIC Link Up: %s\n",
		   speed_desc(speed));
}

static int alx_reinit_rings(struct alx_adapter *adpt)
{
	int i, err = 0;

	alx_free_all_rings_buf(adpt);

	/* set rings' header to HW register */
	alx_init_ring_ptrs(adpt);

	/* alloc hw-rxing buf */
	for (i = 0; i < adpt->nr_hwrxq; i++) {
		int count;

		count = alx_alloc_rxring_buf(adpt, adpt->qnapi[i]->rxq);
		if (unlikely(!count)) {
			err = -ENOMEM;
			break;
		}
	}

	return err;
}



static void alx_check_link(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	u16 speed, old_speed;
	bool link_up, old_link_up;
	int err;

	if (ALX_FLAG(adpt, HALT))
		return;

	/* clear PHY internal interrupt status,
	 * otherwise the Main interrupt status will be asserted
	 * for ever.
	 */
	alx_clear_phy_intr(hw);

	err = alx_get_phy_link(hw, &link_up, &speed);
	if (err)
		goto out;

	/* open interrutp mask */
	hw->imask |= ALX_ISR_PHY;
	ALX_MEM_W32(hw, ALX_IMR, hw->imask);

	if (!link_up && !hw->link_up)
		goto out;

	old_speed = hw->link_speed + hw->link_duplex;
	old_link_up = hw->link_up;

	if (link_up) {
		/* same speed ? */
		if (old_link_up && old_speed == speed)
			goto out;

		alx_show_speed(adpt, speed);
		hw->link_duplex = speed % 10;
		hw->link_speed = speed - hw->link_duplex;
		hw->link_up = true;
		alx_post_phy_link(hw, hw->link_speed, ALX_CAP(hw, AZ));
		alx_enable_aspm(hw, ALX_CAP(hw, L0S), ALX_CAP(hw, L1));
		alx_start_mac(hw);

		/* link kept, just speed changed */
		if (old_link_up)
			goto out;
		/* link changed from 'down' to 'up' */
		alx_netif_start(adpt);
		goto out;
	}

	/* link changed from 'up' to 'down' */
	alx_netif_stop(adpt);
	hw->link_up = false;
	hw->link_speed = SPEED_0;
	netif_info(adpt, link, adpt->netdev, "NIC Link Down\n");
	err = alx_reset_mac(hw);
	if (err) {
		netif_err(adpt, hw, adpt->netdev,
			  "linkdown:reset_mac fail %d\n", err);
		err = -EIO;
		goto out;
	}
	alx_irq_disable(adpt);

	/* reset-mac cause all settings on HW lost,
	 * following steps restore all of them and
	 * refresh whole RX/TX rings
	 */
	err = alx_reinit_rings(adpt);
	if (err)
		goto out;
	alx_configure(adpt);
	alx_enable_aspm(hw, false, ALX_CAP(hw, L1));
	alx_post_phy_link(hw, SPEED_0, ALX_CAP(hw, AZ));
	alx_irq_enable(adpt);

out:

	if (err) {
		ALX_FLAG_SET(adpt, TASK_RESET);
		alx_schedule_work(adpt);
	}
}

/* alx_open - Called when a network interface is made active */
static int alx_open(struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	int err;

	/* during diag running, disallow open */
	if (ALX_FLAG(adpt, TESTING))
		return -EBUSY;

	err = __alx_open(adpt, false);

	return err;
}

/* alx_stop - Disables a network interface */
static int alx_stop(struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);

	WARN_ON(ALX_FLAG(adpt, RESETING));

	__alx_stop(adpt);

	return 0;
}

static int __alx_shutdown(struct pci_dev *pdev, bool *wol_en)
{
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct net_device *netdev = adpt->netdev;
	struct alx_hw *hw = &adpt->hw;
	int err;
	u16 speed;

	netif_device_detach(netdev);
	if (netif_running(netdev))
		__alx_stop(adpt);

#ifdef CONFIG_PM_SLEEP
	err = pci_save_state(pdev);
	if (err)
		goto out;
#endif

	err = alx_select_powersaving_speed(hw, &speed);
	if (!err)
		err = alx_clear_phy_intr(hw);
	if (!err)
		err = alx_pre_suspend(hw, speed);
	if (!err)
		err = alx_config_wol(hw);
	if (err)
		goto out;

	*wol_en = false;
	if (hw->sleep_ctrl & ALX_SLEEP_ACTIVE) {
		netif_info(adpt, wol, netdev,
			   "wol: ctrl=%X, speed=%X\n",
			   hw->sleep_ctrl, speed);

		device_set_wakeup_enable(&pdev->dev, true);
		*wol_en = true;
	}

	pci_disable_device(pdev);

out:
	if (unlikely(err)) {
		netif_info(adpt, hw, netdev,
			   "shutown err(%x)\n",
			   err);
		err = -EIO;
	}

	return err;
}

static void alx_shutdown(struct pci_dev *pdev)
{
	int err;
	bool wol_en;

	err = __alx_shutdown(pdev, &wol_en);
	if (likely(!err)) {
		pci_wake_from_d3(pdev, wol_en);
		pci_set_power_state(pdev, PCI_D3hot);
	} else {
		dev_err(&pdev->dev, "shutdown fail %d\n", err);
	}
}

#ifdef CONFIG_PM_SLEEP
static int alx_suspend(struct device *dev)
{
	struct pci_dev *pdev = to_pci_dev(dev);
	int err;
	bool wol_en;

	err = __alx_shutdown(pdev, &wol_en);
	if (unlikely(err)) {
		dev_err(&pdev->dev, "shutdown fail in suspend %d\n", err);
		err = -EIO;
		goto out;
	}
	if (wol_en) {
		pci_prepare_to_sleep(pdev);
	} else {
		pci_wake_from_d3(pdev, false);
		pci_set_power_state(pdev, PCI_D3hot);
	}

out:
	return err;
}

static int alx_resume(struct device *dev)
{
	struct pci_dev *pdev = to_pci_dev(dev);
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct net_device *netdev = adpt->netdev;
	struct alx_hw *hw = &adpt->hw;
	int err;

	if (!netif_running(netdev))
		return 0;

	pci_set_power_state(pdev, PCI_D0);
	pci_restore_state(pdev);
	pci_save_state(pdev);

	pci_enable_wake(pdev, PCI_D3hot, 0);
	pci_enable_wake(pdev, PCI_D3cold, 0);

	hw->link_up = false;
	hw->link_speed = SPEED_0;
	hw->imask = ALX_ISR_MISC;

	alx_reset_pcie(hw);
	alx_reset_phy(hw, !hw->hib_patch);
	err = alx_reset_mac(hw);
	if (err) {
		netif_err(adpt, hw, adpt->netdev,
			  "resume:reset_mac fail %d\n",
			  err);
		return -EIO;
	}
	err = alx_setup_speed_duplex(hw, hw->adv_cfg, hw->flowctrl);
	if (err) {
		netif_err(adpt, hw, adpt->netdev,
			  "resume:setup_speed_duplex fail %d\n",
			  err);
		return -EIO;
	}

	if (netif_running(netdev)) {
		err = __alx_open(adpt, true);
		if (err)
			return err;
	}

	netif_device_attach(netdev);

	return err;
}
#endif



/* alx_update_hw_stats - Update the board statistics counters. */
static void alx_update_hw_stats(struct alx_adapter *adpt)
{
	if (ALX_FLAG(adpt, HALT) || ALX_FLAG(adpt, RESETING))
		return;

	__alx_update_hw_stats(&adpt->hw);
}

/* alx_get_stats - Get System Network Statistics
 *
 * Returns the address of the device statistics structure.
 * The statistics are actually updated from the timer callback.
 */
static struct net_device_stats *alx_get_stats(struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	struct net_device_stats *net_stats = &netdev->stats;
	struct alx_hw_stats *hw_stats = &adpt->hw.stats;

	spin_lock(&adpt->smb_lock);

	alx_update_hw_stats(adpt);

	net_stats->tx_packets = hw_stats->tx_ok;
	net_stats->tx_bytes   = hw_stats->tx_byte_cnt;
	net_stats->rx_packets = hw_stats->rx_ok;
	net_stats->rx_bytes   = hw_stats->rx_byte_cnt;
	net_stats->multicast  = hw_stats->rx_mcast;
	net_stats->collisions = hw_stats->tx_single_col +
				hw_stats->tx_multi_col * 2 +
				hw_stats->tx_late_col + hw_stats->tx_abort_col;

	net_stats->rx_errors  = hw_stats->rx_frag + hw_stats->rx_fcs_err +
				hw_stats->rx_len_err + hw_stats->rx_ov_sz +
				hw_stats->rx_ov_rrd + hw_stats->rx_align_err;

	net_stats->rx_fifo_errors   = hw_stats->rx_ov_rxf;
	net_stats->rx_length_errors = hw_stats->rx_len_err;
	net_stats->rx_crc_errors    = hw_stats->rx_fcs_err;
	net_stats->rx_frame_errors  = hw_stats->rx_align_err;
	net_stats->rx_over_errors   = hw_stats->rx_ov_rrd + hw_stats->rx_ov_rxf;

	net_stats->rx_missed_errors = hw_stats->rx_ov_rrd + hw_stats->rx_ov_rxf;

	net_stats->tx_errors = hw_stats->tx_late_col + hw_stats->tx_abort_col +
			       hw_stats->tx_underrun + hw_stats->tx_trunc;

	net_stats->tx_aborted_errors = hw_stats->tx_abort_col;
	net_stats->tx_fifo_errors    = hw_stats->tx_underrun;
	net_stats->tx_window_errors  = hw_stats->tx_late_col;

	spin_unlock(&adpt->smb_lock);

	return net_stats;
}

static void alx_update_stats(struct alx_adapter *adpt)
{
	spin_lock(&adpt->smb_lock);
	alx_update_hw_stats(adpt);
	spin_unlock(&adpt->smb_lock);
}

void alx_reinit(struct alx_adapter *adpt)
{
	WARN_ON(in_interrupt());

	while (test_and_set_bit(ALX_FLAG_RESETING, &adpt->flags))
		msleep(20);

	if (ALX_FLAG(adpt, HALT))
		return;

	alx_halt(adpt);
	alx_activate(adpt);

	ALX_FLAG_CLEAR(adpt, RESETING);
}

/* alx_task - manages and runs subtasks */
static void alx_task(struct work_struct *work)
{
	struct alx_adapter *adpt = container_of(work, struct alx_adapter, task);

	/* don't support reentrance */
	while (test_and_set_bit(ALX_FLAG_TASK_PENDING, &adpt->flags))
		msleep(20);

	if (ALX_FLAG(adpt, HALT))
		goto out;

	if (test_and_clear_bit(ALX_FLAG_TASK_RESET, &adpt->flags)) {
		netif_info(adpt, hw, adpt->netdev,
			   "task:alx_reinit\n");
		alx_reinit(adpt);
	}

	if (test_and_clear_bit(ALX_FLAG_TASK_UPDATE_SMB, &adpt->flags))
		alx_update_stats(adpt);

	if (test_and_clear_bit(ALX_FLAG_TASK_CHK_LINK, &adpt->flags))
		alx_check_link(adpt);

out:
	ALX_FLAG_CLEAR(adpt, TASK_PENDING);
}


static irqreturn_t alx_msix_ring(int irq, void *data)
{
	struct alx_napi *np = data;
	struct alx_adapter *adpt = np->adpt;
	struct alx_hw *hw = &adpt->hw;

	/* mask interrupt to ACK chip */
	alx_mask_msix(hw, np->vec_idx, true);
	/* clear interrutp status */
	ALX_MEM_W32(hw, ALX_ISR, np->vec_mask);

	if (!ALX_FLAG(adpt, HALT))
		napi_schedule(&np->napi);

	return IRQ_HANDLED;
}

static inline bool alx_handle_intr_misc(struct alx_adapter *adpt, u32 intr)
{
	struct alx_hw *hw = &adpt->hw;

	if (unlikely(intr & ALX_ISR_FATAL)) {
		netif_info(adpt, hw, adpt->netdev,
			   "intr-fatal:%08X\n", intr);
		ALX_FLAG_SET(adpt, TASK_RESET);
		alx_schedule_work(adpt);
		return true;
	}

	if (intr & ALX_ISR_ALERT)
		netdev_warn(adpt->netdev, "interrutp alert :%x\n", intr);

	if (intr & ALX_ISR_SMB) {
		ALX_FLAG_SET(adpt, TASK_UPDATE_SMB);
		alx_schedule_work(adpt);
	}

	if (intr & ALX_ISR_PHY) {
		/* suppress PHY interrupt, because the source
		 * is from PHY internal. only the internal status
		 * is cleared, the interrupt status could be cleared.
		 */
		hw->imask &= ~ALX_ISR_PHY;
		ALX_MEM_W32(hw, ALX_IMR, hw->imask);
		ALX_FLAG_SET(adpt, TASK_CHK_LINK);
		alx_schedule_work(adpt);
	}

	return false;
}

static irqreturn_t alx_intr_msix_misc(int irq, void *data)
{
	struct alx_adapter *adpt = data;
	struct alx_hw *hw = &adpt->hw;
	u32 intr;

	/* mask interrupt to ACK chip */
	alx_mask_msix(hw, 0, true);

	/* read interrupt status */
	ALX_MEM_R32(hw, ALX_ISR, &intr);
	intr &= (hw->imask & ~ALX_ISR_ALL_QUEUES);

	if (alx_handle_intr_misc(adpt, intr))
		return IRQ_HANDLED;

	/* clear interrupt status */
	ALX_MEM_W32(hw, ALX_ISR, intr);

	/* enable interrupt again */
	if (!ALX_FLAG(adpt, HALT))
		alx_mask_msix(hw, 0, false);

	return IRQ_HANDLED;
}

static inline irqreturn_t alx_intr_1(struct alx_adapter *adpt, u32 intr)
{
	struct alx_hw *hw = &adpt->hw;

	/* ACK interrupt */
	netif_info(adpt, intr, adpt->netdev,
		   "ACK interrupt: 0x%lx\n",
		   intr | ALX_ISR_DIS);
	ALX_MEM_W32(hw, ALX_ISR, intr | ALX_ISR_DIS);
	intr &= hw->imask;

	if (alx_handle_intr_misc(adpt, intr))
		return IRQ_HANDLED;

	if (intr & (ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0)) {
		napi_schedule(&adpt->qnapi[0]->napi);
		/* mask rx/tx interrupt, enable them when napi complete */
		hw->imask &= ~ALX_ISR_ALL_QUEUES;
		ALX_MEM_W32(hw, ALX_IMR, hw->imask);
	}

	ALX_MEM_W32(hw, ALX_ISR, 0);

	return IRQ_HANDLED;
}


static irqreturn_t alx_intr_msi(int irq, void *data)
{
	struct alx_adapter *adpt = data;
	u32 intr;

	/* read interrupt status */
	ALX_MEM_R32(&adpt->hw, ALX_ISR, &intr);

	return alx_intr_1(adpt, intr);
}

static irqreturn_t alx_intr_legacy(int irq, void *data)
{
	struct alx_adapter *adpt = data;
	struct alx_hw *hw = &adpt->hw;
	u32 intr;

	/* read interrupt status */
	ALX_MEM_R32(hw, ALX_ISR, &intr);
	if (intr & ALX_ISR_DIS || 0 == (intr & hw->imask)) {
		u32 mask;

		ALX_MEM_R32(hw, ALX_IMR, &mask);
		netif_info(adpt, intr, adpt->netdev,
			   "seems a wild interrupt, intr=%X, imask=%X, %X\n",
			   intr, hw->imask, mask);

		return IRQ_NONE;
	}

	return alx_intr_1(adpt, intr);
}


static int alx_poll(struct napi_struct *napi, int budget)
{
	struct alx_napi *np = container_of(napi, struct alx_napi, napi);
	struct alx_adapter *adpt = np->adpt;
	bool complete = true;

	netif_info(adpt, intr, adpt->netdev,
		   "alx_poll, budget(%d)\n",
		   budget);

	if (np->txq)
		complete = alx_clean_tx_irq(np->txq);
	if (np->rxq)
		complete &= alx_clean_rx_irq(np->rxq, budget);

	if (!complete)
		return budget;

	/* rx-packet finished, exit the polling mode */
	napi_complete(&np->napi);

	/* enable interrupt */
	if (!ALX_FLAG(adpt, HALT)) {
		struct alx_hw *hw = &adpt->hw;

		if (ALX_FLAG(adpt, USING_MSIX))
			alx_mask_msix(hw, np->vec_idx, false);
		else {
			/* TODO: need irq spinlock for imask ?? */
			hw->imask |= ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0;
			ALX_MEM_W32(hw, ALX_IMR, hw->imask);
		}
		ALX_MEM_FLUSH(hw);
	}

	return 0;
}

static inline struct alx_tx_queue *alx_tx_queue_mapping(
			struct alx_adapter *adpt,
			struct sk_buff *skb)
{
	int index = skb_get_queue_mapping(skb);

	if (index >= adpt->nr_txq)
		index = index % adpt->nr_txq;

	return adpt->qnapi[index]->txq;
}

static inline int alx_tpd_req(struct sk_buff *skb)
{
	int num;

	num = skb_shinfo(skb)->nr_frags + 1;
	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6)
		num++;

	return num;
}

/* get custom checksum offload params
 * return val:
 *     neg-val: drop this skb
 *     0: no custom checksum offload
 *     pos-val: have custom cksum offload
 */
static int alx_tx_csum(struct sk_buff *skb, struct tpd_desc *first)
{
	u8 cso, css;

	if (skb->ip_summed != CHECKSUM_PARTIAL)
		return 0;

	cso = skb_checksum_start_offset(skb);
	if (cso & 0x1)
		return -1;

	css = cso + skb->csum_offset;
	first->word1 |= FIELDX(TPD_CXSUMSTART, cso >> 1);
	first->word1 |= FIELDX(TPD_CXSUMOFFSET, css >> 1);
	first->word1 |= 1 << TPD_CXSUM_EN_SHIFT;

	return 1;
}

static int alx_tso(struct sk_buff *skb, struct tpd_desc *first)
{
	int hdr_len;
	int err;

	if (skb->ip_summed != CHECKSUM_PARTIAL ||
	    !skb_is_gso(skb))
		return 0;

	if (skb_header_cloned(skb)) {
		err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
		if (unlikely(err))
			return err;
	}

	if (skb->protocol == htons(ETH_P_IP)) {
		struct iphdr *iph;

		iph = ip_hdr(skb);
		hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
		iph->check = 0;
		tcp_hdr(skb)->check = ~csum_tcpudp_magic(
						iph->saddr,
						iph->daddr,
						0, IPPROTO_TCP, 0);
		first->word1 |= 1 << TPD_IPV4_SHIFT;
		first->word1 |= FIELDX(TPD_L4HDROFFSET,
				       skb_transport_offset(skb));
		if (unlikely(skb->len == hdr_len)) {
			/* no tcp payload */
			first->word1 |= 1 << TPD_IP_XSUM_SHIFT;
			first->word1 |= 1 << TPD_TCP_XSUM_SHIFT;
			return 0;
		}
		first->word1 |= 1 << TPD_LSO_EN_SHIFT;
		first->word1 |= FIELDX(TPD_MSS, skb_shinfo(skb)->gso_size);
	} else if (skb_is_gso_v6(skb)) {
		struct ipv6hdr *ip6h;

		ip6h = ipv6_hdr(skb);
		hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
		ip6h->payload_len = 0;
		tcp_hdr(skb)->check = ~csum_ipv6_magic(
						&ip6h->saddr,
						&ip6h->daddr,
						0, IPPROTO_TCP, 0);
		first->word1 |= FIELDX(TPD_L4HDROFFSET,
				       skb_transport_offset(skb));
		if (unlikely(skb->len == hdr_len)) {
			/* no tcp payload */
			ip6h->payload_len = skb->len -
				((unsigned char *)ip6h - skb->data) -
				sizeof(struct ipv6hdr);
			first->word1 |= 1 << TPD_IP_XSUM_SHIFT;
			first->word1 |= 1 << TPD_TCP_XSUM_SHIFT;
			return 0;
		}
		/* for LSOv2, the 1st TPD just provides packet length */
		first->adrl.l.pkt_len = skb->len;
		first->word1 |= 1 << TPD_LSO_EN_SHIFT;
		first->word1 |= 1 << TPD_LSO_V2_SHIFT;
		first->word1 |= FIELDX(TPD_MSS, skb_shinfo(skb)->gso_size);
	}

	return 1;
}

static int alx_tx_map(struct alx_tx_queue *txq, struct sk_buff *skb)
{
	struct tpd_desc *tpd, *first_tpd;
	struct alx_buffer *buf, *first_buf;
	dma_addr_t dma;
	u16 producer, maplen, f;

	producer = txq->pidx;

	first_tpd = txq->tpd_hdr + producer;
	first_buf = txq->bf_info + producer;
	tpd = first_tpd;
	buf = first_buf;
	if (tpd->word1 & (1 << TPD_LSO_V2_SHIFT)) {
		if (++producer == txq->count)
			producer = 0;
		tpd = txq->tpd_hdr + producer;
		buf = txq->bf_info + producer;
		tpd->word0 = first_tpd->word0;
		tpd->word1 = first_tpd->word1;
	}
	maplen = skb_headlen(skb);
	dma = dma_map_single(txq->dev, skb->data, maplen, DMA_TO_DEVICE);
	if (dma_mapping_error(txq->dev, dma))
		goto err_dma;

	dma_unmap_len_set(buf, size, maplen);
	dma_unmap_addr_set(buf, dma, dma);

	tpd->adrl.addr = cpu_to_le64(dma);
	FIELD_SET32(tpd->word0, TPD_BUFLEN, maplen);

	for (f = 0; f < skb_shinfo(skb)->nr_frags; f++) {
		struct skb_frag_struct *frag;

		frag = &skb_shinfo(skb)->frags[f];
		if (++producer == txq->count)
			producer = 0;
		tpd = txq->tpd_hdr + producer;
		buf = txq->bf_info + producer;
		tpd->word0 = first_tpd->word0;
		tpd->word1 = first_tpd->word1;
		maplen = skb_frag_size(frag);
		dma = skb_frag_dma_map(txq->dev, frag, 0,
				       maplen, DMA_TO_DEVICE);
		if (dma_mapping_error(txq->dev, dma))
			goto err_dma;
		dma_unmap_len_set(buf, size, maplen);
		dma_unmap_addr_set(buf, dma, dma);

		tpd->adrl.addr = cpu_to_le64(dma);
		FIELD_SET32(tpd->word0, TPD_BUFLEN, maplen);
	}
	/* last TPD */
	tpd->word1 |= 1 << TPD_EOP_SHIFT;

	if (++producer == txq->count)
		producer = 0;

	first_buf->flags |= ALX_BUF_TX_FIRSTFRAG;
	buf->skb = skb;
	txq->pidx = producer;

	return 0;

err_dma:

	for (f = txq->pidx; f != producer;) {
		alx_txbuf_unmap_and_free(txq, f);
		if (++f == txq->count)
			f = 0;
	}
	return -1;
}

static netdev_tx_t alx_start_xmit_ring(struct alx_tx_queue *txq,
				       struct sk_buff *skb)
{
	struct alx_adapter *adpt;
	struct netdev_queue *netque;
	struct tpd_desc *first;
	int budget, tpdreq;
	int do_tso;

	adpt = netdev_priv(txq->netdev);
	netque = netdev_get_tx_queue(txq->netdev, skb_get_queue_mapping(skb));

	tpdreq = alx_tpd_req(skb);
	budget = alx_tpd_avail(txq);

	if (unlikely(budget < tpdreq)) {
		if (!netif_tx_queue_stopped(netque)) {
			netif_tx_stop_queue(netque);

			/* TX reclaim might have plenty of free TPD
			 * but see tx_queue is active (because its
			 * judement doesn't acquire tx-spin-lock,
			 * this situation cause the TX-queue stop and
			 * never be wakeup.
			 * try one more time
			 */
			budget = alx_tpd_avail(txq);
			if (budget >= tpdreq) {
				netif_tx_wake_queue(netque);
				goto tx_conti;
			}
			netif_err(adpt, tx_err, adpt->netdev,
				  "TPD Ring is full when queue awake!\n");
		}
		return NETDEV_TX_BUSY;
	}

tx_conti:

	first = txq->tpd_hdr + txq->pidx;
	memset(first, 0, sizeof(struct tpd_desc));

	/* NOTE, chip only supports single-VLAN insertion (81-00-TAG) */
	if (vlan_tx_tag_present(skb)) {
		first->word1 |= 1 << TPD_INS_VLTAG_SHIFT;
		first->word0 |= FIELDX(TPD_VLTAG, htons(vlan_tx_tag_get(skb)));
	}
	if (skb->protocol == htons(ETH_P_8021Q))
		first->word1 |= 1 << TPD_VLTAGGED_SHIFT;
	if (skb_network_offset(skb) != ETH_HLEN)
		first->word1 |= 1 << TPD_ETHTYPE_SHIFT;

	do_tso = alx_tso(skb, first);
	if (do_tso < 0)
		goto drop;
	else if (!do_tso && alx_tx_csum(skb, first) < 0)
		goto drop;

	if (alx_tx_map(txq, skb) < 0)
		goto drop;

	netdev_tx_sent_queue(netque, skb->len);

	/* refresh produce idx on HW */
	wmb();
	ALX_MEM_W16(&adpt->hw, txq->p_reg, txq->pidx);

	netif_info(adpt, tx_done, adpt->netdev,
		   "TX[Preg:%X]: producer = 0x%x, consumer = 0x%x\n",
		   txq->p_reg, txq->pidx, atomic_read(&txq->cidx));

	return NETDEV_TX_OK;

drop:
	netif_info(adpt, tx_done, adpt->netdev,
		   "tx-skb(%d) dropped\n",
		   skb->len);
	memset(first, 0, sizeof(struct tpd_desc));
	dev_kfree_skb(skb);

	return NETDEV_TX_OK;
}

static netdev_tx_t alx_start_xmit(struct sk_buff *skb,
				  struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);

	if (ALX_FLAG(adpt, HALT)) {
		dev_kfree_skb_any(skb);
		return NETDEV_TX_OK;
	}

	if (skb->len <= 0) {
		dev_kfree_skb_any(skb);
		return NETDEV_TX_OK;
	}

	return alx_start_xmit_ring(alx_tx_queue_mapping(adpt, skb), skb);
}


static void alx_dump_state(struct alx_adapter *adpt)
{
	struct alx_hw *hw = &adpt->hw;
	struct alx_tx_queue *txq;
	struct tpd_desc *tpd;
	u16 begin, end;
	int i;

	for (i = 0; i < adpt->nr_txq; i++) {

		txq = adpt->qnapi[i]->txq;
		begin = txq->pidx >=  8 ? (txq->pidx - 8) :
				(txq->count + txq->pidx - 8);
		end = txq->pidx + 4;
		if (end >= txq->count)
			end -= txq->count;

		netif_err(adpt, tx_err, adpt->netdev,
			  "-----------------TPD-ring(%d)------------------\n",
			  i);

		while (begin != end) {
			tpd = txq->tpd_hdr + begin;
			netif_err(adpt, tx_err, adpt->netdev,
				  "%X: W0=%08X, W1=%08X, W2=%X\n",
				  begin, tpd->word0, tpd->word1,
				  tpd->adrl.l.pkt_len);
			if (++begin >= txq->count)
				begin = 0;
		}
	}

	netif_err(adpt, tx_err, adpt->netdev,
		  "---------------dump registers-----------------\n");
	end = 0x1800;
	for (begin = 0x1400; begin < end; begin += 16) {
		u32 v1, v2, v3, v4;

		ALX_MEM_R32(hw, begin, &v1);
		ALX_MEM_R32(hw, begin+4, &v2);
		ALX_MEM_R32(hw, begin+8, &v3);
		ALX_MEM_R32(hw, begin+12, &v4);
		netif_err(adpt, tx_err, adpt->netdev,
			  "%04X: %08X,%08X,%08X,%08X\n",
			  begin, v1, v2, v3, v4);
	}
}

static void alx_tx_timeout(struct net_device *dev)
{
	struct alx_adapter *adpt = netdev_priv(dev);

	alx_dump_state(adpt);

	ALX_FLAG_SET(adpt, TASK_RESET);
	alx_schedule_work(adpt);
}

static int alx_mdio_read(struct net_device *netdev,
			 int prtad, int devad, u16 addr)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	struct alx_hw *hw = &adpt->hw;
	u16 val;
	int err;

	netif_dbg(adpt, hw, netdev,
		  "alx_mdio_read, prtad=%d, devad=%d, addr=%X\n",
		  prtad, devad, addr);

	if (prtad != hw->mdio.prtad)
		return -EINVAL;

	if (devad != MDIO_DEVAD_NONE)
		err = alx_read_phy_ext(hw, devad, addr, &val);
	else
		err = alx_read_phy_reg(hw, addr, &val);

	return err ? -EIO : val;
}

static int alx_mdio_write(struct net_device *netdev,
			  int prtad, int devad, u16 addr, u16 val)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	struct alx_hw *hw = &adpt->hw;
	int err;

	netif_dbg(adpt, hw, netdev,
		  "alx_mdio_write: prtad=%d, devad=%d, addr=%X, val=%X\n",
		  prtad, devad, addr, val);

	if (prtad != hw->mdio.prtad)
		return -EINVAL;

	if (devad != MDIO_DEVAD_NONE)
		err = alx_write_phy_ext(hw, devad, addr, val);
	else
		err = alx_write_phy_reg(hw, addr, val);

	return err ? -EIO : 0;
}

static int alx_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
{
	struct alx_adapter *adpt = netdev_priv(netdev);

	if (!netif_running(netdev))
		return -EAGAIN;

	return mdio_mii_ioctl(&adpt->hw.mdio, if_mii(ifr), cmd);
}

#ifdef CONFIG_NET_POLL_CONTROLLER
static void alx_poll_controller(struct net_device *netdev)
{
	struct alx_adapter *adpt = netdev_priv(netdev);
	int i;

	if (ALX_FLAG(adpt, HALT))
		return;

	if (ALX_FLAG(adpt, USING_MSIX)) {
		alx_intr_msix_misc(0, adpt);
		for (i = 0; i < adpt->nr_napi; i++)
			alx_msix_ring(0, adpt->qnapi[i]);
	} else if (ALX_FLAG(adpt, USING_MSI))
		alx_intr_msi(0, adpt);
	else
		alx_intr_legacy(0, adpt);

}
#endif

static const struct net_device_ops alx_netdev_ops = {
	.ndo_open               = alx_open,
	.ndo_stop               = alx_stop,
	.ndo_start_xmit         = alx_start_xmit,
	.ndo_get_stats          = alx_get_stats,
	.ndo_set_rx_mode        = alx_set_rx_mode,
	.ndo_validate_addr      = eth_validate_addr,
	.ndo_set_mac_address    = alx_set_mac_address,
	.ndo_change_mtu         = alx_change_mtu,
	.ndo_do_ioctl           = alx_ioctl,
	.ndo_tx_timeout         = alx_tx_timeout,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39))
	.ndo_fix_features	= alx_fix_features,
	.ndo_set_features	= alx_set_features,
#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */
#ifdef CONFIG_NET_POLL_CONTROLLER
	.ndo_poll_controller    = alx_poll_controller,
#endif
};

/* alx_probe - Device Initialization Routine */
static int
alx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct net_device *netdev;
	struct alx_adapter *adpt = NULL;
	struct alx_hw *hw;
	bool phy_cfged;
	int bars, pm_cap, err;
	static int cards_found;

	/* enable device (incl. PCI PM wakeup and hotplug setup) */
	err = pci_enable_device_mem(pdev);
	if (err) {
		dev_err(&pdev->dev, "cannot enable PCI device memory\n");
		return err;
	}

	/* The alx chip can DMA to 64-bit addresses, but it uses a single
	 * shared register for the high 32 bits, so only a single, aligned,
	 * 4 GB physical address range can be used at a time.
	 */
	if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) &&
	    !dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64))) {
		dev_dbg(&pdev->dev, "DMA to 64-BIT addresses\n");
	} else {
		err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
		if (err) {
			err = dma_set_coherent_mask(&pdev->dev,
						    DMA_BIT_MASK(32));
			if (err) {
				dev_err(&pdev->dev,
					"No usable DMA config, aborting\n");
				goto err_dma_mask;
			}
		}
	}

	/* obtain PCI resources */
	bars = pci_select_bars(pdev, IORESOURCE_MEM);
	err = pci_request_selected_regions(pdev, bars, alx_drv_name);
	if (err) {
		dev_err(&pdev->dev,
			"pci_request_selected_regions failed(bars:%d)\n", bars);
		goto err_pci_region;
	}

	pci_enable_pcie_error_reporting(pdev);
	pci_set_master(pdev);

	/* find PM capability */
	pm_cap = pci_find_capability(pdev, PCI_CAP_ID_PM);
	if (pm_cap == 0) {
		dev_err(&pdev->dev,
			"Can't find power management capability, aborting\n");
		err = -EIO;
		goto err_pm;
	}
	err = pci_set_power_state(pdev, PCI_D0);
	if (err) {
		dev_err(&pdev->dev, "switch to D0 status failed, aborting\n");
		goto err_pm;
	}

	/* netdev zeroed in init_etherdev */
	netdev = alloc_etherdev_mqs(sizeof(struct alx_adapter),
				    ALX_MAX_TX_QUEUES,
				    ALX_MAX_RX_QUEUES);
	if (!netdev) {
		dev_err(&pdev->dev, "etherdev_mq alloc failed\n");
		err = -ENOMEM;
		goto err_alloc_ethdev;
	}

	SET_NETDEV_DEV(netdev, &pdev->dev);
	adpt = netdev_priv(netdev);
	adpt->netdev = netdev;
	adpt->pdev = pdev;
	adpt->msg_enable = NETIF_MSG_LINK |
			   NETIF_MSG_HW |
			   NETIF_MSG_IFUP |
			   NETIF_MSG_TX_ERR |
			   NETIF_MSG_RX_ERR |
			   NETIF_MSG_WOL;
	adpt->bd_number = cards_found;
	hw = &adpt->hw;
	hw->pdev = pdev;
	pci_set_drvdata(pdev, adpt);

	hw->hw_addr = pci_ioremap_bar(pdev, 0);
	if (!hw->hw_addr) {
		dev_err(&pdev->dev, "cannot map device registers\n");
		err = -EIO;
		goto err_iomap;
	}

	netdev_attach_ops(netdev, &alx_netdev_ops);
	alx_set_ethtool_ops(netdev);
	netdev->irq  = pdev->irq;
	netdev->watchdog_timeo = ALX_WATCHDOG_TIME;

	/* init alx_adapte structure */
	err = alx_init_sw(adpt);
	if (err) {
		dev_err(&pdev->dev, "net device private data init failed\n");
		goto err_init_sw;
	}

	/* reset pcie */
	alx_reset_pcie(hw);

	/* check if phy already configed by ohter driver */
	phy_cfged = alx_phy_configed(hw);

	/* reset PHY to a known stable status */
	if (!phy_cfged)
		alx_reset_phy(hw, !hw->hib_patch);
	else
		dev_info(&pdev->dev, "PHY has been configured.\n");

	/* reset mac/dma controller */
	err = alx_reset_mac(hw);
	if (err) {
		dev_err(&pdev->dev, "MAC Reset failed, error = %d\n", err);
		err = -EIO;
		goto err_rst_mac;
	}

	/* setup link to put it in a known good starting state */
	if (!phy_cfged) {
		err = alx_setup_speed_duplex(hw,
			hw->adv_cfg, hw->flowctrl);
		if (err) {
			dev_err(&pdev->dev,
				"config PHY speed/duplex failed,err=%d\n",
				err);
			err = -EIO;
			goto err_setup_link;
		}
	}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39))
	netdev->hw_features = NETIF_F_SG	 |
			      NETIF_F_HW_CSUM	 |
			      NETIF_F_HW_VLAN_RX |
			      NETIF_F_TSO        |
			      NETIF_F_TSO6;
	netdev->features = netdev->hw_features | NETIF_F_HW_VLAN_TX;
#else
	netdev->features = NETIF_F_SG            |
			   NETIF_F_HW_CSUM       |
			   NETIF_F_HW_VLAN_RX    |
			   NETIF_F_TSO           |
			   NETIF_F_TSO6          |
			   NETIF_F_HW_VLAN_TX;
#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */

	/* read permanent mac addr from register or eFuse */
	if (alx_get_perm_macaddr(hw, hw->perm_addr)) {
		dev_warn(&pdev->dev, "invalid perm-address, use random one\n");
		eth_hw_addr_random(netdev);
		memcpy(hw->perm_addr, netdev->dev_addr, netdev->addr_len);
	}
	/* using permanent address as current address */
	memcpy(hw->mac_addr, hw->perm_addr, ETH_ALEN);
	memcpy(netdev->dev_addr, hw->mac_addr, ETH_ALEN);
	memcpy(netdev->perm_addr, hw->perm_addr, ETH_ALEN);

	/* PHY mdio */
	hw->mdio.prtad = 0;
	hw->mdio.mmds = 0;
	hw->mdio.dev = netdev;
	hw->mdio.mode_support =
		MDIO_SUPPORTS_C45 | MDIO_SUPPORTS_C22 | MDIO_EMULATE_C22;
	hw->mdio.mdio_read = alx_mdio_read;
	hw->mdio.mdio_write = alx_mdio_write;
	if (!alx_get_phy_info(hw)) {
		dev_err(&pdev->dev, "identify PHY failed\n");
		err = -EIO;
		goto err_id_phy;
	}

	INIT_WORK(&adpt->task, alx_task);

	err = register_netdev(netdev);
	if (err) {
		dev_err(&pdev->dev, "register netdevice failed\n");
		goto err_register_netdev;
	}

	/* carrier off reporting is important to ethtool even BEFORE open */
	netif_carrier_off(netdev);

	device_set_wakeup_enable(&pdev->dev, hw->sleep_ctrl);
	cards_found++;

	dev_info(&pdev->dev,
		 "alx(%pM): Qualcomm Atheros Ethernet Network Connection\n",
		 netdev->dev_addr);

	return 0;

err_id_phy:
err_register_netdev:
err_setup_link:
err_rst_mac:
err_init_sw:
	iounmap(hw->hw_addr);
err_iomap:
	free_netdev(netdev);
err_alloc_ethdev:
err_pm:
	pci_release_selected_regions(pdev, bars);
err_pci_region:
err_dma_mask:
	pci_disable_device(pdev);
	dev_err(&pdev->dev, "error when probe device, error = %d\n", err);
	return err;
}

/* alx_remove - Device Removal Routine */
static void alx_remove(struct pci_dev *pdev)
{
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct alx_hw *hw = &adpt->hw;
	struct net_device *netdev;

	if (!adpt)
		return;

	netdev = adpt->netdev;

	ALX_FLAG_SET(adpt, HALT);
	alx_cancel_work(adpt);

	/* restore permanent mac address */
	alx_set_macaddr(hw, hw->perm_addr);

	unregister_netdev(netdev);
	iounmap(hw->hw_addr);
	pci_release_selected_regions(pdev,
				     pci_select_bars(pdev, IORESOURCE_MEM));
	free_netdev(netdev);
	pci_disable_pcie_error_reporting(pdev);
	pci_disable_device(pdev);
	pci_set_drvdata(pdev, NULL);
}

/* alx_pci_error_detected */
static pci_ers_result_t alx_pci_error_detected(struct pci_dev *pdev,
					       pci_channel_state_t state)
{
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct net_device *netdev = adpt->netdev;
	pci_ers_result_t rc = PCI_ERS_RESULT_NEED_RESET;

	dev_info(&pdev->dev, "pci error detected\n");

	rtnl_lock();

	if (netif_running(netdev)) {
		netif_device_detach(netdev);
		alx_halt(adpt);
	}
	if (state == pci_channel_io_perm_failure)
		rc = PCI_ERS_RESULT_DISCONNECT;
	else
		pci_disable_device(pdev);

	rtnl_unlock();

	return rc;
}

/* alx_pci_error_slot_reset */
static pci_ers_result_t alx_pci_error_slot_reset(struct pci_dev *pdev)
{
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct alx_hw *hw = &adpt->hw;
	pci_ers_result_t rc = PCI_ERS_RESULT_DISCONNECT;

	dev_info(&pdev->dev, "pci error slot reset\n");

	rtnl_lock();

	if (pci_enable_device(pdev)) {
		dev_err(&pdev->dev, "Re-enable PCI device after reset fail\n");
		goto out;
	}

	pci_set_master(pdev);
	pci_enable_wake(pdev, PCI_D3hot, 0);
	pci_enable_wake(pdev, PCI_D3cold, 0);

	alx_reset_pcie(hw);
	if (!alx_reset_mac(hw))
		rc = PCI_ERS_RESULT_RECOVERED;
out:
	pci_cleanup_aer_uncorrect_error_status(pdev);

	rtnl_unlock();

	return rc;
}

/* alx_pci_error_resume */
static void alx_pci_error_resume(struct pci_dev *pdev)
{
	struct alx_adapter *adpt = pci_get_drvdata(pdev);
	struct net_device *netdev = adpt->netdev;

	dev_info(&pdev->dev, "pci error resume\n");

	rtnl_lock();

	if (netif_running(netdev)) {
		alx_activate(adpt);
		netif_device_attach(netdev);
	}

	rtnl_unlock();
}


static struct pci_error_handlers alx_err_handler = {
	.error_detected = alx_pci_error_detected,
	.slot_reset     = alx_pci_error_slot_reset,
	.resume         = alx_pci_error_resume,
};

#ifdef CONFIG_PM_SLEEP
compat_pci_suspend(alx_suspend);
compat_pci_resume(alx_resume);
static SIMPLE_DEV_PM_OPS(alx_pm_ops, alx_suspend, alx_resume);
#define ALX_PM_OPS      (&alx_pm_ops)
#else
#define ALX_PM_OPS      NULL
#endif

static struct pci_driver alx_driver = {
	.name        = alx_drv_name,
	.id_table    = alx_pci_tbl,
	.probe       = alx_probe,
	.remove      = alx_remove,
	.shutdown    = alx_shutdown,
	.err_handler = &alx_err_handler,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29))
	.driver.pm   = ALX_PM_OPS,
#elif defined(CONFIG_PM_SLEEP)
	.suspend        = alx_suspend_compat,
	.resume         = alx_resume_compat,
#endif
};


static int __init alx_init_module(void)
{
	pr_info("%s\n", alx_drv_desc);
	return pci_register_driver(&alx_driver);
}
module_init(alx_init_module);

static void __exit alx_exit_module(void)
{
	pci_unregister_driver(&alx_driver);
}
module_exit(alx_exit_module);
