[dpdk-dev,32/56] net/sfc: implement driver operation to init device on attach

Message ID 1479740470-6723-33-git-send-email-arybchenko@solarflare.com (mailing list archive)
State Changes Requested, archived
Delegated to: Ferruh Yigit
Headers

Checks

Context Check Description
checkpatch/checkpatch warning coding style issues

Commit Message

Andrew Rybchenko Nov. 21, 2016, 3 p.m. UTC
  The setup and configuration of the PMD is not performance sensitive,
but is not thread safe either. It is possible that the multiple
read/writes during PMD setup and configuration could be corrupted
in a multi-thread environment.  Since this is not performance
sensitive, the developer can choose to add their own layer to provide
thread-safe setup and configuration. It is expected that, in most
applications, the initial configuration of the network ports would be
done by a single thread at startup.

Reviewed-by: Andy Moreton <amoreton@solarflare.com>
Signed-off-by: Andrew Rybchenko <arybchenko@solarflare.com>
---
 drivers/net/sfc/efx/Makefile     |   2 +
 drivers/net/sfc/efx/sfc.c        | 227 +++++++++++++++++++++++++++++++++++++++
 drivers/net/sfc/efx/sfc.h        | 100 +++++++++++++++++
 drivers/net/sfc/efx/sfc_debug.h  |  12 +++
 drivers/net/sfc/efx/sfc_ethdev.c |  52 ++++++++-
 drivers/net/sfc/efx/sfc_mcdi.c   | 197 +++++++++++++++++++++++++++++++++
 6 files changed, 589 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/sfc/efx/sfc.c
 create mode 100644 drivers/net/sfc/efx/sfc_mcdi.c
  

Comments

Ferruh Yigit Nov. 23, 2016, 3:26 p.m. UTC | #1
On 11/21/2016 3:00 PM, Andrew Rybchenko wrote:
> The setup and configuration of the PMD is not performance sensitive,
> but is not thread safe either. It is possible that the multiple
> read/writes during PMD setup and configuration could be corrupted
> in a multi-thread environment.  

Right, this is not thread-safe, but this is valid for all PMDs, it is
not expected to have initialization in multi-threaded environment, that
said so, synchronization also won't hurt, as you said this is not fast
path, just may not be necessary.

> Since this is not performance
> sensitive, the developer can choose to add their own layer to provide
> thread-safe setup and configuration. It is expected that, in most
> applications, the initial configuration of the network ports would be
> done by a single thread at startup.
> 
> Reviewed-by: Andy Moreton <amoreton@solarflare.com>
> Signed-off-by: Andrew Rybchenko <arybchenko@solarflare.com>

<...>

> diff --git a/drivers/net/sfc/efx/sfc_ethdev.c b/drivers/net/sfc/efx/sfc_ethdev.c
> index 0deff07..e5b609c 100644
> --- a/drivers/net/sfc/efx/sfc_ethdev.c
> +++ b/drivers/net/sfc/efx/sfc_ethdev.c
> @@ -31,6 +31,8 @@
>  #include <rte_ethdev.h>
>  #include <rte_pci.h>
>  
> +#include "efx.h"
> +
>  #include "sfc.h"
>  #include "sfc_debug.h"
>  #include "sfc_log.h"
> @@ -55,6 +57,8 @@ sfc_eth_dev_init(struct rte_eth_dev *dev)
>  	struct sfc_adapter *sa = dev->data->dev_private;
>  	struct rte_pci_device *pci_dev = dev->pci_dev;
>  	int rc;
> +	const efx_nic_cfg_t *encp;
> +	const struct ether_addr *from;
>  
>  	/* Required for logging */
>  	sa->eth_dev = dev;
> @@ -73,11 +77,43 @@ sfc_eth_dev_init(struct rte_eth_dev *dev)
>  
>  	sfc_log_init(sa, "entry");
>  
> +	dev->data->mac_addrs = rte_zmalloc("sfc", ETHER_ADDR_LEN, 0);
> +	if (dev->data->mac_addrs == NULL) {
> +		rc = ENOMEM;
> +		goto fail_mac_addrs;
> +	}
> +
> +	sfc_adapter_lock_init(sa);
> +	sfc_adapter_lock(sa);
> +
> +	sfc_log_init(sa, "attaching");
> +	rc = sfc_attach(sa);
> +	if (rc != 0)
> +		goto fail_attach;
> +
> +	encp = efx_nic_cfg_get(sa->nic);
> +
> +	/*
> +	 * The arguments are really reverse order in comparison to
> +	 * Linux kernel. Copy from NIC config to Ethernet device data.
> +	 */

Yes it is J, and agreed this is confusing.

> +	from = (const struct ether_addr *)(encp->enc_mac_addr);
> +	ether_addr_copy(from, &dev->data->mac_addrs[0]);
> +

<...>
  
Andrew Rybchenko Nov. 24, 2016, 2:58 p.m. UTC | #2
On 11/23/2016 06:26 PM, Ferruh Yigit wrote:
> On 11/21/2016 3:00 PM, Andrew Rybchenko wrote:
>> The setup and configuration of the PMD is not performance sensitive,
>> but is not thread safe either. It is possible that the multiple
>> read/writes during PMD setup and configuration could be corrupted
>> in a multi-thread environment.
> Right, this is not thread-safe, but this is valid for all PMDs, it is
> not expected to have initialization in multi-threaded environment, that
> said so, synchronization also won't hurt, as you said this is not fast
> path, just may not be necessary.

In fact further patches really need the lock and it should be introduced and
maintained from the very beginning. I'll add comments in v2 to explain it.

>> Since this is not performance
>> sensitive, the developer can choose to add their own layer to provide
>> thread-safe setup and configuration. It is expected that, in most
>> applications, the initial configuration of the network ports would be
>> done by a single thread at startup.
>>
>> Reviewed-by: Andy Moreton <amoreton@solarflare.com>
>> Signed-off-by: Andrew Rybchenko <arybchenko@solarflare.com>
> <...>

Thanks,
Andrew.
  

Patch

diff --git a/drivers/net/sfc/efx/Makefile b/drivers/net/sfc/efx/Makefile
index de95ea8..eadb1ea 100644
--- a/drivers/net/sfc/efx/Makefile
+++ b/drivers/net/sfc/efx/Makefile
@@ -82,6 +82,8 @@  LIBABIVER := 1
 #
 SRCS-$(CONFIG_RTE_LIBRTE_SFC_EFX_PMD) += sfc_ethdev.c
 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
 
 VPATH += $(SRCDIR)/base
 
diff --git a/drivers/net/sfc/efx/sfc.c b/drivers/net/sfc/efx/sfc.c
new file mode 100644
index 0000000..2a17d26
--- /dev/null
+++ b/drivers/net/sfc/efx/sfc.c
@@ -0,0 +1,227 @@ 
+/*-
+ * 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.
+ */
+
+/* sysconf() */
+#include <unistd.h>
+
+#include <rte_errno.h>
+
+#include "efx.h"
+
+#include "sfc.h"
+#include "sfc_log.h"
+
+
+int
+sfc_dma_alloc(const struct sfc_adapter *sa, const char *name, uint16_t id,
+	      size_t len, int socket_id, efsys_mem_t *esmp)
+{
+	const struct rte_memzone *mz;
+
+	sfc_log_init(sa, "name=%s id=%u len=%lu socket_id=%d",
+		     name, id, len, socket_id);
+
+	mz = rte_eth_dma_zone_reserve(sa->eth_dev, name, id, len,
+				      sysconf(_SC_PAGESIZE), socket_id);
+	if (mz == NULL) {
+		sfc_err(sa, "cannot reserve DMA zone for %s:%u %#x@%d: %s",
+			name, (unsigned int)id, (unsigned int)len, socket_id,
+			rte_strerror(rte_errno));
+		return ENOMEM;
+	}
+
+	esmp->esm_addr = rte_mem_phy2mch(mz->memseg_id, mz->phys_addr);
+	if (esmp->esm_addr == RTE_BAD_PHYS_ADDR) {
+		(void)rte_memzone_free(mz);
+		return EFAULT;
+	}
+
+	esmp->esm_mz = mz;
+	esmp->esm_base = mz->addr;
+
+	return 0;
+}
+
+void
+sfc_dma_free(const struct sfc_adapter *sa, efsys_mem_t *esmp)
+{
+	int rc;
+
+	sfc_log_init(sa, "name=%s", esmp->esm_mz->name);
+
+	rc = rte_memzone_free(esmp->esm_mz);
+	if (rc != 0)
+		sfc_err(sa, "rte_memzone_free(() failed: %d", rc);
+
+	memset(esmp, 0, sizeof(*esmp));
+}
+
+static int
+sfc_mem_bar_init(struct sfc_adapter *sa)
+{
+	struct rte_eth_dev *eth_dev = sa->eth_dev;
+	struct rte_pci_device *pci_dev = eth_dev->pci_dev;
+	efsys_bar_t *ebp = &sa->mem_bar;
+	unsigned int i;
+	struct rte_mem_resource *res;
+
+	for (i = 0; i < RTE_DIM(pci_dev->mem_resource); i++) {
+		res = &pci_dev->mem_resource[i];
+		if ((res->len != 0) && (res->phys_addr != 0)) {
+			/* Found first memory BAR */
+			SFC_BAR_LOCK_INIT(ebp, eth_dev->data->name);
+			ebp->esb_rid = i;
+			ebp->esb_dev = pci_dev;
+			ebp->esb_base = res->addr;
+			return 0;
+		}
+	}
+
+	return EFAULT;
+}
+
+static void
+sfc_mem_bar_fini(struct sfc_adapter *sa)
+{
+	efsys_bar_t *ebp = &sa->mem_bar;
+
+	SFC_BAR_LOCK_DESTROY(ebp);
+	memset(ebp, 0, sizeof(*ebp));
+}
+
+int
+sfc_attach(struct sfc_adapter *sa)
+{
+	struct rte_pci_device *pci_dev = sa->eth_dev->pci_dev;
+	efx_nic_t *enp;
+	int rc;
+
+	sfc_log_init(sa, "entry");
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	sa->socket_id = rte_socket_id();
+
+	sfc_log_init(sa, "init mem bar");
+	rc = sfc_mem_bar_init(sa);
+	if (rc != 0)
+		goto fail_mem_bar_init;
+
+	sfc_log_init(sa, "get family");
+	rc = efx_family(pci_dev->id.vendor_id, pci_dev->id.device_id,
+			&sa->family);
+	if (rc != 0)
+		goto fail_family;
+	sfc_log_init(sa, "family is %u", sa->family);
+
+	sfc_log_init(sa, "create nic");
+	rte_spinlock_init(&sa->nic_lock);
+	rc = efx_nic_create(sa->family, (efsys_identifier_t *)sa,
+			    &sa->mem_bar, &sa->nic_lock, &enp);
+	if (rc != 0)
+		goto fail_nic_create;
+	sa->nic = enp;
+
+	rc = sfc_mcdi_init(sa);
+	if (rc != 0)
+		goto fail_mcdi_init;
+
+	sfc_log_init(sa, "probe nic");
+	rc = efx_nic_probe(enp);
+	if (rc != 0)
+		goto fail_nic_probe;
+
+	efx_mcdi_new_epoch(enp);
+
+	sfc_log_init(sa, "reset nic");
+	rc = efx_nic_reset(enp);
+	if (rc != 0)
+		goto fail_nic_reset;
+
+	/* Initialize NIC to double-check hardware */
+	sfc_log_init(sa, "init nic");
+	rc = efx_nic_init(enp);
+	if (rc != 0)
+		goto fail_nic_init;
+
+	sfc_log_init(sa, "fini nic");
+	efx_nic_fini(enp);
+
+	sa->rxq_max = 1;
+	sa->txq_max = 1;
+
+	sa->state = SFC_ADAPTER_INITIALIZED;
+
+	sfc_log_init(sa, "done");
+	return 0;
+
+fail_nic_init:
+fail_nic_reset:
+	sfc_log_init(sa, "unprobe nic");
+	efx_nic_unprobe(enp);
+
+fail_nic_probe:
+	sfc_mcdi_fini(sa);
+
+fail_mcdi_init:
+	sfc_log_init(sa, "destroy nic");
+	sa->nic = NULL;
+	efx_nic_destroy(enp);
+
+fail_nic_create:
+fail_family:
+	sfc_mem_bar_fini(sa);
+
+fail_mem_bar_init:
+	sfc_log_init(sa, "failed %d", rc);
+	return rc;
+}
+
+void
+sfc_detach(struct sfc_adapter *sa)
+{
+	efx_nic_t *enp = sa->nic;
+
+	sfc_log_init(sa, "entry");
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	sfc_log_init(sa, "unprobe nic");
+	efx_nic_unprobe(enp);
+
+	sfc_mcdi_fini(sa);
+
+	sfc_log_init(sa, "destroy nic");
+	sa->nic = NULL;
+	efx_nic_destroy(enp);
+
+	sfc_mem_bar_fini(sa);
+
+	sa->state = SFC_ADAPTER_UNINITIALIZED;
+}
diff --git a/drivers/net/sfc/efx/sfc.h b/drivers/net/sfc/efx/sfc.h
index 16fd2bb..01d652d 100644
--- a/drivers/net/sfc/efx/sfc.h
+++ b/drivers/net/sfc/efx/sfc.h
@@ -34,18 +34,118 @@ 
 
 #include <rte_ethdev.h>
 #include <rte_kvargs.h>
+#include <rte_spinlock.h>
+
+#include "efx.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/*
+ * +---------------+
+ * | UNINITIALIZED |<-----------+
+ * +---------------+		|
+ *	|.eth_dev_init		|.eth_dev_uninit
+ *	V			|
+ * +---------------+------------+
+ * |  INITIALIZED  |
+ * +---------------+
+ */
+enum sfc_adapter_state {
+	SFC_ADAPTER_UNINITIALIZED = 0,
+	SFC_ADAPTER_INITIALIZED,
+
+	SFC_ADAPTER_NSTATES
+};
+
+enum sfc_mcdi_state {
+	SFC_MCDI_UNINITIALIZED = 0,
+	SFC_MCDI_INITIALIZED,
+	SFC_MCDI_BUSY,
+	SFC_MCDI_COMPLETED,
+
+	SFC_MCDI_NSTATES
+};
+
+struct sfc_mcdi {
+	rte_spinlock_t			lock;
+	efsys_mem_t			mem;
+	enum sfc_mcdi_state		state;
+	efx_mcdi_transport_t		transport;
+};
+
 /* Adapter private data */
 struct sfc_adapter {
+	/*
+	 * PMD setup and configuration is not thread safe.
+	 * Since it is not performance sensitive, it is better to guarantee
+	 * thread-safety and add device level lock.
+	 * Adapter control operations which change its state should
+	 * acquire the lock.
+	 */
+	rte_spinlock_t			lock;
+	enum sfc_adapter_state		state;
 	struct rte_eth_dev		*eth_dev;
 	struct rte_kvargs		*kvargs;
 	bool				debug_init;
+	int				socket_id;
+	efsys_bar_t			mem_bar;
+	efx_family_t			family;
+	efx_nic_t			*nic;
+	rte_spinlock_t			nic_lock;
+
+	struct sfc_mcdi			mcdi;
+
+	unsigned int			rxq_max;
+	unsigned int			txq_max;
 };
 
+/*
+ * Add wrapper functions to acquire/release lock to be able to remove or
+ * change the lock in one place.
+ */
+
+static inline void
+sfc_adapter_lock_init(struct sfc_adapter *sa)
+{
+	rte_spinlock_init(&sa->lock);
+}
+
+static inline int
+sfc_adapter_is_locked(struct sfc_adapter *sa)
+{
+	return rte_spinlock_is_locked(&sa->lock);
+}
+
+static inline void
+sfc_adapter_lock(struct sfc_adapter *sa)
+{
+	rte_spinlock_lock(&sa->lock);
+}
+
+static inline void
+sfc_adapter_unlock(struct sfc_adapter *sa)
+{
+	rte_spinlock_unlock(&sa->lock);
+}
+
+static inline void
+sfc_adapter_lock_destroy(struct sfc_adapter *sa)
+{
+	/* Just for symmetry of the API */
+}
+
+int sfc_dma_alloc(const struct sfc_adapter *sa, const char *name, uint16_t id,
+		  size_t len, int socket_id, efsys_mem_t *esmp);
+void sfc_dma_free(const struct sfc_adapter *sa, efsys_mem_t *esmp);
+
+int sfc_attach(struct sfc_adapter *sa);
+void sfc_detach(struct sfc_adapter *sa);
+
+int sfc_mcdi_init(struct sfc_adapter *sa);
+void sfc_mcdi_fini(struct sfc_adapter *sa);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/drivers/net/sfc/efx/sfc_debug.h b/drivers/net/sfc/efx/sfc_debug.h
index de3ec61..2c3988b 100644
--- a/drivers/net/sfc/efx/sfc_debug.h
+++ b/drivers/net/sfc/efx/sfc_debug.h
@@ -42,4 +42,16 @@ 
 #define	SFC_ASSERT(exp)			RTE_ASSERT(exp)
 #endif
 
+/* Log PMD message, automatically add prefix and \n */
+#define	sfc_panic(sa, fmt, args...) \
+	do {								\
+		const struct rte_eth_dev *_dev = (sa)->eth_dev;		\
+		const struct rte_pci_device *_pci_dev = _dev->pci_dev;	\
+									\
+		rte_panic("sfc " PCI_PRI_FMT " #%" PRIu8 ": " fmt "\n",	\
+			  _pci_dev->addr.domain, _pci_dev->addr.bus,	\
+			  _pci_dev->addr.devid, _pci_dev->addr.function,\
+			  _dev->data->port_id, ##args);			\
+	} while (0)
+
 #endif /* _SFC_DEBUG_H_ */
diff --git a/drivers/net/sfc/efx/sfc_ethdev.c b/drivers/net/sfc/efx/sfc_ethdev.c
index 0deff07..e5b609c 100644
--- a/drivers/net/sfc/efx/sfc_ethdev.c
+++ b/drivers/net/sfc/efx/sfc_ethdev.c
@@ -31,6 +31,8 @@ 
 #include <rte_ethdev.h>
 #include <rte_pci.h>
 
+#include "efx.h"
+
 #include "sfc.h"
 #include "sfc_debug.h"
 #include "sfc_log.h"
@@ -55,6 +57,8 @@  sfc_eth_dev_init(struct rte_eth_dev *dev)
 	struct sfc_adapter *sa = dev->data->dev_private;
 	struct rte_pci_device *pci_dev = dev->pci_dev;
 	int rc;
+	const efx_nic_cfg_t *encp;
+	const struct ether_addr *from;
 
 	/* Required for logging */
 	sa->eth_dev = dev;
@@ -73,11 +77,43 @@  sfc_eth_dev_init(struct rte_eth_dev *dev)
 
 	sfc_log_init(sa, "entry");
 
+	dev->data->mac_addrs = rte_zmalloc("sfc", ETHER_ADDR_LEN, 0);
+	if (dev->data->mac_addrs == NULL) {
+		rc = ENOMEM;
+		goto fail_mac_addrs;
+	}
+
+	sfc_adapter_lock_init(sa);
+	sfc_adapter_lock(sa);
+
+	sfc_log_init(sa, "attaching");
+	rc = sfc_attach(sa);
+	if (rc != 0)
+		goto fail_attach;
+
+	encp = efx_nic_cfg_get(sa->nic);
+
+	/*
+	 * The arguments are really reverse order in comparison to
+	 * Linux kernel. Copy from NIC config to Ethernet device data.
+	 */
+	from = (const struct ether_addr *)(encp->enc_mac_addr);
+	ether_addr_copy(from, &dev->data->mac_addrs[0]);
+
 	dev->dev_ops = &sfc_eth_dev_ops;
 
+	sfc_adapter_unlock(sa);
+
 	sfc_log_init(sa, "done");
 	return 0;
 
+fail_attach:
+	sfc_adapter_unlock(sa);
+	sfc_adapter_lock_destroy(sa);
+	rte_free(dev->data->mac_addrs);
+	dev->data->mac_addrs = NULL;
+
+fail_mac_addrs:
 fail_kvarg_debug_init:
 	sfc_kvargs_cleanup(sa);
 
@@ -94,10 +130,20 @@  sfc_eth_dev_uninit(struct rte_eth_dev *dev)
 
 	sfc_log_init(sa, "entry");
 
+	sfc_adapter_lock(sa);
+
+	sfc_detach(sa);
+
+	rte_free(dev->data->mac_addrs);
+	dev->data->mac_addrs = NULL;
+
 	dev->dev_ops = NULL;
 
 	sfc_kvargs_cleanup(sa);
 
+	sfc_adapter_unlock(sa);
+	sfc_adapter_lock_destroy(sa);
+
 	sfc_log_init(sa, "done");
 
 	/* Required for logging, so cleanup last */
@@ -106,13 +152,17 @@  sfc_eth_dev_uninit(struct rte_eth_dev *dev)
 }
 
 static const struct rte_pci_id pci_id_sfc_efx_map[] = {
+	{ RTE_PCI_DEVICE(EFX_PCI_VENID_SFC, EFX_PCI_DEVID_FARMINGDALE) },
+	{ RTE_PCI_DEVICE(EFX_PCI_VENID_SFC, EFX_PCI_DEVID_GREENPORT) },
+	{ RTE_PCI_DEVICE(EFX_PCI_VENID_SFC, EFX_PCI_DEVID_MEDFORD) },
 	{ .vendor_id = 0 /* sentinel */ }
 };
 
 static struct eth_driver sfc_efx_pmd = {
 	.pci_drv = {
 		.id_table = pci_id_sfc_efx_map,
-		.drv_flags = 0,
+		.drv_flags =
+			RTE_PCI_DRV_NEED_MAPPING,
 		.probe = rte_eth_dev_pci_probe,
 		.remove = rte_eth_dev_pci_remove,
 	},
diff --git a/drivers/net/sfc/efx/sfc_mcdi.c b/drivers/net/sfc/efx/sfc_mcdi.c
new file mode 100644
index 0000000..bf641d9
--- /dev/null
+++ b/drivers/net/sfc/efx/sfc_mcdi.c
@@ -0,0 +1,197 @@ 
+/*-
+ * 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 <rte_cycles.h>
+
+#include "efx.h"
+#include "efx_mcdi.h"
+#include "efx_regs_mcdi.h"
+
+#include "sfc.h"
+#include "sfc_log.h"
+
+#define	SFC_MCDI_POLL_INTERVAL_MIN_US	10		/* 10us in 1us units */
+#define	SFC_MCDI_POLL_INTERVAL_MAX_US	(US_PER_S / 10)	/* 100ms in 1us units */
+#define	SFC_MCDI_WATCHDOG_INTERVAL_US	(10 * US_PER_S)	/* 10s in 1us units */
+
+static void
+sfc_mcdi_timeout(struct sfc_adapter *sa)
+{
+	sfc_warn(sa, "MC TIMEOUT");
+
+	sfc_panic(sa, "MCDI timeout handling is not implemented\n");
+}
+
+static void
+sfc_mcdi_poll(struct sfc_adapter *sa)
+{
+	efx_nic_t *enp;
+	unsigned int delay_total;
+	unsigned int delay_us;
+	boolean_t aborted;
+
+	delay_total = 0;
+	delay_us = SFC_MCDI_POLL_INTERVAL_MIN_US;
+	enp = sa->nic;
+
+	do {
+		if (efx_mcdi_request_poll(enp))
+			return;
+
+		if (delay_total > SFC_MCDI_WATCHDOG_INTERVAL_US) {
+			aborted = efx_mcdi_request_abort(enp);
+			SFC_ASSERT(aborted);
+			sfc_mcdi_timeout(sa);
+			return;
+		}
+
+		rte_delay_us(delay_us);
+
+		delay_total += delay_us;
+
+		/* Exponentially back off the poll frequency */
+		RTE_BUILD_BUG_ON(SFC_MCDI_POLL_INTERVAL_MAX_US > UINT_MAX / 2);
+		delay_us *= 2;
+		if (delay_us > SFC_MCDI_POLL_INTERVAL_MAX_US)
+			delay_us = SFC_MCDI_POLL_INTERVAL_MAX_US;
+
+	} while (1);
+}
+
+static void
+sfc_mcdi_execute(void *arg, efx_mcdi_req_t *emrp)
+{
+	struct sfc_adapter *sa = (struct sfc_adapter *)arg;
+	struct sfc_mcdi *mcdi = &sa->mcdi;
+
+	rte_spinlock_lock(&mcdi->lock);
+
+	SFC_ASSERT(mcdi->state == SFC_MCDI_INITIALIZED);
+
+	efx_mcdi_request_start(sa->nic, emrp, B_FALSE);
+	sfc_mcdi_poll(sa);
+
+	rte_spinlock_unlock(&mcdi->lock);
+}
+
+static void
+sfc_mcdi_ev_cpl(void *arg)
+{
+	struct sfc_adapter *sa = (struct sfc_adapter *)arg;
+	struct sfc_mcdi *mcdi = &sa->mcdi;
+
+	SFC_ASSERT(mcdi->state == SFC_MCDI_INITIALIZED);
+
+	/* MCDI is polled, completions are not expected */
+	SFC_ASSERT(0);
+}
+
+static void
+sfc_mcdi_exception(void *arg, efx_mcdi_exception_t eme)
+{
+	struct sfc_adapter *sa = (struct sfc_adapter *)arg;
+
+	sfc_warn(sa, "MC %s",
+	    (eme == EFX_MCDI_EXCEPTION_MC_REBOOT) ? "REBOOT" :
+	    (eme == EFX_MCDI_EXCEPTION_MC_BADASSERT) ? "BADASSERT" : "UNKNOWN");
+
+	sfc_panic(sa, "MCDI exceptions handling is not implemented\n");
+}
+
+int
+sfc_mcdi_init(struct sfc_adapter *sa)
+{
+	struct sfc_mcdi *mcdi;
+	size_t max_msg_size;
+	efx_mcdi_transport_t *emtp;
+	int rc;
+
+	sfc_log_init(sa, "entry");
+
+	mcdi = &sa->mcdi;
+
+	SFC_ASSERT(mcdi->state == SFC_MCDI_UNINITIALIZED);
+
+	rte_spinlock_init(&mcdi->lock);
+
+	mcdi->state = SFC_MCDI_INITIALIZED;
+
+	max_msg_size = sizeof(uint32_t) + MCDI_CTL_SDU_LEN_MAX_V2;
+	rc = sfc_dma_alloc(sa, "mcdi", 0, max_msg_size, sa->socket_id,
+			   &mcdi->mem);
+	if (rc != 0)
+		goto fail_dma_alloc;
+
+	emtp = &mcdi->transport;
+	emtp->emt_context = sa;
+	emtp->emt_dma_mem = &mcdi->mem;
+	emtp->emt_execute = sfc_mcdi_execute;
+	emtp->emt_ev_cpl = sfc_mcdi_ev_cpl;
+	emtp->emt_exception = sfc_mcdi_exception;
+
+	sfc_log_init(sa, "init MCDI");
+	rc = efx_mcdi_init(sa->nic, emtp);
+	if (rc != 0)
+		goto fail_mcdi_init;
+
+	return 0;
+
+fail_mcdi_init:
+	memset(emtp, 0, sizeof(*emtp));
+	sfc_dma_free(sa, &mcdi->mem);
+
+fail_dma_alloc:
+	mcdi->state = SFC_MCDI_UNINITIALIZED;
+	return rc;
+}
+
+void
+sfc_mcdi_fini(struct sfc_adapter *sa)
+{
+	struct sfc_mcdi *mcdi;
+	efx_mcdi_transport_t *emtp;
+
+	sfc_log_init(sa, "entry");
+
+	mcdi = &sa->mcdi;
+	emtp = &mcdi->transport;
+
+	rte_spinlock_lock(&mcdi->lock);
+
+	SFC_ASSERT(mcdi->state == SFC_MCDI_INITIALIZED);
+	mcdi->state = SFC_MCDI_UNINITIALIZED;
+
+	sfc_log_init(sa, "fini MCDI");
+	efx_mcdi_fini(sa->nic);
+	memset(emtp, 0, sizeof(*emtp));
+
+	rte_spinlock_unlock(&mcdi->lock);
+
+	sfc_dma_free(sa, &mcdi->mem);
+}