alx: add msi-x support

Add msi-x support to the alx driver. This is in preparation for multi queue
support.

msi-x interrupts are disabled by default because without multi queue support
there is no advantage over msi interrupts. The performance numbers observed
with iperf stay the same.

Based on information of the downstream driver at github.com/qca/alx

Signed-off-by: Tobias Regnery <tobias.regnery@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Tobias Regnery 2016-09-09 12:19:54 +02:00 committed by David S. Miller
parent a0373aef3e
commit dc39a78b3c
4 changed files with 184 additions and 8 deletions

View File

@ -84,6 +84,11 @@ struct alx_priv {
struct alx_hw hw;
/* msi-x vectors */
int num_vec;
struct msix_entry *msix_entries;
char irq_lbl[IFNAMSIZ + 8];
/* all descriptor memory */
struct {
dma_addr_t dma;

View File

@ -1031,6 +1031,20 @@ void alx_configure_basic(struct alx_hw *hw)
alx_write_mem32(hw, ALX_WRR, val);
}
void alx_mask_msix(struct alx_hw *hw, int index, bool mask)
{
u32 reg, val;
reg = ALX_MSIX_ENTRY_BASE + index * PCI_MSIX_ENTRY_SIZE +
PCI_MSIX_ENTRY_VECTOR_CTRL;
val = mask ? PCI_MSIX_ENTRY_CTRL_MASKBIT : 0;
alx_write_mem32(hw, reg, val);
alx_post_write(hw);
}
bool alx_get_phy_info(struct alx_hw *hw)
{
u16 devs1, devs2;

View File

@ -562,6 +562,7 @@ int alx_reset_mac(struct alx_hw *hw);
void alx_set_macaddr(struct alx_hw *hw, const u8 *addr);
bool alx_phy_configured(struct alx_hw *hw);
void alx_configure_basic(struct alx_hw *hw);
void alx_mask_msix(struct alx_hw *hw, int index, bool mask);
void alx_disable_rss(struct alx_hw *hw);
bool alx_get_phy_info(struct alx_hw *hw);
void alx_update_hw_stats(struct alx_hw *hw);

View File

@ -292,10 +292,14 @@ static int alx_poll(struct napi_struct *napi, int budget)
napi_complete(&alx->napi);
/* enable interrupt */
spin_lock_irqsave(&alx->irq_lock, flags);
alx->int_mask |= ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0;
alx_write_mem32(hw, ALX_IMR, alx->int_mask);
spin_unlock_irqrestore(&alx->irq_lock, flags);
if (alx->flags & ALX_FLAG_USING_MSIX) {
alx_mask_msix(hw, 1, false);
} else {
spin_lock_irqsave(&alx->irq_lock, flags);
alx->int_mask |= ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0;
alx_write_mem32(hw, ALX_IMR, alx->int_mask);
spin_unlock_irqrestore(&alx->irq_lock, flags);
}
alx_post_write(hw);
@ -356,6 +360,46 @@ static irqreturn_t alx_intr_handle(struct alx_priv *alx, u32 intr)
return IRQ_HANDLED;
}
static irqreturn_t alx_intr_msix_ring(int irq, void *data)
{
struct alx_priv *alx = data;
struct alx_hw *hw = &alx->hw;
/* mask interrupt to ACK chip */
alx_mask_msix(hw, 1, true);
/* clear interrupt status */
alx_write_mem32(hw, ALX_ISR, (ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0));
napi_schedule(&alx->napi);
return IRQ_HANDLED;
}
static irqreturn_t alx_intr_msix_misc(int irq, void *data)
{
struct alx_priv *alx = data;
struct alx_hw *hw = &alx->hw;
u32 intr;
/* mask interrupt to ACK chip */
alx_mask_msix(hw, 0, true);
/* read interrupt status */
intr = alx_read_mem32(hw, ALX_ISR);
intr &= (alx->int_mask & ~ALX_ISR_ALL_QUEUES);
if (alx_intr_handle_misc(alx, intr))
return IRQ_HANDLED;
/* clear interrupt status */
alx_write_mem32(hw, ALX_ISR, intr);
/* enable interrupt again */
alx_mask_msix(hw, 0, false);
return IRQ_HANDLED;
}
static irqreturn_t alx_intr_msi(int irq, void *data)
{
struct alx_priv *alx = data;
@ -620,15 +664,84 @@ static void alx_free_rings(struct alx_priv *alx)
static void alx_config_vector_mapping(struct alx_priv *alx)
{
struct alx_hw *hw = &alx->hw;
u32 tbl = 0;
alx_write_mem32(hw, ALX_MSI_MAP_TBL1, 0);
if (alx->flags & ALX_FLAG_USING_MSIX) {
tbl |= 1 << ALX_MSI_MAP_TBL1_TXQ0_SHIFT;
tbl |= 1 << ALX_MSI_MAP_TBL1_RXQ0_SHIFT;
}
alx_write_mem32(hw, ALX_MSI_MAP_TBL1, tbl);
alx_write_mem32(hw, ALX_MSI_MAP_TBL2, 0);
alx_write_mem32(hw, ALX_MSI_ID_MAP, 0);
}
static bool alx_enable_msix(struct alx_priv *alx)
{
int i, err, num_vec = 2;
alx->msix_entries = kcalloc(num_vec, sizeof(struct msix_entry),
GFP_KERNEL);
if (!alx->msix_entries) {
netdev_warn(alx->dev, "Allocation of msix entries failed!\n");
return false;
}
for (i = 0; i < num_vec; i++)
alx->msix_entries[i].entry = i;
err = pci_enable_msix(alx->hw.pdev, alx->msix_entries, num_vec);
if (err) {
kfree(alx->msix_entries);
netdev_warn(alx->dev, "Enabling MSI-X interrupts failed!\n");
return false;
}
alx->num_vec = num_vec;
return true;
}
static int alx_request_msix(struct alx_priv *alx)
{
struct net_device *netdev = alx->dev;
int i, err, vector = 0, free_vector = 0;
err = request_irq(alx->msix_entries[0].vector, alx_intr_msix_misc,
0, netdev->name, alx);
if (err)
goto out_err;
vector++;
sprintf(alx->irq_lbl, "%s-TxRx-0", netdev->name);
err = request_irq(alx->msix_entries[vector].vector,
alx_intr_msix_ring, 0, alx->irq_lbl, alx);
if (err)
goto out_free;
return 0;
out_free:
free_irq(alx->msix_entries[free_vector++].vector, alx);
vector--;
for (i = 0; i < vector; i++)
free_irq(alx->msix_entries[free_vector++].vector, alx);
out_err:
return err;
}
static void alx_init_intr(struct alx_priv *alx, bool msix)
{
if (msix) {
if (alx_enable_msix(alx))
alx->flags |= ALX_FLAG_USING_MSIX;
}
if (!(alx->flags & ALX_FLAG_USING_MSIX)) {
alx->num_vec = 1;
if (!pci_enable_msi(alx->hw.pdev))
alx->flags |= ALX_FLAG_USING_MSI;
}
@ -636,6 +749,12 @@ static void alx_init_intr(struct alx_priv *alx, bool msix)
static void alx_disable_advanced_intr(struct alx_priv *alx)
{
if (alx->flags & ALX_FLAG_USING_MSIX) {
kfree(alx->msix_entries);
pci_disable_msix(alx->hw.pdev);
alx->flags &= ~ALX_FLAG_USING_MSIX;
}
if (alx->flags & ALX_FLAG_USING_MSI) {
pci_disable_msi(alx->hw.pdev);
alx->flags &= ~ALX_FLAG_USING_MSI;
@ -645,22 +764,36 @@ static void alx_disable_advanced_intr(struct alx_priv *alx)
static void alx_irq_enable(struct alx_priv *alx)
{
struct alx_hw *hw = &alx->hw;
int i;
/* level-1 interrupt switch */
alx_write_mem32(hw, ALX_ISR, 0);
alx_write_mem32(hw, ALX_IMR, alx->int_mask);
alx_post_write(hw);
if (alx->flags & ALX_FLAG_USING_MSIX)
/* enable all msix irqs */
for (i = 0; i < alx->num_vec; i++)
alx_mask_msix(hw, i, false);
}
static void alx_irq_disable(struct alx_priv *alx)
{
struct alx_hw *hw = &alx->hw;
int i;
alx_write_mem32(hw, ALX_ISR, ALX_ISR_DIS);
alx_write_mem32(hw, ALX_IMR, 0);
alx_post_write(hw);
synchronize_irq(alx->hw.pdev->irq);
if (alx->flags & ALX_FLAG_USING_MSIX) {
for (i = 0; i < alx->num_vec; i++) {
alx_mask_msix(hw, i, true);
synchronize_irq(alx->msix_entries[i].vector);
}
} else {
synchronize_irq(alx->hw.pdev->irq);
}
}
static int alx_request_irq(struct alx_priv *alx)
@ -672,6 +805,17 @@ static int alx_request_irq(struct alx_priv *alx)
msi_ctrl = (hw->imt >> 1) << ALX_MSI_RETRANS_TM_SHIFT;
if (alx->flags & ALX_FLAG_USING_MSIX) {
alx_write_mem32(hw, ALX_MSI_RETRANS_TIMER, msi_ctrl);
err = alx_request_msix(alx);
if (!err)
goto out;
/* msix request failed, realloc resources */
alx_disable_advanced_intr(alx);
alx_init_intr(alx, false);
}
if (alx->flags & ALX_FLAG_USING_MSI) {
alx_write_mem32(hw, ALX_MSI_RETRANS_TIMER,
msi_ctrl | ALX_MSI_MASK_SEL_LINE);
@ -690,14 +834,23 @@ static int alx_request_irq(struct alx_priv *alx)
out:
if (!err)
alx_config_vector_mapping(alx);
else
netdev_err(alx->dev, "IRQ registration failed!\n");
return err;
}
static void alx_free_irq(struct alx_priv *alx)
{
struct pci_dev *pdev = alx->hw.pdev;
int i;
free_irq(pdev->irq, alx);
if (alx->flags & ALX_FLAG_USING_MSIX) {
/* we have only 2 vectors without multi queue support */
for (i = 0; i < 2; i++)
free_irq(alx->msix_entries[i].vector, alx);
} else {
free_irq(pdev->irq, alx);
}
alx_disable_advanced_intr(alx);
}
@ -1256,7 +1409,10 @@ static void alx_poll_controller(struct net_device *netdev)
{
struct alx_priv *alx = netdev_priv(netdev);
if (alx->flags & ALX_FLAG_USING_MSI)
if (alx->flags & ALX_FLAG_USING_MSIX) {
alx_intr_msix_misc(0, alx);
alx_intr_msix_ring(0, alx);
} else if (alx->flags & ALX_FLAG_USING_MSI)
alx_intr_msi(0, alx);
else
alx_intr_legacy(0, alx);