From 58294ee65afbc01d8e42cfc5e4033af255d378e3 Mon Sep 17 00:00:00 2001 From: Andrew Rybchenko Date: Tue, 29 Nov 2016 16:19:09 +0000 Subject: [PATCH] net/sfc: support event queue Signed-off-by: Andrew Rybchenko Reviewed-by: Andy Moreton Reviewed-by: Ferruh Yigit --- drivers/net/sfc/Makefile | 1 + drivers/net/sfc/sfc.c | 17 ++ drivers/net/sfc/sfc.h | 7 + drivers/net/sfc/sfc_ev.c | 482 +++++++++++++++++++++++++++++++++++++++ drivers/net/sfc/sfc_ev.h | 139 +++++++++++ 5 files changed, 646 insertions(+) create mode 100644 drivers/net/sfc/sfc_ev.c create mode 100644 drivers/net/sfc/sfc_ev.h diff --git a/drivers/net/sfc/Makefile b/drivers/net/sfc/Makefile index d19497c38b..3286fd88b9 100644 --- a/drivers/net/sfc/Makefile +++ b/drivers/net/sfc/Makefile @@ -83,6 +83,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc_kvargs.c SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc.c SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc_mcdi.c SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc_intr.c +SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc_ev.c VPATH += $(SRCDIR)/base diff --git a/drivers/net/sfc/sfc.c b/drivers/net/sfc/sfc.c index 8d18def2d1..38a7e5297b 100644 --- a/drivers/net/sfc/sfc.c +++ b/drivers/net/sfc/sfc.c @@ -36,6 +36,7 @@ #include "sfc.h" #include "sfc_log.h" +#include "sfc_ev.h" int @@ -261,10 +262,17 @@ sfc_start(struct sfc_adapter *sa) if (rc != 0) goto fail_intr_start; + rc = sfc_ev_start(sa); + if (rc != 0) + goto fail_ev_start; + sa->state = SFC_ADAPTER_STARTED; sfc_log_init(sa, "done"); return 0; +fail_ev_start: + sfc_intr_stop(sa); + fail_intr_start: efx_nic_fini(sa->nic); @@ -297,6 +305,7 @@ sfc_stop(struct sfc_adapter *sa) sa->state = SFC_ADAPTER_STOPPING; + sfc_ev_stop(sa); sfc_intr_stop(sa); efx_nic_fini(sa->nic); @@ -324,10 +333,17 @@ sfc_configure(struct sfc_adapter *sa) if (rc != 0) goto fail_intr_init; + rc = sfc_ev_init(sa); + if (rc != 0) + goto fail_ev_init; + sa->state = SFC_ADAPTER_CONFIGURED; sfc_log_init(sa, "done"); return 0; +fail_ev_init: + sfc_intr_fini(sa); + fail_intr_init: fail_check_conf: sa->state = SFC_ADAPTER_INITIALIZED; @@ -345,6 +361,7 @@ sfc_close(struct sfc_adapter *sa) SFC_ASSERT(sa->state == SFC_ADAPTER_CONFIGURED); sa->state = SFC_ADAPTER_CLOSING; + sfc_ev_fini(sa); sfc_intr_fini(sa); sa->state = SFC_ADAPTER_INITIALIZED; diff --git a/drivers/net/sfc/sfc.h b/drivers/net/sfc/sfc.h index 44a98f4c7b..f6d0b81b6f 100644 --- a/drivers/net/sfc/sfc.h +++ b/drivers/net/sfc/sfc.h @@ -116,6 +116,8 @@ struct sfc_intr { efx_intr_type_t type; }; +struct sfc_evq_info; + /* Adapter private data */ struct sfc_adapter { /* @@ -140,6 +142,11 @@ struct sfc_adapter { unsigned int rxq_max; unsigned int txq_max; + + unsigned int evq_count; + struct sfc_evq_info *evq_info; + + unsigned int mgmt_evq_index; }; /* diff --git a/drivers/net/sfc/sfc_ev.c b/drivers/net/sfc/sfc_ev.c new file mode 100644 index 0000000000..2ee579ad2c --- /dev/null +++ b/drivers/net/sfc/sfc_ev.c @@ -0,0 +1,482 @@ +/*- + * Copyright (c) 2016 Solarflare Communications Inc. + * All rights reserved. + * + * This software was jointly developed between OKTET Labs (under contract + * for Solarflare) and Solarflare Communications, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "efx.h" + +#include "sfc.h" +#include "sfc_debug.h" +#include "sfc_log.h" +#include "sfc_ev.h" + + +/* Initial delay when waiting for event queue init complete event */ +#define SFC_EVQ_INIT_BACKOFF_START_US (1) +/* Maximum delay between event queue polling attempts */ +#define SFC_EVQ_INIT_BACKOFF_MAX_US (10 * 1000) +/* Event queue init approx timeout */ +#define SFC_EVQ_INIT_TIMEOUT_US (2 * US_PER_S) + + +static boolean_t +sfc_ev_initialized(void *arg) +{ + struct sfc_evq *evq = arg; + + /* Init done events may be duplicated on SFN7xxx (SFC bug 31631) */ + SFC_ASSERT(evq->init_state == SFC_EVQ_STARTING || + evq->init_state == SFC_EVQ_STARTED); + + evq->init_state = SFC_EVQ_STARTED; + + return B_FALSE; +} + +static boolean_t +sfc_ev_rx(void *arg, __rte_unused uint32_t label, __rte_unused uint32_t id, + __rte_unused uint32_t size, __rte_unused uint16_t flags) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected Rx event", evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_tx(void *arg, __rte_unused uint32_t label, __rte_unused uint32_t id) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected Tx event", evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_exception(void *arg, __rte_unused uint32_t code, + __rte_unused uint32_t data) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected exception event", + evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_rxq_flush_done(void *arg, __rte_unused uint32_t rxq_hw_index) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected Rx flush done event", + evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_rxq_flush_failed(void *arg, __rte_unused uint32_t rxq_hw_index) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected Rx flush failed event", + evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_txq_flush_done(void *arg, __rte_unused uint32_t txq_hw_index) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected Tx flush done event", + evq->evq_index); + return B_TRUE; +} + +static boolean_t +sfc_ev_software(void *arg, uint16_t magic) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected software event magic=%#.4x", + evq->evq_index, magic); + return B_TRUE; +} + +static boolean_t +sfc_ev_sram(void *arg, uint32_t code) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected SRAM event code=%u", + evq->evq_index, code); + return B_TRUE; +} + +static boolean_t +sfc_ev_wake_up(void *arg, uint32_t index) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected wake up event index=%u", + evq->evq_index, index); + return B_TRUE; +} + +static boolean_t +sfc_ev_timer(void *arg, uint32_t index) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected timer event index=%u", + evq->evq_index, index); + return B_TRUE; +} + +static boolean_t +sfc_ev_link_change(void *arg, __rte_unused efx_link_mode_t link_mode) +{ + struct sfc_evq *evq = arg; + + sfc_err(evq->sa, "EVQ %u unexpected link change", + evq->evq_index); + return B_TRUE; +} + +static const efx_ev_callbacks_t sfc_ev_callbacks = { + .eec_initialized = sfc_ev_initialized, + .eec_rx = sfc_ev_rx, + .eec_tx = sfc_ev_tx, + .eec_exception = sfc_ev_exception, + .eec_rxq_flush_done = sfc_ev_rxq_flush_done, + .eec_rxq_flush_failed = sfc_ev_rxq_flush_failed, + .eec_txq_flush_done = sfc_ev_txq_flush_done, + .eec_software = sfc_ev_software, + .eec_sram = sfc_ev_sram, + .eec_wake_up = sfc_ev_wake_up, + .eec_timer = sfc_ev_timer, + .eec_link_change = sfc_ev_link_change, +}; + + +void +sfc_ev_qpoll(struct sfc_evq *evq) +{ + SFC_ASSERT(evq->init_state == SFC_EVQ_STARTED || + evq->init_state == SFC_EVQ_STARTING); + + /* Synchronize the DMA memory for reading not required */ + + efx_ev_qpoll(evq->common, &evq->read_ptr, &sfc_ev_callbacks, evq); + + /* Poll-mode driver does not re-prime the event queue for interrupts */ +} + +int +sfc_ev_qprime(struct sfc_evq *evq) +{ + SFC_ASSERT(evq->init_state == SFC_EVQ_STARTED); + return efx_ev_qprime(evq->common, evq->read_ptr); +} + +int +sfc_ev_qstart(struct sfc_adapter *sa, unsigned int sw_index) +{ + const struct sfc_evq_info *evq_info; + struct sfc_evq *evq; + efsys_mem_t *esmp; + unsigned int total_delay_us; + unsigned int delay_us; + int rc; + + sfc_log_init(sa, "sw_index=%u", sw_index); + + evq_info = &sa->evq_info[sw_index]; + evq = evq_info->evq; + esmp = &evq->mem; + + /* Clear all events */ + (void)memset((void *)esmp->esm_base, 0xff, + EFX_EVQ_SIZE(evq_info->entries)); + + /* Create the common code event queue */ + rc = efx_ev_qcreate(sa->nic, sw_index, esmp, evq_info->entries, + 0 /* unused on EF10 */, 0, + EFX_EVQ_FLAGS_TYPE_THROUGHPUT | + EFX_EVQ_FLAGS_NOTIFY_DISABLED, + &evq->common); + if (rc != 0) + goto fail_ev_qcreate; + + evq->init_state = SFC_EVQ_STARTING; + + /* Wait for the initialization event */ + total_delay_us = 0; + delay_us = SFC_EVQ_INIT_BACKOFF_START_US; + do { + (void)sfc_ev_qpoll(evq); + + /* Check to see if the initialization complete indication + * posted by the hardware. + */ + if (evq->init_state == SFC_EVQ_STARTED) + goto done; + + /* Give event queue some time to init */ + rte_delay_us(delay_us); + + total_delay_us += delay_us; + + /* Exponential backoff */ + delay_us *= 2; + if (delay_us > SFC_EVQ_INIT_BACKOFF_MAX_US) + delay_us = SFC_EVQ_INIT_BACKOFF_MAX_US; + + } while (total_delay_us < SFC_EVQ_INIT_TIMEOUT_US); + + rc = ETIMEDOUT; + goto fail_timedout; + +done: + return 0; + +fail_timedout: + evq->init_state = SFC_EVQ_INITIALIZED; + efx_ev_qdestroy(evq->common); + +fail_ev_qcreate: + sfc_log_init(sa, "failed %d", rc); + return rc; +} + +void +sfc_ev_qstop(struct sfc_adapter *sa, unsigned int sw_index) +{ + const struct sfc_evq_info *evq_info; + struct sfc_evq *evq; + + sfc_log_init(sa, "sw_index=%u", sw_index); + + SFC_ASSERT(sw_index < sa->evq_count); + + evq_info = &sa->evq_info[sw_index]; + evq = evq_info->evq; + + if (evq == NULL || evq->init_state != SFC_EVQ_STARTED) + return; + + evq->init_state = SFC_EVQ_INITIALIZED; + evq->read_ptr = 0; + evq->exception = B_FALSE; + + efx_ev_qdestroy(evq->common); +} + +int +sfc_ev_start(struct sfc_adapter *sa) +{ + int rc; + + sfc_log_init(sa, "entry"); + + rc = efx_ev_init(sa->nic); + if (rc != 0) + goto fail_ev_init; + + /* + * Rx/Tx event queues are started/stopped when corresponding queue + * is started/stopped. + */ + + return 0; + +fail_ev_init: + sfc_log_init(sa, "failed %d", rc); + return rc; +} + +void +sfc_ev_stop(struct sfc_adapter *sa) +{ + unsigned int sw_index; + + sfc_log_init(sa, "entry"); + + /* Make sure that all event queues are stopped */ + sw_index = sa->evq_count; + while (sw_index-- > 0) + sfc_ev_qstop(sa, sw_index); + + efx_ev_fini(sa->nic); +} + +int +sfc_ev_qinit(struct sfc_adapter *sa, unsigned int sw_index, + unsigned int entries, int socket_id) +{ + struct sfc_evq_info *evq_info; + struct sfc_evq *evq; + int rc; + + sfc_log_init(sa, "sw_index=%u", sw_index); + + evq_info = &sa->evq_info[sw_index]; + + SFC_ASSERT(rte_is_power_of_2(entries)); + SFC_ASSERT(entries <= evq_info->max_entries); + evq_info->entries = entries; + + evq = rte_zmalloc_socket("sfc-evq", sizeof(*evq), RTE_CACHE_LINE_SIZE, + socket_id); + if (evq == NULL) + return ENOMEM; + + evq->sa = sa; + evq->evq_index = sw_index; + + /* Allocate DMA space */ + rc = sfc_dma_alloc(sa, "evq", sw_index, EFX_EVQ_SIZE(evq_info->entries), + socket_id, &evq->mem); + if (rc != 0) + return rc; + + evq->init_state = SFC_EVQ_INITIALIZED; + + evq_info->evq = evq; + + return 0; +} + +void +sfc_ev_qfini(struct sfc_adapter *sa, unsigned int sw_index) +{ + struct sfc_evq *evq; + + sfc_log_init(sa, "sw_index=%u", sw_index); + + evq = sa->evq_info[sw_index].evq; + + SFC_ASSERT(evq->init_state == SFC_EVQ_INITIALIZED); + + sa->evq_info[sw_index].evq = NULL; + + sfc_dma_free(sa, &evq->mem); + + rte_free(evq); +} + +static int +sfc_ev_qinit_info(struct sfc_adapter *sa, unsigned int sw_index) +{ + struct sfc_evq_info *evq_info = &sa->evq_info[sw_index]; + unsigned int max_entries; + + sfc_log_init(sa, "sw_index=%u", sw_index); + + max_entries = sfc_evq_max_entries(sa, sw_index); + SFC_ASSERT(rte_is_power_of_2(max_entries)); + + evq_info->max_entries = max_entries; + + return 0; +} + +static void +sfc_ev_qfini_info(struct sfc_adapter *sa, unsigned int sw_index) +{ + sfc_log_init(sa, "sw_index=%u", sw_index); + + /* Nothing to cleanup */ +} + +int +sfc_ev_init(struct sfc_adapter *sa) +{ + int rc; + unsigned int sw_index; + + sfc_log_init(sa, "entry"); + + sa->evq_count = sfc_ev_qcount(sa); + sa->mgmt_evq_index = 0; + + /* Allocate EVQ info array */ + rc = ENOMEM; + sa->evq_info = rte_calloc_socket("sfc-evqs", sa->evq_count, + sizeof(struct sfc_evq_info), 0, + sa->socket_id); + if (sa->evq_info == NULL) + goto fail_evqs_alloc; + + for (sw_index = 0; sw_index < sa->evq_count; ++sw_index) { + rc = sfc_ev_qinit_info(sa, sw_index); + if (rc != 0) + goto fail_ev_qinit_info; + } + + /* + * Rx/Tx event queues are created/destroyed when corresponding + * Rx/Tx queue is created/destroyed. + */ + + return 0; + +fail_ev_qinit_info: + while (sw_index-- > 0) + sfc_ev_qfini_info(sa, sw_index); + + rte_free(sa->evq_info); + sa->evq_info = NULL; + +fail_evqs_alloc: + sa->evq_count = 0; + sfc_log_init(sa, "failed %d", rc); + return rc; +} + +void +sfc_ev_fini(struct sfc_adapter *sa) +{ + int sw_index; + + sfc_log_init(sa, "entry"); + + /* Cleanup all event queues */ + sw_index = sa->evq_count; + while (--sw_index >= 0) { + if (sa->evq_info[sw_index].evq != NULL) + sfc_ev_qfini(sa, sw_index); + sfc_ev_qfini_info(sa, sw_index); + } + + rte_free(sa->evq_info); + sa->evq_info = NULL; + sa->evq_count = 0; +} diff --git a/drivers/net/sfc/sfc_ev.h b/drivers/net/sfc/sfc_ev.h new file mode 100644 index 0000000000..11478e90a1 --- /dev/null +++ b/drivers/net/sfc/sfc_ev.h @@ -0,0 +1,139 @@ +/*- + * Copyright (c) 2016 Solarflare Communications Inc. + * All rights reserved. + * + * This software was jointly developed between OKTET Labs (under contract + * for Solarflare) and Solarflare Communications, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SFC_EV_H_ +#define _SFC_EV_H_ + +#include "efx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Number of entries in the management event queue */ +#define SFC_MGMT_EVQ_ENTRIES (EFX_EVQ_MINNEVS) + +struct sfc_adapter; + +enum sfc_evq_state { + SFC_EVQ_UNINITIALIZED = 0, + SFC_EVQ_INITIALIZED, + SFC_EVQ_STARTING, + SFC_EVQ_STARTED, + + SFC_EVQ_NSTATES +}; + +struct sfc_evq { + /* Used on datapath */ + efx_evq_t *common; + unsigned int read_ptr; + boolean_t exception; + efsys_mem_t mem; + + /* Not used on datapath */ + struct sfc_adapter *sa; + unsigned int evq_index; + enum sfc_evq_state init_state; +}; + +struct sfc_evq_info { + /* Maximum number of EVQ entries taken into account when buffer + * table space is allocated. + */ + unsigned int max_entries; + /* Real number of EVQ entries, less or equal to max_entries */ + unsigned int entries; + /* NUMA-aware EVQ data structure used on datapath */ + struct sfc_evq *evq; +}; + +/* + * Functions below define event queue to transmit/receive queue and vice + * versa mapping. + */ + +static inline unsigned int +sfc_ev_qcount(struct sfc_adapter *sa) +{ + const struct rte_eth_dev_data *dev_data = sa->eth_dev->data; + + /* + * One management EVQ for global events. + * Own EVQ for each Tx and Rx queue. + */ + return 1 + dev_data->nb_rx_queues + dev_data->nb_tx_queues; +} + +static inline unsigned int +sfc_evq_max_entries(struct sfc_adapter *sa, unsigned int sw_index) +{ + unsigned int max_entries; + + if (sw_index == sa->mgmt_evq_index) + max_entries = SFC_MGMT_EVQ_ENTRIES; + else if (sw_index <= sa->eth_dev->data->nb_rx_queues) + max_entries = EFX_RXQ_MAXNDESCS; + else + max_entries = efx_nic_cfg_get(sa->nic)->enc_txq_max_ndescs; + + return max_entries; +} + +static inline unsigned int +sfc_evq_index_by_rxq_sw_index(__rte_unused struct sfc_adapter *sa, + unsigned int rxq_sw_index) +{ + return 1 + rxq_sw_index; +} + +static inline unsigned int +sfc_evq_index_by_txq_sw_index(struct sfc_adapter *sa, unsigned int txq_sw_index) +{ + return 1 + sa->eth_dev->data->nb_rx_queues + txq_sw_index; +} + +int sfc_ev_init(struct sfc_adapter *sa); +void sfc_ev_fini(struct sfc_adapter *sa); +int sfc_ev_start(struct sfc_adapter *sa); +void sfc_ev_stop(struct sfc_adapter *sa); + +int sfc_ev_qinit(struct sfc_adapter *sa, unsigned int sw_index, + unsigned int entries, int socket_id); +void sfc_ev_qfini(struct sfc_adapter *sa, unsigned int sw_index); +int sfc_ev_qstart(struct sfc_adapter *sa, unsigned int sw_index); +void sfc_ev_qstop(struct sfc_adapter *sa, unsigned int sw_index); + +int sfc_ev_qprime(struct sfc_evq *evq); +void sfc_ev_qpoll(struct sfc_evq *evq); + +#ifdef __cplusplus +} +#endif +#endif /* _SFC_EV_H_ */ -- 2.20.1