New upstream version 18.08
[deb_dpdk.git] / drivers / net / netvsc / hn_nvs.c
diff --git a/drivers/net/netvsc/hn_nvs.c b/drivers/net/netvsc/hn_nvs.c
new file mode 100644 (file)
index 0000000..77d3b83
--- /dev/null
@@ -0,0 +1,546 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2018 Microsoft Corp.
+ * Copyright (c) 2010-2012 Citrix Inc.
+ * Copyright (c) 2012 NetApp Inc.
+ * All rights reserved.
+ */
+
+/*
+ * Network Virtualization Service.
+ */
+
+
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_memzone.h>
+#include <rte_malloc.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_ether.h>
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_cycles.h>
+#include <rte_memory.h>
+#include <rte_eal.h>
+#include <rte_dev.h>
+#include <rte_bus_vmbus.h>
+
+#include "hn_logs.h"
+#include "hn_var.h"
+#include "hn_nvs.h"
+
+static const uint32_t hn_nvs_version[] = {
+       NVS_VERSION_61,
+       NVS_VERSION_6,
+       NVS_VERSION_5,
+       NVS_VERSION_4,
+       NVS_VERSION_2,
+       NVS_VERSION_1
+};
+
+static int hn_nvs_req_send(struct hn_data *hv,
+                          void *req, uint32_t reqlen)
+{
+       return rte_vmbus_chan_send(hn_primary_chan(hv),
+                                  VMBUS_CHANPKT_TYPE_INBAND,
+                                  req, reqlen, 0,
+                                  VMBUS_CHANPKT_FLAG_NONE, NULL);
+}
+
+static int
+hn_nvs_execute(struct hn_data *hv,
+              void *req, uint32_t reqlen,
+              void *resp, uint32_t resplen,
+              uint32_t type)
+{
+       struct vmbus_channel *chan = hn_primary_chan(hv);
+       char buffer[NVS_RESPSIZE_MAX];
+       const struct hn_nvs_hdr *hdr;
+       uint32_t len;
+       int ret;
+
+       /* Send request to ring buffer */
+       ret = rte_vmbus_chan_send(chan, VMBUS_CHANPKT_TYPE_INBAND,
+                                 req, reqlen, 0,
+                                 VMBUS_CHANPKT_FLAG_RC, NULL);
+
+       if (ret) {
+               PMD_DRV_LOG(ERR, "send request failed: %d", ret);
+               return ret;
+       }
+
+ retry:
+       len = sizeof(buffer);
+       ret = rte_vmbus_chan_recv(chan, buffer, &len, NULL);
+       if (ret == -EAGAIN) {
+               rte_delay_us(HN_CHAN_INTERVAL_US);
+               goto retry;
+       }
+
+       if (ret < 0) {
+               PMD_DRV_LOG(ERR, "recv response failed: %d", ret);
+               return ret;
+       }
+
+       hdr = (struct hn_nvs_hdr *)buffer;
+       if (hdr->type != type) {
+               PMD_DRV_LOG(ERR, "unexpected NVS resp %#x, expect %#x",
+                           hdr->type, type);
+               return -EINVAL;
+       }
+
+       if (len < resplen) {
+               PMD_DRV_LOG(ERR,
+                           "invalid NVS resp len %u (expect %u)",
+                           len, resplen);
+               return -EINVAL;
+       }
+
+       memcpy(resp, buffer, resplen);
+
+       /* All pass! */
+       return 0;
+}
+
+static int
+hn_nvs_doinit(struct hn_data *hv, uint32_t nvs_ver)
+{
+       struct hn_nvs_init init;
+       struct hn_nvs_init_resp resp;
+       uint32_t status;
+       int error;
+
+       memset(&init, 0, sizeof(init));
+       init.type = NVS_TYPE_INIT;
+       init.ver_min = nvs_ver;
+       init.ver_max = nvs_ver;
+
+       error = hn_nvs_execute(hv, &init, sizeof(init),
+                              &resp, sizeof(resp),
+                              NVS_TYPE_INIT_RESP);
+       if (error)
+               return error;
+
+       status = resp.status;
+       if (status != NVS_STATUS_OK) {
+               /* Not fatal, try other versions */
+               PMD_INIT_LOG(DEBUG, "nvs init failed for ver 0x%x",
+                            nvs_ver);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int
+hn_nvs_conn_rxbuf(struct hn_data *hv)
+{
+       struct hn_nvs_rxbuf_conn conn;
+       struct hn_nvs_rxbuf_connresp resp;
+       uint32_t status;
+       int error;
+
+       /* Kernel has already setup RXBUF on primary channel. */
+
+       /*
+        * Connect RXBUF to NVS.
+        */
+       conn.type = NVS_TYPE_RXBUF_CONN;
+       conn.gpadl = hv->rxbuf_res->phys_addr;
+       conn.sig = NVS_RXBUF_SIG;
+       PMD_DRV_LOG(DEBUG, "connect rxbuff va=%p gpad=%#" PRIx64,
+                   hv->rxbuf_res->addr,
+                   hv->rxbuf_res->phys_addr);
+
+       error = hn_nvs_execute(hv, &conn, sizeof(conn),
+                              &resp, sizeof(resp),
+                              NVS_TYPE_RXBUF_CONNRESP);
+       if (error) {
+               PMD_DRV_LOG(ERR,
+                           "exec nvs rxbuf conn failed: %d",
+                           error);
+               return error;
+       }
+
+       status = resp.status;
+       if (status != NVS_STATUS_OK) {
+               PMD_DRV_LOG(ERR,
+                           "nvs rxbuf conn failed: %x", status);
+               return -EIO;
+       }
+       if (resp.nsect != 1) {
+               PMD_DRV_LOG(ERR,
+                           "nvs rxbuf response num sections %u != 1",
+                           resp.nsect);
+               return -EIO;
+       }
+
+       PMD_DRV_LOG(INFO,
+                   "receive buffer size %u count %u",
+                   resp.nvs_sect[0].slotsz,
+                   resp.nvs_sect[0].slotcnt);
+       hv->rxbuf_section_cnt = resp.nvs_sect[0].slotcnt;
+
+       hv->rxbuf_info = rte_calloc("HN_RXBUF_INFO", hv->rxbuf_section_cnt,
+                                   sizeof(*hv->rxbuf_info), RTE_CACHE_LINE_SIZE);
+       if (!hv->rxbuf_info) {
+               PMD_DRV_LOG(ERR,
+                           "could not allocate rxbuf info");
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static void
+hn_nvs_disconn_rxbuf(struct hn_data *hv)
+{
+       struct hn_nvs_rxbuf_disconn disconn;
+       int error;
+
+       /*
+        * Disconnect RXBUF from NVS.
+        */
+       memset(&disconn, 0, sizeof(disconn));
+       disconn.type = NVS_TYPE_RXBUF_DISCONN;
+       disconn.sig = NVS_RXBUF_SIG;
+
+       /* NOTE: No response. */
+       error = hn_nvs_req_send(hv, &disconn, sizeof(disconn));
+       if (error) {
+               PMD_DRV_LOG(ERR,
+                           "send nvs rxbuf disconn failed: %d",
+                           error);
+       }
+
+       rte_free(hv->rxbuf_info);
+       /*
+        * Linger long enough for NVS to disconnect RXBUF.
+        */
+       rte_delay_ms(200);
+}
+
+static void
+hn_nvs_disconn_chim(struct hn_data *hv)
+{
+       int error;
+
+       if (hv->chim_cnt != 0) {
+               struct hn_nvs_chim_disconn disconn;
+
+               /* Disconnect chimney sending buffer from NVS. */
+               memset(&disconn, 0, sizeof(disconn));
+               disconn.type = NVS_TYPE_CHIM_DISCONN;
+               disconn.sig = NVS_CHIM_SIG;
+
+               /* NOTE: No response. */
+               error = hn_nvs_req_send(hv, &disconn, sizeof(disconn));
+
+               if (error) {
+                       PMD_DRV_LOG(ERR,
+                                   "send nvs chim disconn failed: %d", error);
+               }
+
+               hv->chim_cnt = 0;
+               /*
+                * Linger long enough for NVS to disconnect chimney
+                * sending buffer.
+                */
+               rte_delay_ms(200);
+       }
+}
+
+static int
+hn_nvs_conn_chim(struct hn_data *hv)
+{
+       struct hn_nvs_chim_conn chim;
+       struct hn_nvs_chim_connresp resp;
+       uint32_t sectsz;
+       unsigned long len = hv->chim_res->len;
+       int error;
+
+       /* Connect chimney sending buffer to NVS */
+       memset(&chim, 0, sizeof(chim));
+       chim.type = NVS_TYPE_CHIM_CONN;
+       chim.gpadl = hv->chim_res->phys_addr;
+       chim.sig = NVS_CHIM_SIG;
+       PMD_DRV_LOG(DEBUG, "connect send buf va=%p gpad=%#" PRIx64,
+                   hv->chim_res->addr,
+                   hv->chim_res->phys_addr);
+
+       error = hn_nvs_execute(hv, &chim, sizeof(chim),
+                              &resp, sizeof(resp),
+                              NVS_TYPE_CHIM_CONNRESP);
+       if (error) {
+               PMD_DRV_LOG(ERR, "exec nvs chim conn failed");
+               goto cleanup;
+       }
+
+       if (resp.status != NVS_STATUS_OK) {
+               PMD_DRV_LOG(ERR, "nvs chim conn failed: %x",
+                           resp.status);
+               error = -EIO;
+               goto cleanup;
+       }
+
+       sectsz = resp.sectsz;
+       if (sectsz == 0 || sectsz & (sizeof(uint32_t) - 1)) {
+               /* Can't use chimney sending buffer; done! */
+               PMD_DRV_LOG(NOTICE,
+                           "invalid chimney sending buffer section size: %u",
+                           sectsz);
+               return 0;
+       }
+
+       hv->chim_szmax = sectsz;
+       hv->chim_cnt = len / sectsz;
+
+       PMD_DRV_LOG(INFO, "send buffer %lu section size:%u, count:%u",
+                   len, hv->chim_szmax, hv->chim_cnt);
+
+       if (len % hv->chim_szmax != 0) {
+               PMD_DRV_LOG(NOTICE,
+                           "chimney sending sections are not properly aligned");
+       }
+
+       /* Done! */
+       return 0;
+
+cleanup:
+       hn_nvs_disconn_chim(hv);
+       return error;
+}
+
+/*
+ * Configure MTU and enable VLAN.
+ */
+static int
+hn_nvs_conf_ndis(struct hn_data *hv, unsigned int mtu)
+{
+       struct hn_nvs_ndis_conf conf;
+       int error;
+
+       memset(&conf, 0, sizeof(conf));
+       conf.type = NVS_TYPE_NDIS_CONF;
+       conf.mtu = mtu + ETHER_HDR_LEN;
+       conf.caps = NVS_NDIS_CONF_VLAN;
+
+       /* TODO enable SRIOV */
+       //if (hv->nvs_ver >= NVS_VERSION_5)
+       //      conf.caps |= NVS_NDIS_CONF_SRIOV;
+
+       /* NOTE: No response. */
+       error = hn_nvs_req_send(hv, &conf, sizeof(conf));
+       if (error) {
+               PMD_DRV_LOG(ERR,
+                           "send nvs ndis conf failed: %d", error);
+               return error;
+       }
+
+       return 0;
+}
+
+static int
+hn_nvs_init_ndis(struct hn_data *hv)
+{
+       struct hn_nvs_ndis_init ndis;
+       int error;
+
+       memset(&ndis, 0, sizeof(ndis));
+       ndis.type = NVS_TYPE_NDIS_INIT;
+       ndis.ndis_major = NDIS_VERSION_MAJOR(hv->ndis_ver);
+       ndis.ndis_minor = NDIS_VERSION_MINOR(hv->ndis_ver);
+
+       /* NOTE: No response. */
+       error = hn_nvs_req_send(hv, &ndis, sizeof(ndis));
+       if (error)
+               PMD_DRV_LOG(ERR,
+                           "send nvs ndis init failed: %d", error);
+
+       return error;
+}
+
+static int
+hn_nvs_init(struct hn_data *hv)
+{
+       unsigned int i;
+       int error;
+
+       /*
+        * Find the supported NVS version and set NDIS version accordingly.
+        */
+       for (i = 0; i < RTE_DIM(hn_nvs_version); ++i) {
+               error = hn_nvs_doinit(hv, hn_nvs_version[i]);
+               if (error) {
+                       PMD_INIT_LOG(DEBUG, "version %#x error %d",
+                                    hn_nvs_version[i], error);
+                       continue;
+               }
+
+               hv->nvs_ver = hn_nvs_version[i];
+
+               /* Set NDIS version according to NVS version. */
+               hv->ndis_ver = NDIS_VERSION_6_30;
+               if (hv->nvs_ver <= NVS_VERSION_4)
+                       hv->ndis_ver = NDIS_VERSION_6_1;
+
+               PMD_INIT_LOG(DEBUG,
+                            "NVS version %#x, NDIS version %u.%u",
+                            hv->nvs_ver, NDIS_VERSION_MAJOR(hv->ndis_ver),
+                            NDIS_VERSION_MINOR(hv->ndis_ver));
+               return 0;
+       }
+
+       PMD_DRV_LOG(ERR,
+                   "no NVS compatible version available");
+       return -ENXIO;
+}
+
+int
+hn_nvs_attach(struct hn_data *hv, unsigned int mtu)
+{
+       int error;
+
+       /*
+        * Initialize NVS.
+        */
+       error = hn_nvs_init(hv);
+       if (error)
+               return error;
+
+       /** Configure NDIS before initializing it. */
+       if (hv->nvs_ver >= NVS_VERSION_2) {
+               error = hn_nvs_conf_ndis(hv, mtu);
+               if (error)
+                       return error;
+       }
+
+       /*
+        * Initialize NDIS.
+        */
+       error = hn_nvs_init_ndis(hv);
+       if (error)
+               return error;
+
+       /*
+        * Connect RXBUF.
+        */
+       error = hn_nvs_conn_rxbuf(hv);
+       if (error)
+               return error;
+
+       /*
+        * Connect chimney sending buffer.
+        */
+       error = hn_nvs_conn_chim(hv);
+       if (error) {
+               hn_nvs_disconn_rxbuf(hv);
+               return error;
+       }
+
+       return 0;
+}
+
+void
+hn_nvs_detach(struct hn_data *hv __rte_unused)
+{
+       PMD_INIT_FUNC_TRACE();
+
+       /* NOTE: there are no requests to stop the NVS. */
+       hn_nvs_disconn_rxbuf(hv);
+       hn_nvs_disconn_chim(hv);
+}
+
+/*
+ * Ack the consumed RXBUF associated w/ this channel packet,
+ * so that this RXBUF can be recycled by the hypervisor.
+ */
+void
+hn_nvs_ack_rxbuf(struct vmbus_channel *chan, uint64_t tid)
+{
+       unsigned int retries = 0;
+       struct hn_nvs_rndis_ack ack = {
+               .type = NVS_TYPE_RNDIS_ACK,
+               .status = NVS_STATUS_OK,
+       };
+       int error;
+
+       PMD_RX_LOG(DEBUG, "ack RX id %" PRIu64, tid);
+
+ again:
+       error = rte_vmbus_chan_send(chan, VMBUS_CHANPKT_TYPE_COMP,
+                                   &ack, sizeof(ack), tid,
+                                   VMBUS_CHANPKT_FLAG_NONE, NULL);
+
+       if (error == 0)
+               return;
+
+       if (error == -EAGAIN) {
+               /*
+                * NOTE:
+                * This should _not_ happen in real world, since the
+                * consumption of the TX bufring from the TX path is
+                * controlled.
+                */
+               PMD_RX_LOG(NOTICE, "RXBUF ack retry");
+               if (++retries < 10) {
+                       rte_delay_ms(1);
+                       goto again;
+               }
+       }
+       /* RXBUF leaks! */
+       PMD_DRV_LOG(ERR, "RXBUF ack failed");
+}
+
+int
+hn_nvs_alloc_subchans(struct hn_data *hv, uint32_t *nsubch)
+{
+       struct hn_nvs_subch_req req;
+       struct hn_nvs_subch_resp resp;
+       int error;
+
+       memset(&req, 0, sizeof(req));
+       req.type = NVS_TYPE_SUBCH_REQ;
+       req.op = NVS_SUBCH_OP_ALLOC;
+       req.nsubch = *nsubch;
+
+       error = hn_nvs_execute(hv, &req, sizeof(req),
+                              &resp, sizeof(resp),
+                              NVS_TYPE_SUBCH_RESP);
+       if (error)
+               return error;
+
+       if (resp.status != NVS_STATUS_OK) {
+               PMD_INIT_LOG(ERR,
+                            "nvs subch alloc failed: %#x",
+                            resp.status);
+               return -EIO;
+       }
+
+       if (resp.nsubch > *nsubch) {
+               PMD_INIT_LOG(NOTICE,
+                            "%u subchans are allocated, requested %u",
+                            resp.nsubch, *nsubch);
+       }
+       *nsubch = resp.nsubch;
+
+       return 0;
+}
+
+void
+hn_nvs_set_datapath(struct hn_data *hv, uint32_t path)
+{
+       struct hn_nvs_datapath dp;
+
+       memset(&dp, 0, sizeof(dp));
+       dp.type = NVS_TYPE_SET_DATAPATH;
+       dp.active_path = path;
+
+       hn_nvs_req_send(hv, &dp, sizeof(dp));
+}