From 3b809c27b1fec82a975ddbd597c68cdbbc709e8d Mon Sep 17 00:00:00 2001 From: Andrew Rybchenko Date: Thu, 15 Dec 2016 12:50:57 +0000 Subject: [PATCH] net/sfc: support link status change interrupt Signed-off-by: Andrew Rybchenko Reviewed-by: Andrew Lee Reviewed-by: Robert Stonehouse --- doc/guides/nics/features/sfc_efx.ini | 1 + doc/guides/nics/sfc_efx.rst | 4 +- drivers/net/sfc/sfc.c | 4 +- drivers/net/sfc/sfc.h | 4 + drivers/net/sfc/sfc_ethdev.c | 1 + drivers/net/sfc/sfc_ev.c | 30 +++- drivers/net/sfc/sfc_intr.c | 205 +++++++++++++++++++++++++++ 7 files changed, 243 insertions(+), 6 deletions(-) diff --git a/doc/guides/nics/features/sfc_efx.ini b/doc/guides/nics/features/sfc_efx.ini index 25472f8612..693d35ed74 100644 --- a/doc/guides/nics/features/sfc_efx.ini +++ b/doc/guides/nics/features/sfc_efx.ini @@ -5,6 +5,7 @@ ; [Features] Link status = Y +Link status event = Y Flow control = Y L3 checksum offload = P L4 checksum offload = P diff --git a/doc/guides/nics/sfc_efx.rst b/doc/guides/nics/sfc_efx.rst index 06b0c14814..58f824290f 100644 --- a/doc/guides/nics/sfc_efx.rst +++ b/doc/guides/nics/sfc_efx.rst @@ -44,7 +44,7 @@ SFC EFX PMD has support for: - Multiple transmit and receive queues -- Link state information +- Link state information including link status change interrupt - IPv4/IPv6 TCP/UDP transmit checksum offload @@ -61,8 +61,6 @@ Non-supported Features The features not yet supported include: -- Link status change interrupt - - Receive queue interupts - Priority-based flow control diff --git a/drivers/net/sfc/sfc.c b/drivers/net/sfc/sfc.c index 4757e399fa..6b3dc48a42 100644 --- a/drivers/net/sfc/sfc.c +++ b/drivers/net/sfc/sfc.c @@ -116,7 +116,9 @@ sfc_check_conf(struct sfc_adapter *sa) rc = EINVAL; } - if (conf->intr_conf.lsc != 0) { + if ((conf->intr_conf.lsc != 0) && + (sa->intr.type != EFX_INTR_LINE) && + (sa->intr.type != EFX_INTR_MESSAGE)) { sfc_err(sa, "Link status change interrupt not supported"); rc = EINVAL; } diff --git a/drivers/net/sfc/sfc.h b/drivers/net/sfc/sfc.h index 913da8c516..83c44302a2 100644 --- a/drivers/net/sfc/sfc.h +++ b/drivers/net/sfc/sfc.h @@ -115,6 +115,8 @@ struct sfc_mcdi { struct sfc_intr { efx_intr_type_t type; + rte_intr_callback_fn handler; + boolean_t lsc_intr; }; struct sfc_evq_info; @@ -122,6 +124,8 @@ struct sfc_rxq_info; struct sfc_txq_info; struct sfc_port { + unsigned int lsc_seq; + unsigned int flow_ctrl; boolean_t flow_ctrl_autoneg; size_t pdu; diff --git a/drivers/net/sfc/sfc_ethdev.c b/drivers/net/sfc/sfc_ethdev.c index 0fb5e32714..4308dbb84c 100644 --- a/drivers/net/sfc/sfc_ethdev.c +++ b/drivers/net/sfc/sfc_ethdev.c @@ -681,6 +681,7 @@ static struct eth_driver sfc_efx_pmd = { .pci_drv = { .id_table = pci_id_sfc_efx_map, .drv_flags = + RTE_PCI_DRV_INTR_LSC | RTE_PCI_DRV_NEED_MAPPING, .probe = rte_eth_dev_pci_probe, .remove = rte_eth_dev_pci_remove, diff --git a/drivers/net/sfc/sfc_ev.c b/drivers/net/sfc/sfc_ev.c index de428452cb..c788986801 100644 --- a/drivers/net/sfc/sfc_ev.c +++ b/drivers/net/sfc/sfc_ev.c @@ -287,11 +287,25 @@ sfc_ev_link_change(void *arg, efx_link_mode_t link_mode) struct sfc_adapter *sa = evq->sa; struct rte_eth_link *dev_link = &sa->eth_dev->data->dev_link; struct rte_eth_link new_link; + uint64_t new_link_u64; + uint64_t old_link_u64; EFX_STATIC_ASSERT(sizeof(*dev_link) == sizeof(rte_atomic64_t)); sfc_port_link_mode_to_info(link_mode, &new_link); - rte_atomic64_set((rte_atomic64_t *)dev_link, *(uint64_t *)&new_link); + + new_link_u64 = *(uint64_t *)&new_link; + do { + old_link_u64 = rte_atomic64_read((rte_atomic64_t *)dev_link); + if (old_link_u64 == new_link_u64) + break; + + if (rte_atomic64_cmpset((volatile uint64_t *)dev_link, + old_link_u64, new_link_u64)) { + evq->sa->port.lsc_seq++; + break; + } + } while (B_TRUE); return B_FALSE; } @@ -521,6 +535,12 @@ sfc_ev_start(struct sfc_adapter *sa) if (rc != 0) goto fail_mgmt_evq_start; + if (sa->intr.lsc_intr) { + rc = sfc_ev_qprime(sa->evq_info[sa->mgmt_evq_index].evq); + if (rc != 0) + goto fail_evq0_prime; + } + rte_spinlock_unlock(&sa->mgmt_evq_lock); /* @@ -538,6 +558,9 @@ sfc_ev_start(struct sfc_adapter *sa) return 0; +fail_evq0_prime: + sfc_ev_qstop(sa, 0); + fail_mgmt_evq_start: rte_spinlock_unlock(&sa->mgmt_evq_lock); efx_ev_fini(sa->nic); @@ -639,7 +662,10 @@ sfc_ev_qinit_info(struct sfc_adapter *sa, unsigned int sw_index) SFC_ASSERT(rte_is_power_of_2(max_entries)); evq_info->max_entries = max_entries; - evq_info->flags = sa->evq_flags | EFX_EVQ_FLAGS_NOTIFY_DISABLED; + evq_info->flags = sa->evq_flags | + ((sa->intr.lsc_intr && sw_index == sa->mgmt_evq_index) ? + EFX_EVQ_FLAGS_NOTIFY_INTERRUPT : + EFX_EVQ_FLAGS_NOTIFY_DISABLED); return 0; } diff --git a/drivers/net/sfc/sfc_intr.c b/drivers/net/sfc/sfc_intr.c index 622b7cba80..d06980ecbb 100644 --- a/drivers/net/sfc/sfc_intr.c +++ b/drivers/net/sfc/sfc_intr.c @@ -27,10 +27,130 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* + * At the momemt of writing DPDK v16.07 has notion of two types of + * interrupts: LSC (link status change) and RXQ (receive indication). + * It allows to register interrupt callback for entire device which is + * not intended to be used for receive indication (i.e. link status + * change indication only). The handler has no information which HW + * interrupt has triggered it, so we don't know which event queue should + * be polled/reprimed (except qmask in the case of legacy line interrupt). + */ + +#include +#include + #include "efx.h" #include "sfc.h" #include "sfc_log.h" +#include "sfc_ev.h" + +static void +sfc_intr_handle_mgmt_evq(struct sfc_adapter *sa) +{ + struct sfc_evq *evq; + + rte_spinlock_lock(&sa->mgmt_evq_lock); + + evq = sa->evq_info[sa->mgmt_evq_index].evq; + + if (evq->init_state != SFC_EVQ_STARTED) { + sfc_log_init(sa, "interrupt on stopped EVQ %u", evq->evq_index); + } else { + sfc_ev_qpoll(evq); + + if (sfc_ev_qprime(evq) != 0) + sfc_err(sa, "cannot prime EVQ %u", evq->evq_index); + } + + rte_spinlock_unlock(&sa->mgmt_evq_lock); +} + +static void +sfc_intr_line_handler(struct rte_intr_handle *intr_handle, void *cb_arg) +{ + struct sfc_adapter *sa = (struct sfc_adapter *)cb_arg; + efx_nic_t *enp = sa->nic; + boolean_t fatal; + uint32_t qmask; + unsigned int lsc_seq = sa->port.lsc_seq; + + sfc_log_init(sa, "entry"); + + if (sa->state != SFC_ADAPTER_STARTED && + sa->state != SFC_ADAPTER_STARTING && + sa->state != SFC_ADAPTER_STOPPING) { + sfc_log_init(sa, + "interrupt on stopped adapter, don't reenable"); + goto exit; + } + + efx_intr_status_line(enp, &fatal, &qmask); + if (fatal) { + (void)efx_intr_disable(enp); + (void)efx_intr_fatal(enp); + sfc_err(sa, "fatal, interrupts disabled"); + goto exit; + } + + if (qmask & (1 << sa->mgmt_evq_index)) + sfc_intr_handle_mgmt_evq(sa); + + if (rte_intr_enable(intr_handle) != 0) + sfc_err(sa, "cannot reenable interrupts"); + + sfc_log_init(sa, "done"); + +exit: + if (lsc_seq != sa->port.lsc_seq) { + sfc_info(sa, "link status change event: link %s", + sa->eth_dev->data->dev_link.link_status ? + "UP" : "DOWN"); + _rte_eth_dev_callback_process(sa->eth_dev, + RTE_ETH_EVENT_INTR_LSC, NULL); + } +} + +static void +sfc_intr_message_handler(struct rte_intr_handle *intr_handle, void *cb_arg) +{ + struct sfc_adapter *sa = (struct sfc_adapter *)cb_arg; + efx_nic_t *enp = sa->nic; + boolean_t fatal; + unsigned int lsc_seq = sa->port.lsc_seq; + + sfc_log_init(sa, "entry"); + + if (sa->state != SFC_ADAPTER_STARTED && + sa->state != SFC_ADAPTER_STARTING && + sa->state != SFC_ADAPTER_STOPPING) { + sfc_log_init(sa, "adapter not-started, don't reenable"); + goto exit; + } + + efx_intr_status_message(enp, sa->mgmt_evq_index, &fatal); + if (fatal) { + (void)efx_intr_disable(enp); + (void)efx_intr_fatal(enp); + sfc_err(sa, "fatal, interrupts disabled"); + goto exit; + } + + sfc_intr_handle_mgmt_evq(sa); + + if (rte_intr_enable(intr_handle) != 0) + sfc_err(sa, "cannot reenable interrupts"); + + sfc_log_init(sa, "done"); + +exit: + if (lsc_seq != sa->port.lsc_seq) { + sfc_info(sa, "link status change event"); + _rte_eth_dev_callback_process(sa->eth_dev, + RTE_ETH_EVENT_INTR_LSC, NULL); + } +} int sfc_intr_start(struct sfc_adapter *sa) @@ -56,11 +176,49 @@ sfc_intr_start(struct sfc_adapter *sa) pci_dev = SFC_DEV_TO_PCI(sa->eth_dev); intr_handle = &pci_dev->intr_handle; + if (intr->handler != NULL) { + sfc_log_init(sa, "rte_intr_callback_register"); + rc = rte_intr_callback_register(intr_handle, intr->handler, + (void *)sa); + if (rc != 0) { + sfc_err(sa, + "cannot register interrupt handler (rc=%d)", + rc); + /* + * Convert error code from negative returned by RTE API + * to positive used in the driver. + */ + rc = -rc; + goto fail_rte_intr_cb_reg; + } + + sfc_log_init(sa, "rte_intr_enable"); + rc = rte_intr_enable(intr_handle); + if (rc != 0) { + sfc_err(sa, "cannot enable interrupts (rc=%d)", rc); + /* + * Convert error code from negative returned by RTE API + * to positive used in the driver. + */ + rc = -rc; + goto fail_rte_intr_enable; + } + + sfc_log_init(sa, "efx_intr_enable"); + efx_intr_enable(sa->nic); + } + sfc_log_init(sa, "done type=%u max_intr=%d nb_efd=%u vec=%p", intr_handle->type, intr_handle->max_intr, intr_handle->nb_efd, intr_handle->intr_vec); return 0; +fail_rte_intr_enable: + rte_intr_callback_unregister(intr_handle, intr->handler, (void *)sa); + +fail_rte_intr_cb_reg: + efx_intr_fini(sa->nic); + fail_intr_init: sfc_log_init(sa, "failed %d", rc); return rc; @@ -69,8 +227,30 @@ fail_intr_init: void sfc_intr_stop(struct sfc_adapter *sa) { + struct sfc_intr *intr = &sa->intr; + struct rte_pci_device *pci_dev = SFC_DEV_TO_PCI(sa->eth_dev); + sfc_log_init(sa, "entry"); + if (intr->handler != NULL) { + struct rte_intr_handle *intr_handle; + int rc; + + efx_intr_disable(sa->nic); + + intr_handle = &pci_dev->intr_handle; + if (rte_intr_disable(intr_handle) != 0) + sfc_err(sa, "cannot disable interrupts"); + + while ((rc = rte_intr_callback_unregister(intr_handle, + intr->handler, (void *)sa)) == -EAGAIN) + ; + if (rc != 1) + sfc_err(sa, + "cannot unregister interrupt handler %d", + rc); + } + efx_intr_fini(sa->nic); sfc_log_init(sa, "done"); @@ -79,8 +259,33 @@ sfc_intr_stop(struct sfc_adapter *sa) int sfc_intr_init(struct sfc_adapter *sa) { + struct sfc_intr *intr = &sa->intr; + sfc_log_init(sa, "entry"); + intr->handler = NULL; + intr->lsc_intr = (sa->eth_dev->data->dev_conf.intr_conf.lsc != 0); + if (!intr->lsc_intr) { + sfc_info(sa, "LSC tracking using interrupts is disabled"); + goto done; + } + + switch (intr->type) { + case EFX_INTR_MESSAGE: + intr->handler = sfc_intr_message_handler; + break; + case EFX_INTR_LINE: + intr->handler = sfc_intr_line_handler; + break; + case EFX_INTR_INVALID: + sfc_warn(sa, "interrupts are not supported"); + break; + default: + sfc_panic(sa, "unexpected EFX interrupt type %u\n", intr->type); + break; + } + +done: sfc_log_init(sa, "done"); return 0; } -- 2.20.1