5bdfd3fd36
Hyper-V's NIC SR-IOV implementation needs a Hyper-V synthetic NIC and a VF NIC to work together (both NICs have the same MAC address), mainly to support seamless live migration. When the VF device becomes UP (or DOWN), the synthetic NIC driver needs to switch the data path from the synthetic NIC to the VF (or the opposite). Note: multicast/broadcast packets are still received through the synthetic NIC and we need to inject the packets through the VF interface (if the VF is UP), even if the synthetic NIC is DOWN (so we need to force the rxfilter to be NDIS_PACKET_TYPE_PROMISCUOUS, when the VF is UP). Reviewed by: sephe Approved by: sephe (mentor) MFC after: 2 weeks Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8964
736 lines
18 KiB
C
736 lines
18 KiB
C
/*-
|
|
* Copyright (c) 2009-2012,2016 Microsoft Corp.
|
|
* Copyright (c) 2010-2012 Citrix Inc.
|
|
* Copyright (c) 2012 NetApp Inc.
|
|
* All rights reserved.
|
|
*
|
|
* 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 unmodified, 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 AUTHOR ``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 AUTHOR 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.
|
|
*/
|
|
|
|
/*
|
|
* Network Virtualization Service.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_inet6.h"
|
|
#include "opt_inet.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_media.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp_lro.h>
|
|
|
|
#include <dev/hyperv/include/hyperv.h>
|
|
#include <dev/hyperv/include/hyperv_busdma.h>
|
|
#include <dev/hyperv/include/vmbus.h>
|
|
#include <dev/hyperv/include/vmbus_xact.h>
|
|
|
|
#include <dev/hyperv/netvsc/ndis.h>
|
|
#include <dev/hyperv/netvsc/if_hnreg.h>
|
|
#include <dev/hyperv/netvsc/if_hnvar.h>
|
|
#include <dev/hyperv/netvsc/hn_nvs.h>
|
|
|
|
static int hn_nvs_conn_chim(struct hn_softc *);
|
|
static int hn_nvs_conn_rxbuf(struct hn_softc *);
|
|
static void hn_nvs_disconn_chim(struct hn_softc *);
|
|
static void hn_nvs_disconn_rxbuf(struct hn_softc *);
|
|
static int hn_nvs_conf_ndis(struct hn_softc *, int);
|
|
static int hn_nvs_init_ndis(struct hn_softc *);
|
|
static int hn_nvs_doinit(struct hn_softc *, uint32_t);
|
|
static int hn_nvs_init(struct hn_softc *);
|
|
static const void *hn_nvs_xact_execute(struct hn_softc *,
|
|
struct vmbus_xact *, void *, int,
|
|
size_t *, uint32_t);
|
|
static void hn_nvs_sent_none(struct hn_nvs_sendctx *,
|
|
struct hn_softc *, struct vmbus_channel *,
|
|
const void *, int);
|
|
|
|
struct hn_nvs_sendctx hn_nvs_sendctx_none =
|
|
HN_NVS_SENDCTX_INITIALIZER(hn_nvs_sent_none, NULL);
|
|
|
|
static const uint32_t hn_nvs_version[] = {
|
|
HN_NVS_VERSION_5,
|
|
HN_NVS_VERSION_4,
|
|
HN_NVS_VERSION_2,
|
|
HN_NVS_VERSION_1
|
|
};
|
|
|
|
static const void *
|
|
hn_nvs_xact_execute(struct hn_softc *sc, struct vmbus_xact *xact,
|
|
void *req, int reqlen, size_t *resplen0, uint32_t type)
|
|
{
|
|
struct hn_nvs_sendctx sndc;
|
|
size_t resplen, min_resplen = *resplen0;
|
|
const struct hn_nvs_hdr *hdr;
|
|
int error;
|
|
|
|
KASSERT(min_resplen >= sizeof(*hdr),
|
|
("invalid minimum response len %zu", min_resplen));
|
|
|
|
/*
|
|
* Execute the xact setup by the caller.
|
|
*/
|
|
hn_nvs_sendctx_init(&sndc, hn_nvs_sent_xact, xact);
|
|
|
|
vmbus_xact_activate(xact);
|
|
error = hn_nvs_send(sc->hn_prichan, VMBUS_CHANPKT_FLAG_RC,
|
|
req, reqlen, &sndc);
|
|
if (error) {
|
|
vmbus_xact_deactivate(xact);
|
|
return (NULL);
|
|
}
|
|
hdr = vmbus_chan_xact_wait(sc->hn_prichan, xact, &resplen,
|
|
HN_CAN_SLEEP(sc));
|
|
|
|
/*
|
|
* Check this NVS response message.
|
|
*/
|
|
if (resplen < min_resplen) {
|
|
if_printf(sc->hn_ifp, "invalid NVS resp len %zu\n", resplen);
|
|
return (NULL);
|
|
}
|
|
if (hdr->nvs_type != type) {
|
|
if_printf(sc->hn_ifp, "unexpected NVS resp 0x%08x, "
|
|
"expect 0x%08x\n", hdr->nvs_type, type);
|
|
return (NULL);
|
|
}
|
|
/* All pass! */
|
|
*resplen0 = resplen;
|
|
return (hdr);
|
|
}
|
|
|
|
static __inline int
|
|
hn_nvs_req_send(struct hn_softc *sc, void *req, int reqlen)
|
|
{
|
|
|
|
return (hn_nvs_send(sc->hn_prichan, VMBUS_CHANPKT_FLAG_NONE,
|
|
req, reqlen, &hn_nvs_sendctx_none));
|
|
}
|
|
|
|
static int
|
|
hn_nvs_conn_rxbuf(struct hn_softc *sc)
|
|
{
|
|
struct vmbus_xact *xact = NULL;
|
|
struct hn_nvs_rxbuf_conn *conn;
|
|
const struct hn_nvs_rxbuf_connresp *resp;
|
|
size_t resp_len;
|
|
uint32_t status;
|
|
int error, rxbuf_size;
|
|
|
|
/*
|
|
* Limit RXBUF size for old NVS.
|
|
*/
|
|
if (sc->hn_nvs_ver <= HN_NVS_VERSION_2)
|
|
rxbuf_size = HN_RXBUF_SIZE_COMPAT;
|
|
else
|
|
rxbuf_size = HN_RXBUF_SIZE;
|
|
|
|
/*
|
|
* Connect the RXBUF GPADL to the primary channel.
|
|
*
|
|
* NOTE:
|
|
* Only primary channel has RXBUF connected to it. Sub-channels
|
|
* just share this RXBUF.
|
|
*/
|
|
error = vmbus_chan_gpadl_connect(sc->hn_prichan,
|
|
sc->hn_rxbuf_dma.hv_paddr, rxbuf_size, &sc->hn_rxbuf_gpadl);
|
|
if (error) {
|
|
if_printf(sc->hn_ifp, "rxbuf gpadl conn failed: %d\n",
|
|
error);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Connect RXBUF to NVS.
|
|
*/
|
|
|
|
xact = vmbus_xact_get(sc->hn_xact, sizeof(*conn));
|
|
if (xact == NULL) {
|
|
if_printf(sc->hn_ifp, "no xact for nvs rxbuf conn\n");
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
conn = vmbus_xact_req_data(xact);
|
|
conn->nvs_type = HN_NVS_TYPE_RXBUF_CONN;
|
|
conn->nvs_gpadl = sc->hn_rxbuf_gpadl;
|
|
conn->nvs_sig = HN_NVS_RXBUF_SIG;
|
|
|
|
resp_len = sizeof(*resp);
|
|
resp = hn_nvs_xact_execute(sc, xact, conn, sizeof(*conn), &resp_len,
|
|
HN_NVS_TYPE_RXBUF_CONNRESP);
|
|
if (resp == NULL) {
|
|
if_printf(sc->hn_ifp, "exec nvs rxbuf conn failed\n");
|
|
error = EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
status = resp->nvs_status;
|
|
vmbus_xact_put(xact);
|
|
xact = NULL;
|
|
|
|
if (status != HN_NVS_STATUS_OK) {
|
|
if_printf(sc->hn_ifp, "nvs rxbuf conn failed: %x\n", status);
|
|
error = EIO;
|
|
goto cleanup;
|
|
}
|
|
sc->hn_flags |= HN_FLAG_RXBUF_CONNECTED;
|
|
|
|
return (0);
|
|
|
|
cleanup:
|
|
if (xact != NULL)
|
|
vmbus_xact_put(xact);
|
|
hn_nvs_disconn_rxbuf(sc);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
hn_nvs_conn_chim(struct hn_softc *sc)
|
|
{
|
|
struct vmbus_xact *xact = NULL;
|
|
struct hn_nvs_chim_conn *chim;
|
|
const struct hn_nvs_chim_connresp *resp;
|
|
size_t resp_len;
|
|
uint32_t status, sectsz;
|
|
int error;
|
|
|
|
/*
|
|
* Connect chimney sending buffer GPADL to the primary channel.
|
|
*
|
|
* NOTE:
|
|
* Only primary channel has chimney sending buffer connected to it.
|
|
* Sub-channels just share this chimney sending buffer.
|
|
*/
|
|
error = vmbus_chan_gpadl_connect(sc->hn_prichan,
|
|
sc->hn_chim_dma.hv_paddr, HN_CHIM_SIZE, &sc->hn_chim_gpadl);
|
|
if (error) {
|
|
if_printf(sc->hn_ifp, "chim gpadl conn failed: %d\n", error);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Connect chimney sending buffer to NVS
|
|
*/
|
|
|
|
xact = vmbus_xact_get(sc->hn_xact, sizeof(*chim));
|
|
if (xact == NULL) {
|
|
if_printf(sc->hn_ifp, "no xact for nvs chim conn\n");
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
chim = vmbus_xact_req_data(xact);
|
|
chim->nvs_type = HN_NVS_TYPE_CHIM_CONN;
|
|
chim->nvs_gpadl = sc->hn_chim_gpadl;
|
|
chim->nvs_sig = HN_NVS_CHIM_SIG;
|
|
|
|
resp_len = sizeof(*resp);
|
|
resp = hn_nvs_xact_execute(sc, xact, chim, sizeof(*chim), &resp_len,
|
|
HN_NVS_TYPE_CHIM_CONNRESP);
|
|
if (resp == NULL) {
|
|
if_printf(sc->hn_ifp, "exec nvs chim conn failed\n");
|
|
error = EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
status = resp->nvs_status;
|
|
sectsz = resp->nvs_sectsz;
|
|
vmbus_xact_put(xact);
|
|
xact = NULL;
|
|
|
|
if (status != HN_NVS_STATUS_OK) {
|
|
if_printf(sc->hn_ifp, "nvs chim conn failed: %x\n", status);
|
|
error = EIO;
|
|
goto cleanup;
|
|
}
|
|
if (sectsz == 0) {
|
|
/*
|
|
* Can't use chimney sending buffer; done!
|
|
*/
|
|
if_printf(sc->hn_ifp, "zero chimney sending buffer "
|
|
"section size\n");
|
|
sc->hn_chim_szmax = 0;
|
|
sc->hn_chim_cnt = 0;
|
|
sc->hn_flags |= HN_FLAG_CHIM_CONNECTED;
|
|
return (0);
|
|
}
|
|
|
|
sc->hn_chim_szmax = sectsz;
|
|
sc->hn_chim_cnt = HN_CHIM_SIZE / sc->hn_chim_szmax;
|
|
if (HN_CHIM_SIZE % sc->hn_chim_szmax != 0) {
|
|
if_printf(sc->hn_ifp, "chimney sending sections are "
|
|
"not properly aligned\n");
|
|
}
|
|
if (sc->hn_chim_cnt % LONG_BIT != 0) {
|
|
if_printf(sc->hn_ifp, "discard %d chimney sending sections\n",
|
|
sc->hn_chim_cnt % LONG_BIT);
|
|
}
|
|
|
|
sc->hn_chim_bmap_cnt = sc->hn_chim_cnt / LONG_BIT;
|
|
sc->hn_chim_bmap = malloc(sc->hn_chim_bmap_cnt * sizeof(u_long),
|
|
M_DEVBUF, M_WAITOK | M_ZERO);
|
|
|
|
/* Done! */
|
|
sc->hn_flags |= HN_FLAG_CHIM_CONNECTED;
|
|
if (bootverbose) {
|
|
if_printf(sc->hn_ifp, "chimney sending buffer %d/%d\n",
|
|
sc->hn_chim_szmax, sc->hn_chim_cnt);
|
|
}
|
|
return (0);
|
|
|
|
cleanup:
|
|
if (xact != NULL)
|
|
vmbus_xact_put(xact);
|
|
hn_nvs_disconn_chim(sc);
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
hn_nvs_disconn_rxbuf(struct hn_softc *sc)
|
|
{
|
|
int error;
|
|
|
|
if (sc->hn_flags & HN_FLAG_RXBUF_CONNECTED) {
|
|
struct hn_nvs_rxbuf_disconn disconn;
|
|
|
|
/*
|
|
* Disconnect RXBUF from NVS.
|
|
*/
|
|
memset(&disconn, 0, sizeof(disconn));
|
|
disconn.nvs_type = HN_NVS_TYPE_RXBUF_DISCONN;
|
|
disconn.nvs_sig = HN_NVS_RXBUF_SIG;
|
|
|
|
/* NOTE: No response. */
|
|
error = hn_nvs_req_send(sc, &disconn, sizeof(disconn));
|
|
if (error) {
|
|
if_printf(sc->hn_ifp,
|
|
"send nvs rxbuf disconn failed: %d\n", error);
|
|
/*
|
|
* Fine for a revoked channel, since the hypervisor
|
|
* does not drain TX bufring for a revoked channel.
|
|
*/
|
|
if (!vmbus_chan_is_revoked(sc->hn_prichan))
|
|
sc->hn_flags |= HN_FLAG_RXBUF_REF;
|
|
}
|
|
sc->hn_flags &= ~HN_FLAG_RXBUF_CONNECTED;
|
|
|
|
/*
|
|
* Wait for the hypervisor to receive this NVS request.
|
|
*
|
|
* NOTE:
|
|
* The TX bufring will not be drained by the hypervisor,
|
|
* if the primary channel is revoked.
|
|
*/
|
|
while (!vmbus_chan_tx_empty(sc->hn_prichan) &&
|
|
!vmbus_chan_is_revoked(sc->hn_prichan))
|
|
pause("waittx", 1);
|
|
/*
|
|
* Linger long enough for NVS to disconnect RXBUF.
|
|
*/
|
|
pause("lingtx", (200 * hz) / 1000);
|
|
}
|
|
|
|
if (sc->hn_rxbuf_gpadl != 0) {
|
|
/*
|
|
* Disconnect RXBUF from primary channel.
|
|
*/
|
|
error = vmbus_chan_gpadl_disconnect(sc->hn_prichan,
|
|
sc->hn_rxbuf_gpadl);
|
|
if (error) {
|
|
if_printf(sc->hn_ifp,
|
|
"rxbuf gpadl disconn failed: %d\n", error);
|
|
sc->hn_flags |= HN_FLAG_RXBUF_REF;
|
|
}
|
|
sc->hn_rxbuf_gpadl = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
hn_nvs_disconn_chim(struct hn_softc *sc)
|
|
{
|
|
int error;
|
|
|
|
if (sc->hn_flags & HN_FLAG_CHIM_CONNECTED) {
|
|
struct hn_nvs_chim_disconn disconn;
|
|
|
|
/*
|
|
* Disconnect chimney sending buffer from NVS.
|
|
*/
|
|
memset(&disconn, 0, sizeof(disconn));
|
|
disconn.nvs_type = HN_NVS_TYPE_CHIM_DISCONN;
|
|
disconn.nvs_sig = HN_NVS_CHIM_SIG;
|
|
|
|
/* NOTE: No response. */
|
|
error = hn_nvs_req_send(sc, &disconn, sizeof(disconn));
|
|
if (error) {
|
|
if_printf(sc->hn_ifp,
|
|
"send nvs chim disconn failed: %d\n", error);
|
|
/*
|
|
* Fine for a revoked channel, since the hypervisor
|
|
* does not drain TX bufring for a revoked channel.
|
|
*/
|
|
if (!vmbus_chan_is_revoked(sc->hn_prichan))
|
|
sc->hn_flags |= HN_FLAG_CHIM_REF;
|
|
}
|
|
sc->hn_flags &= ~HN_FLAG_CHIM_CONNECTED;
|
|
|
|
/*
|
|
* Wait for the hypervisor to receive this NVS request.
|
|
*
|
|
* NOTE:
|
|
* The TX bufring will not be drained by the hypervisor,
|
|
* if the primary channel is revoked.
|
|
*/
|
|
while (!vmbus_chan_tx_empty(sc->hn_prichan) &&
|
|
!vmbus_chan_is_revoked(sc->hn_prichan))
|
|
pause("waittx", 1);
|
|
/*
|
|
* Linger long enough for NVS to disconnect chimney
|
|
* sending buffer.
|
|
*/
|
|
pause("lingtx", (200 * hz) / 1000);
|
|
}
|
|
|
|
if (sc->hn_chim_gpadl != 0) {
|
|
/*
|
|
* Disconnect chimney sending buffer from primary channel.
|
|
*/
|
|
error = vmbus_chan_gpadl_disconnect(sc->hn_prichan,
|
|
sc->hn_chim_gpadl);
|
|
if (error) {
|
|
if_printf(sc->hn_ifp,
|
|
"chim gpadl disconn failed: %d\n", error);
|
|
sc->hn_flags |= HN_FLAG_CHIM_REF;
|
|
}
|
|
sc->hn_chim_gpadl = 0;
|
|
}
|
|
|
|
if (sc->hn_chim_bmap != NULL) {
|
|
free(sc->hn_chim_bmap, M_DEVBUF);
|
|
sc->hn_chim_bmap = NULL;
|
|
sc->hn_chim_bmap_cnt = 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
hn_nvs_doinit(struct hn_softc *sc, uint32_t nvs_ver)
|
|
{
|
|
struct vmbus_xact *xact;
|
|
struct hn_nvs_init *init;
|
|
const struct hn_nvs_init_resp *resp;
|
|
size_t resp_len;
|
|
uint32_t status;
|
|
|
|
xact = vmbus_xact_get(sc->hn_xact, sizeof(*init));
|
|
if (xact == NULL) {
|
|
if_printf(sc->hn_ifp, "no xact for nvs init\n");
|
|
return (ENXIO);
|
|
}
|
|
init = vmbus_xact_req_data(xact);
|
|
init->nvs_type = HN_NVS_TYPE_INIT;
|
|
init->nvs_ver_min = nvs_ver;
|
|
init->nvs_ver_max = nvs_ver;
|
|
|
|
resp_len = sizeof(*resp);
|
|
resp = hn_nvs_xact_execute(sc, xact, init, sizeof(*init), &resp_len,
|
|
HN_NVS_TYPE_INIT_RESP);
|
|
if (resp == NULL) {
|
|
if_printf(sc->hn_ifp, "exec init failed\n");
|
|
vmbus_xact_put(xact);
|
|
return (EIO);
|
|
}
|
|
|
|
status = resp->nvs_status;
|
|
vmbus_xact_put(xact);
|
|
|
|
if (status != HN_NVS_STATUS_OK) {
|
|
if (bootverbose) {
|
|
/*
|
|
* Caller may try another NVS version, and will log
|
|
* error if there are no more NVS versions to try,
|
|
* so don't bark out loud here.
|
|
*/
|
|
if_printf(sc->hn_ifp, "nvs init failed for ver 0x%x\n",
|
|
nvs_ver);
|
|
}
|
|
return (EINVAL);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Configure MTU and enable VLAN.
|
|
*/
|
|
static int
|
|
hn_nvs_conf_ndis(struct hn_softc *sc, int mtu)
|
|
{
|
|
struct hn_nvs_ndis_conf conf;
|
|
int error;
|
|
|
|
memset(&conf, 0, sizeof(conf));
|
|
conf.nvs_type = HN_NVS_TYPE_NDIS_CONF;
|
|
conf.nvs_mtu = mtu;
|
|
conf.nvs_caps = HN_NVS_NDIS_CONF_VLAN;
|
|
if (sc->hn_nvs_ver >= HN_NVS_VERSION_5)
|
|
conf.nvs_caps |= HN_NVS_NDIS_CONF_SRIOV;
|
|
|
|
/* NOTE: No response. */
|
|
error = hn_nvs_req_send(sc, &conf, sizeof(conf));
|
|
if (error) {
|
|
if_printf(sc->hn_ifp, "send nvs ndis conf failed: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
if (bootverbose)
|
|
if_printf(sc->hn_ifp, "nvs ndis conf done\n");
|
|
sc->hn_caps |= HN_CAP_MTU | HN_CAP_VLAN;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hn_nvs_init_ndis(struct hn_softc *sc)
|
|
{
|
|
struct hn_nvs_ndis_init ndis;
|
|
int error;
|
|
|
|
memset(&ndis, 0, sizeof(ndis));
|
|
ndis.nvs_type = HN_NVS_TYPE_NDIS_INIT;
|
|
ndis.nvs_ndis_major = HN_NDIS_VERSION_MAJOR(sc->hn_ndis_ver);
|
|
ndis.nvs_ndis_minor = HN_NDIS_VERSION_MINOR(sc->hn_ndis_ver);
|
|
|
|
/* NOTE: No response. */
|
|
error = hn_nvs_req_send(sc, &ndis, sizeof(ndis));
|
|
if (error)
|
|
if_printf(sc->hn_ifp, "send nvs ndis init failed: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
hn_nvs_init(struct hn_softc *sc)
|
|
{
|
|
int i, error;
|
|
|
|
if (device_is_attached(sc->hn_dev)) {
|
|
/*
|
|
* NVS version and NDIS version MUST NOT be changed.
|
|
*/
|
|
if (bootverbose) {
|
|
if_printf(sc->hn_ifp, "reinit NVS version 0x%x, "
|
|
"NDIS version %u.%u\n", sc->hn_nvs_ver,
|
|
HN_NDIS_VERSION_MAJOR(sc->hn_ndis_ver),
|
|
HN_NDIS_VERSION_MINOR(sc->hn_ndis_ver));
|
|
}
|
|
|
|
error = hn_nvs_doinit(sc, sc->hn_nvs_ver);
|
|
if (error) {
|
|
if_printf(sc->hn_ifp, "reinit NVS version 0x%x "
|
|
"failed: %d\n", sc->hn_nvs_ver, error);
|
|
return (error);
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Find the supported NVS version and set NDIS version accordingly.
|
|
*/
|
|
for (i = 0; i < nitems(hn_nvs_version); ++i) {
|
|
error = hn_nvs_doinit(sc, hn_nvs_version[i]);
|
|
if (!error) {
|
|
sc->hn_nvs_ver = hn_nvs_version[i];
|
|
|
|
/* Set NDIS version according to NVS version. */
|
|
sc->hn_ndis_ver = HN_NDIS_VERSION_6_30;
|
|
if (sc->hn_nvs_ver <= HN_NVS_VERSION_4)
|
|
sc->hn_ndis_ver = HN_NDIS_VERSION_6_1;
|
|
|
|
if (bootverbose) {
|
|
if_printf(sc->hn_ifp, "NVS version 0x%x, "
|
|
"NDIS version %u.%u\n", sc->hn_nvs_ver,
|
|
HN_NDIS_VERSION_MAJOR(sc->hn_ndis_ver),
|
|
HN_NDIS_VERSION_MINOR(sc->hn_ndis_ver));
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
if_printf(sc->hn_ifp, "no NVS available\n");
|
|
return (ENXIO);
|
|
|
|
done:
|
|
if (sc->hn_nvs_ver >= HN_NVS_VERSION_5)
|
|
sc->hn_caps |= HN_CAP_HASHVAL;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
hn_nvs_attach(struct hn_softc *sc, int mtu)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* Initialize NVS.
|
|
*/
|
|
error = hn_nvs_init(sc);
|
|
if (error)
|
|
return (error);
|
|
|
|
if (sc->hn_nvs_ver >= HN_NVS_VERSION_2) {
|
|
/*
|
|
* Configure NDIS before initializing it.
|
|
*/
|
|
error = hn_nvs_conf_ndis(sc, mtu);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Initialize NDIS.
|
|
*/
|
|
error = hn_nvs_init_ndis(sc);
|
|
if (error)
|
|
return (error);
|
|
|
|
/*
|
|
* Connect RXBUF.
|
|
*/
|
|
error = hn_nvs_conn_rxbuf(sc);
|
|
if (error)
|
|
return (error);
|
|
|
|
/*
|
|
* Connect chimney sending buffer.
|
|
*/
|
|
error = hn_nvs_conn_chim(sc);
|
|
if (error) {
|
|
hn_nvs_disconn_rxbuf(sc);
|
|
return (error);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
hn_nvs_detach(struct hn_softc *sc)
|
|
{
|
|
|
|
/* NOTE: there are no requests to stop the NVS. */
|
|
hn_nvs_disconn_rxbuf(sc);
|
|
hn_nvs_disconn_chim(sc);
|
|
}
|
|
|
|
void
|
|
hn_nvs_sent_xact(struct hn_nvs_sendctx *sndc,
|
|
struct hn_softc *sc __unused, struct vmbus_channel *chan __unused,
|
|
const void *data, int dlen)
|
|
{
|
|
|
|
vmbus_xact_wakeup(sndc->hn_cbarg, data, dlen);
|
|
}
|
|
|
|
static void
|
|
hn_nvs_sent_none(struct hn_nvs_sendctx *sndc __unused,
|
|
struct hn_softc *sc __unused, struct vmbus_channel *chan __unused,
|
|
const void *data __unused, int dlen __unused)
|
|
{
|
|
/* EMPTY */
|
|
}
|
|
|
|
int
|
|
hn_nvs_alloc_subchans(struct hn_softc *sc, int *nsubch0)
|
|
{
|
|
struct vmbus_xact *xact;
|
|
struct hn_nvs_subch_req *req;
|
|
const struct hn_nvs_subch_resp *resp;
|
|
int error, nsubch_req;
|
|
uint32_t nsubch;
|
|
size_t resp_len;
|
|
|
|
nsubch_req = *nsubch0;
|
|
KASSERT(nsubch_req > 0, ("invalid # of sub-channels %d", nsubch_req));
|
|
|
|
xact = vmbus_xact_get(sc->hn_xact, sizeof(*req));
|
|
if (xact == NULL) {
|
|
if_printf(sc->hn_ifp, "no xact for nvs subch alloc\n");
|
|
return (ENXIO);
|
|
}
|
|
req = vmbus_xact_req_data(xact);
|
|
req->nvs_type = HN_NVS_TYPE_SUBCH_REQ;
|
|
req->nvs_op = HN_NVS_SUBCH_OP_ALLOC;
|
|
req->nvs_nsubch = nsubch_req;
|
|
|
|
resp_len = sizeof(*resp);
|
|
resp = hn_nvs_xact_execute(sc, xact, req, sizeof(*req), &resp_len,
|
|
HN_NVS_TYPE_SUBCH_RESP);
|
|
if (resp == NULL) {
|
|
if_printf(sc->hn_ifp, "exec nvs subch alloc failed\n");
|
|
error = EIO;
|
|
goto done;
|
|
}
|
|
if (resp->nvs_status != HN_NVS_STATUS_OK) {
|
|
if_printf(sc->hn_ifp, "nvs subch alloc failed: %x\n",
|
|
resp->nvs_status);
|
|
error = EIO;
|
|
goto done;
|
|
}
|
|
|
|
nsubch = resp->nvs_nsubch;
|
|
if (nsubch > nsubch_req) {
|
|
if_printf(sc->hn_ifp, "%u subchans are allocated, "
|
|
"requested %d\n", nsubch, nsubch_req);
|
|
nsubch = nsubch_req;
|
|
}
|
|
*nsubch0 = nsubch;
|
|
error = 0;
|
|
done:
|
|
vmbus_xact_put(xact);
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
hn_nvs_send_rndis_ctrl(struct vmbus_channel *chan,
|
|
struct hn_nvs_sendctx *sndc, struct vmbus_gpa *gpa, int gpa_cnt)
|
|
{
|
|
|
|
return hn_nvs_send_rndis_sglist(chan, HN_NVS_RNDIS_MTYPE_CTRL,
|
|
sndc, gpa, gpa_cnt);
|
|
}
|
|
|
|
void
|
|
hn_nvs_set_datapath(struct hn_softc *sc, uint32_t path)
|
|
{
|
|
struct hn_nvs_datapath dp;
|
|
|
|
memset(&dp, 0, sizeof(dp));
|
|
dp.nvs_type = HN_NVS_TYPE_SET_DATAPATH;
|
|
dp.nvs_active_path = path;
|
|
|
|
hn_nvs_req_send(sc, &dp, sizeof(dp));
|
|
}
|