numam-dpdk/drivers/net/netvsc/hn_nvs.c
Stephen Hemminger 4e9c73e96e net/netvsc: add Hyper-V network device
The driver supports Hyper-V networking directly like
virtio for KVM or vmxnet3 for VMware.

This code is based off of the FreeBSD driver. The file and variable
names are kept the same to help with understanding (with most of the
BSD style warts removed).

This version supports the latest NetVSP 6.1 version and
older versions.

Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: Stephen Hemminger <sthemmin@microsoft.com>
2018-07-13 23:48:07 +02:00

547 lines
11 KiB
C

/* 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));
}