linux/drivers/net/ethernet/meta/fbnic/fbnic_irq.c
Alexander Duyck 69684376ee eth: fbnic: Add link detection
Add basic support for detecting the link and reporting it at the netdev
layer. For now we will just use the values reporeted by the firmware as the
link configuration and assume that is the current configuration of the MAC
and PCS.

With this we start the stubbing out of the phylink interface that will be
used to provide the configuration interface for ethtool in a future patch
set.

The phylink interface isn't an exact fit. As such we are currently working
around several issues in this patch set that we plan to address in the
future such as:
1. Support for FEC
2. Support for multiple lanes to handle 50GbaseR2 vs 50GbaseR1
3. Support for BMC

CC: Russell King <linux@armlinux.org.uk>
CC: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Alexander Duyck <alexanderduyck@fb.com>
Link: https://patch.msgid.link/172079939835.1778861.5964790909718481811.stgit@ahduyck-xeon-server.home.arpa
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2024-07-15 12:50:43 -07:00

208 lines
5.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/pci.h>
#include <linux/types.h>
#include "fbnic.h"
#include "fbnic_netdev.h"
#include "fbnic_txrx.h"
static irqreturn_t fbnic_fw_msix_intr(int __always_unused irq, void *data)
{
struct fbnic_dev *fbd = (struct fbnic_dev *)data;
fbnic_mbx_poll(fbd);
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0), 1u << FBNIC_FW_MSIX_ENTRY);
return IRQ_HANDLED;
}
/**
* fbnic_fw_enable_mbx - Configure and initialize Firmware Mailbox
* @fbd: Pointer to device to initialize
*
* This function will initialize the firmware mailbox rings, enable the IRQ
* and initialize the communication between the Firmware and the host. The
* firmware is expected to respond to the initialization by sending an
* interrupt essentially notifying the host that it has seen the
* initialization and is now synced up.
*
* Return: non-zero on failure.
**/
int fbnic_fw_enable_mbx(struct fbnic_dev *fbd)
{
u32 vector = fbd->fw_msix_vector;
int err;
/* Request the IRQ for FW Mailbox vector. */
err = request_threaded_irq(vector, NULL, &fbnic_fw_msix_intr,
IRQF_ONESHOT, dev_name(fbd->dev), fbd);
if (err)
return err;
/* Initialize mailbox and attempt to poll it into ready state */
fbnic_mbx_init(fbd);
err = fbnic_mbx_poll_tx_ready(fbd);
if (err) {
dev_warn(fbd->dev, "FW mailbox did not enter ready state\n");
free_irq(vector, fbd);
return err;
}
/* Enable interrupts */
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0), 1u << FBNIC_FW_MSIX_ENTRY);
return 0;
}
/**
* fbnic_fw_disable_mbx - Disable mailbox and place it in standby state
* @fbd: Pointer to device to disable
*
* This function will disable the mailbox interrupt, free any messages still
* in the mailbox and place it into a standby state. The firmware is
* expected to see the update and assume that the host is in the reset state.
**/
void fbnic_fw_disable_mbx(struct fbnic_dev *fbd)
{
/* Disable interrupt and free vector */
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(0), 1u << FBNIC_FW_MSIX_ENTRY);
/* Free the vector */
free_irq(fbd->fw_msix_vector, fbd);
/* Make sure disabling logs message is sent, must be done here to
* avoid risk of completing without a running interrupt.
*/
fbnic_mbx_flush_tx(fbd);
/* Reset the mailboxes to the initialized state */
fbnic_mbx_clean(fbd);
}
static irqreturn_t fbnic_pcs_msix_intr(int __always_unused irq, void *data)
{
struct fbnic_dev *fbd = data;
struct fbnic_net *fbn;
if (fbd->mac->pcs_get_link_event(fbd) == FBNIC_LINK_EVENT_NONE) {
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0),
1u << FBNIC_PCS_MSIX_ENTRY);
return IRQ_HANDLED;
}
fbn = netdev_priv(fbd->netdev);
phylink_pcs_change(&fbn->phylink_pcs, false);
return IRQ_HANDLED;
}
/**
* fbnic_pcs_irq_enable - Configure the MAC to enable it to advertise link
* @fbd: Pointer to device to initialize
*
* This function provides basic bringup for the MAC/PCS IRQ. For now the IRQ
* will remain disabled until we start the MAC/PCS/PHY logic via phylink.
*
* Return: non-zero on failure.
**/
int fbnic_pcs_irq_enable(struct fbnic_dev *fbd)
{
u32 vector = fbd->pcs_msix_vector;
int err;
/* Request the IRQ for MAC link vector.
* Map MAC cause to it, and unmask it
*/
err = request_irq(vector, &fbnic_pcs_msix_intr, 0,
fbd->netdev->name, fbd);
if (err)
return err;
fbnic_wr32(fbd, FBNIC_INTR_MSIX_CTRL(FBNIC_INTR_MSIX_CTRL_PCS_IDX),
FBNIC_PCS_MSIX_ENTRY | FBNIC_INTR_MSIX_CTRL_ENABLE);
return 0;
}
/**
* fbnic_pcs_irq_disable - Teardown the MAC IRQ to prepare for stopping
* @fbd: Pointer to device that is stopping
*
* This function undoes the work done in fbnic_pcs_irq_enable and prepares
* the device to no longer receive traffic on the host interface.
**/
void fbnic_pcs_irq_disable(struct fbnic_dev *fbd)
{
/* Disable interrupt */
fbnic_wr32(fbd, FBNIC_INTR_MSIX_CTRL(FBNIC_INTR_MSIX_CTRL_PCS_IDX),
FBNIC_PCS_MSIX_ENTRY);
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(0), 1u << FBNIC_PCS_MSIX_ENTRY);
/* Free the vector */
free_irq(fbd->pcs_msix_vector, fbd);
}
int fbnic_request_irq(struct fbnic_dev *fbd, int nr, irq_handler_t handler,
unsigned long flags, const char *name, void *data)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int irq = pci_irq_vector(pdev, nr);
if (irq < 0)
return irq;
return request_irq(irq, handler, flags, name, data);
}
void fbnic_free_irq(struct fbnic_dev *fbd, int nr, void *data)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int irq = pci_irq_vector(pdev, nr);
if (irq < 0)
return;
free_irq(irq, data);
}
void fbnic_free_irqs(struct fbnic_dev *fbd)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
fbd->pcs_msix_vector = 0;
fbd->fw_msix_vector = 0;
fbd->num_irqs = 0;
pci_free_irq_vectors(pdev);
}
int fbnic_alloc_irqs(struct fbnic_dev *fbd)
{
unsigned int wanted_irqs = FBNIC_NON_NAPI_VECTORS;
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int num_irqs;
wanted_irqs += min_t(unsigned int, num_online_cpus(), FBNIC_MAX_RXQS);
num_irqs = pci_alloc_irq_vectors(pdev, FBNIC_NON_NAPI_VECTORS + 1,
wanted_irqs, PCI_IRQ_MSIX);
if (num_irqs < 0) {
dev_err(fbd->dev, "Failed to allocate MSI-X entries\n");
return num_irqs;
}
if (num_irqs < wanted_irqs)
dev_warn(fbd->dev, "Allocated %d IRQs, expected %d\n",
num_irqs, wanted_irqs);
fbd->num_irqs = num_irqs;
fbd->pcs_msix_vector = pci_irq_vector(pdev, FBNIC_PCS_MSIX_ENTRY);
fbd->fw_msix_vector = pci_irq_vector(pdev, FBNIC_FW_MSIX_ENTRY);
return 0;
}