While there, also make MODULE_PNP_INFO to reflect that the device description is provided. Reported-by: jrtc27 Reviewed-by: jrtc27, imp Differential Revision: https://reviews.freebsd.org/D40430
873 lines
21 KiB
C
873 lines
21 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 2023 Google LLC
|
|
*
|
|
* 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.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* 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 HOLDER 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 "gve.h"
|
|
#include "gve_adminq.h"
|
|
|
|
#define GVE_DRIVER_VERSION "GVE-FBSD-1.0.0\n"
|
|
#define GVE_VERSION_MAJOR 0
|
|
#define GVE_VERSION_MINOR 9
|
|
#define GVE_VERSION_SUB 0
|
|
|
|
#define GVE_DEFAULT_RX_COPYBREAK 256
|
|
|
|
/* Devices supported by this driver. */
|
|
static struct gve_dev {
|
|
uint16_t vendor_id;
|
|
uint16_t device_id;
|
|
const char *name;
|
|
} gve_devs[] = {
|
|
{ PCI_VENDOR_ID_GOOGLE, PCI_DEV_ID_GVNIC, "gVNIC" }
|
|
};
|
|
|
|
struct sx gve_global_lock;
|
|
|
|
static int
|
|
gve_verify_driver_compatibility(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
struct gve_driver_info *driver_info;
|
|
struct gve_dma_handle driver_info_mem;
|
|
|
|
err = gve_dma_alloc_coherent(priv, sizeof(struct gve_driver_info),
|
|
PAGE_SIZE, &driver_info_mem);
|
|
|
|
if (err != 0)
|
|
return (ENOMEM);
|
|
|
|
driver_info = driver_info_mem.cpu_addr;
|
|
|
|
*driver_info = (struct gve_driver_info) {
|
|
.os_type = 3, /* Freebsd */
|
|
.driver_major = GVE_VERSION_MAJOR,
|
|
.driver_minor = GVE_VERSION_MINOR,
|
|
.driver_sub = GVE_VERSION_SUB,
|
|
.os_version_major = htobe32(FBSD_VERSION_MAJOR),
|
|
.os_version_minor = htobe32(FBSD_VERSION_MINOR),
|
|
.os_version_sub = htobe32(FBSD_VERSION_PATCH),
|
|
.driver_capability_flags = {
|
|
htobe64(GVE_DRIVER_CAPABILITY_FLAGS1),
|
|
htobe64(GVE_DRIVER_CAPABILITY_FLAGS2),
|
|
htobe64(GVE_DRIVER_CAPABILITY_FLAGS3),
|
|
htobe64(GVE_DRIVER_CAPABILITY_FLAGS4),
|
|
},
|
|
};
|
|
|
|
snprintf(driver_info->os_version_str1, sizeof(driver_info->os_version_str1),
|
|
"FreeBSD %u", __FreeBSD_version);
|
|
|
|
bus_dmamap_sync(driver_info_mem.tag, driver_info_mem.map,
|
|
BUS_DMASYNC_PREREAD);
|
|
|
|
err = gve_adminq_verify_driver_compatibility(priv,
|
|
sizeof(struct gve_driver_info), driver_info_mem.bus_addr);
|
|
|
|
/* It's ok if the device doesn't support this */
|
|
if (err == EOPNOTSUPP)
|
|
err = 0;
|
|
|
|
gve_dma_free_coherent(&driver_info_mem);
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
gve_up(struct gve_priv *priv)
|
|
{
|
|
if_t ifp = priv->ifp;
|
|
int err;
|
|
|
|
GVE_IFACE_LOCK_ASSERT(priv->gve_iface_lock);
|
|
|
|
if (device_is_attached(priv->dev) == 0) {
|
|
device_printf(priv->dev, "Cannot bring the iface up when detached\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_QUEUES_UP))
|
|
return (0);
|
|
|
|
if_clearhwassist(ifp);
|
|
if (if_getcapenable(ifp) & IFCAP_TXCSUM)
|
|
if_sethwassistbits(ifp, CSUM_TCP | CSUM_UDP, 0);
|
|
if (if_getcapenable(ifp) & IFCAP_TXCSUM_IPV6)
|
|
if_sethwassistbits(ifp, CSUM_IP6_TCP | CSUM_IP6_UDP, 0);
|
|
if (if_getcapenable(ifp) & IFCAP_TSO4)
|
|
if_sethwassistbits(ifp, CSUM_IP_TSO, 0);
|
|
if (if_getcapenable(ifp) & IFCAP_TSO6)
|
|
if_sethwassistbits(ifp, CSUM_IP6_TSO, 0);
|
|
|
|
err = gve_register_qpls(priv);
|
|
if (err != 0)
|
|
goto reset;
|
|
|
|
err = gve_create_rx_rings(priv);
|
|
if (err != 0)
|
|
goto reset;
|
|
|
|
err = gve_create_tx_rings(priv);
|
|
if (err != 0)
|
|
goto reset;
|
|
|
|
if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE);
|
|
|
|
if (!gve_get_state_flag(priv, GVE_STATE_FLAG_LINK_UP)) {
|
|
if_link_state_change(ifp, LINK_STATE_UP);
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_LINK_UP);
|
|
}
|
|
|
|
gve_unmask_all_queue_irqs(priv);
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_QUEUES_UP);
|
|
priv->interface_up_cnt++;
|
|
return (0);
|
|
|
|
reset:
|
|
gve_schedule_reset(priv);
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
gve_down(struct gve_priv *priv)
|
|
{
|
|
GVE_IFACE_LOCK_ASSERT(priv->gve_iface_lock);
|
|
|
|
if (!gve_get_state_flag(priv, GVE_STATE_FLAG_QUEUES_UP))
|
|
return;
|
|
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_LINK_UP)) {
|
|
if_link_state_change(priv->ifp, LINK_STATE_DOWN);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_LINK_UP);
|
|
}
|
|
|
|
if_setdrvflagbits(priv->ifp, IFF_DRV_OACTIVE, IFF_DRV_RUNNING);
|
|
|
|
if (gve_destroy_rx_rings(priv) != 0)
|
|
goto reset;
|
|
|
|
if (gve_destroy_tx_rings(priv) != 0)
|
|
goto reset;
|
|
|
|
if (gve_unregister_qpls(priv) != 0)
|
|
goto reset;
|
|
|
|
gve_mask_all_queue_irqs(priv);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_QUEUES_UP);
|
|
priv->interface_down_cnt++;
|
|
return;
|
|
|
|
reset:
|
|
gve_schedule_reset(priv);
|
|
}
|
|
|
|
static int
|
|
gve_set_mtu(if_t ifp, uint32_t new_mtu)
|
|
{
|
|
struct gve_priv *priv = if_getsoftc(ifp);
|
|
int err;
|
|
|
|
if ((new_mtu > priv->max_mtu) || (new_mtu < ETHERMIN)) {
|
|
device_printf(priv->dev, "Invalid new MTU setting. new mtu: %d max mtu: %d min mtu: %d\n",
|
|
new_mtu, priv->max_mtu, ETHERMIN);
|
|
return (EINVAL);
|
|
}
|
|
|
|
err = gve_adminq_set_mtu(priv, new_mtu);
|
|
if (err == 0) {
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "MTU set to %d\n", new_mtu);
|
|
if_setmtu(ifp, new_mtu);
|
|
} else {
|
|
device_printf(priv->dev, "Failed to set MTU to %d\n", new_mtu);
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
gve_init(void *arg)
|
|
{
|
|
struct gve_priv *priv = (struct gve_priv *)arg;
|
|
|
|
if (!gve_get_state_flag(priv, GVE_STATE_FLAG_QUEUES_UP)) {
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
gve_up(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
}
|
|
}
|
|
|
|
static int
|
|
gve_ioctl(if_t ifp, u_long command, caddr_t data)
|
|
{
|
|
struct gve_priv *priv;
|
|
struct ifreq *ifr;
|
|
int rc = 0;
|
|
|
|
priv = if_getsoftc(ifp);
|
|
ifr = (struct ifreq *)data;
|
|
|
|
switch (command) {
|
|
case SIOCSIFMTU:
|
|
if (if_getmtu(ifp) == ifr->ifr_mtu)
|
|
break;
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
gve_down(priv);
|
|
gve_set_mtu(ifp, ifr->ifr_mtu);
|
|
rc = gve_up(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
if ((if_getflags(ifp) & IFF_UP) != 0) {
|
|
if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) {
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
rc = gve_up(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
}
|
|
} else {
|
|
if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) {
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
gve_down(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SIOCSIFCAP:
|
|
if (ifr->ifr_reqcap == if_getcapenable(ifp))
|
|
break;
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
gve_down(priv);
|
|
if_setcapenable(ifp, ifr->ifr_reqcap);
|
|
rc = gve_up(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
break;
|
|
|
|
case SIOCSIFMEDIA:
|
|
/* FALLTHROUGH */
|
|
case SIOCGIFMEDIA:
|
|
rc = ifmedia_ioctl(ifp, ifr, &priv->media, command);
|
|
break;
|
|
|
|
default:
|
|
rc = ether_ioctl(ifp, command, data);
|
|
break;
|
|
}
|
|
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
gve_media_change(if_t ifp)
|
|
{
|
|
struct gve_priv *priv = if_getsoftc(ifp);
|
|
|
|
device_printf(priv->dev, "Media change not supported\n");
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
gve_media_status(if_t ifp, struct ifmediareq *ifmr)
|
|
{
|
|
struct gve_priv *priv = if_getsoftc(ifp);
|
|
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
|
|
ifmr->ifm_status = IFM_AVALID;
|
|
ifmr->ifm_active = IFM_ETHER;
|
|
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_LINK_UP)) {
|
|
ifmr->ifm_status |= IFM_ACTIVE;
|
|
ifmr->ifm_active |= IFM_AUTO;
|
|
} else {
|
|
ifmr->ifm_active |= IFM_NONE;
|
|
}
|
|
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
}
|
|
|
|
static uint64_t
|
|
gve_get_counter(if_t ifp, ift_counter cnt)
|
|
{
|
|
struct gve_priv *priv;
|
|
uint64_t rpackets = 0;
|
|
uint64_t tpackets = 0;
|
|
uint64_t rbytes = 0;
|
|
uint64_t tbytes = 0;
|
|
uint64_t rx_dropped_pkt = 0;
|
|
uint64_t tx_dropped_pkt = 0;
|
|
|
|
priv = if_getsoftc(ifp);
|
|
|
|
gve_accum_stats(priv, &rpackets, &rbytes, &rx_dropped_pkt, &tpackets,
|
|
&tbytes, &tx_dropped_pkt);
|
|
|
|
switch (cnt) {
|
|
case IFCOUNTER_IPACKETS:
|
|
return (rpackets);
|
|
|
|
case IFCOUNTER_OPACKETS:
|
|
return (tpackets);
|
|
|
|
case IFCOUNTER_IBYTES:
|
|
return (rbytes);
|
|
|
|
case IFCOUNTER_OBYTES:
|
|
return (tbytes);
|
|
|
|
case IFCOUNTER_IQDROPS:
|
|
return (rx_dropped_pkt);
|
|
|
|
case IFCOUNTER_OQDROPS:
|
|
return (tx_dropped_pkt);
|
|
|
|
default:
|
|
return (if_get_counter_default(ifp, cnt));
|
|
}
|
|
}
|
|
|
|
static int
|
|
gve_setup_ifnet(device_t dev, struct gve_priv *priv)
|
|
{
|
|
int caps = 0;
|
|
if_t ifp;
|
|
|
|
ifp = priv->ifp = if_alloc(IFT_ETHER);
|
|
if (ifp == NULL) {
|
|
device_printf(priv->dev, "Failed to allocate ifnet struct\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
|
|
if_setsoftc(ifp, priv);
|
|
if_setdev(ifp, dev);
|
|
if_setinitfn(ifp, gve_init);
|
|
if_setioctlfn(ifp, gve_ioctl);
|
|
if_settransmitfn(ifp, gve_xmit_ifp);
|
|
if_setqflushfn(ifp, gve_qflush);
|
|
|
|
#if __FreeBSD_version >= 1400086
|
|
if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
|
|
#else
|
|
if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_KNOWSEPOCH);
|
|
#endif
|
|
|
|
ifmedia_init(&priv->media, IFM_IMASK, gve_media_change, gve_media_status);
|
|
if_setgetcounterfn(ifp, gve_get_counter);
|
|
|
|
caps = IFCAP_RXCSUM |
|
|
IFCAP_TXCSUM |
|
|
IFCAP_TXCSUM_IPV6 |
|
|
IFCAP_TSO |
|
|
IFCAP_LRO;
|
|
|
|
if ((priv->supported_features & GVE_SUP_JUMBO_FRAMES_MASK) != 0)
|
|
caps |= IFCAP_JUMBO_MTU;
|
|
|
|
if_setcapabilities(ifp, caps);
|
|
if_setcapenable(ifp, caps);
|
|
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "Setting initial MTU to %d\n", priv->max_mtu);
|
|
if_setmtu(ifp, priv->max_mtu);
|
|
|
|
ether_ifattach(ifp, priv->mac);
|
|
|
|
ifmedia_add(&priv->media, IFM_ETHER | IFM_AUTO, 0, NULL);
|
|
ifmedia_set(&priv->media, IFM_ETHER | IFM_AUTO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
gve_alloc_counter_array(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
err = gve_dma_alloc_coherent(priv, sizeof(uint32_t) * priv->num_event_counters,
|
|
PAGE_SIZE, &priv->counter_array_mem);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
priv->counters = priv->counter_array_mem.cpu_addr;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
gve_free_counter_array(struct gve_priv *priv)
|
|
{
|
|
if (priv->counters != NULL)
|
|
gve_dma_free_coherent(&priv->counter_array_mem);
|
|
priv->counter_array_mem = (struct gve_dma_handle){};
|
|
}
|
|
|
|
static int
|
|
gve_alloc_irq_db_array(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
err = gve_dma_alloc_coherent(priv,
|
|
sizeof(struct gve_irq_db) * (priv->num_queues), PAGE_SIZE,
|
|
&priv->irqs_db_mem);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
priv->irq_db_indices = priv->irqs_db_mem.cpu_addr;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
gve_free_irq_db_array(struct gve_priv *priv)
|
|
{
|
|
if (priv->irq_db_indices != NULL)
|
|
gve_dma_free_coherent(&priv->irqs_db_mem);
|
|
priv->irqs_db_mem = (struct gve_dma_handle){};
|
|
}
|
|
|
|
static void
|
|
gve_free_rings(struct gve_priv *priv)
|
|
{
|
|
gve_free_irqs(priv);
|
|
gve_free_tx_rings(priv);
|
|
gve_free_rx_rings(priv);
|
|
gve_free_qpls(priv);
|
|
}
|
|
|
|
static int
|
|
gve_alloc_rings(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
err = gve_alloc_qpls(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_alloc_rx_rings(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_alloc_tx_rings(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_alloc_irqs(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
return (0);
|
|
|
|
abort:
|
|
gve_free_rings(priv);
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
gve_deconfigure_resources(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_RESOURCES_OK)) {
|
|
err = gve_adminq_deconfigure_device_resources(priv);
|
|
if (err != 0) {
|
|
device_printf(priv->dev, "Failed to deconfigure device resources: err=%d\n",
|
|
err);
|
|
return;
|
|
}
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "Deconfigured device resources\n");
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_RESOURCES_OK);
|
|
}
|
|
|
|
gve_free_irq_db_array(priv);
|
|
gve_free_counter_array(priv);
|
|
}
|
|
|
|
static int
|
|
gve_configure_resources(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_RESOURCES_OK))
|
|
return (0);
|
|
|
|
err = gve_alloc_counter_array(priv);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
err = gve_alloc_irq_db_array(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_adminq_configure_device_resources(priv);
|
|
if (err != 0) {
|
|
device_printf(priv->dev, "Failed to configure device resources: err=%d\n",
|
|
err);
|
|
err = (ENXIO);
|
|
goto abort;
|
|
}
|
|
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_RESOURCES_OK);
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "Configured device resources\n");
|
|
return (0);
|
|
|
|
abort:
|
|
gve_deconfigure_resources(priv);
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
gve_set_queue_cnts(struct gve_priv *priv)
|
|
{
|
|
priv->tx_cfg.max_queues = gve_reg_bar_read_4(priv, MAX_TX_QUEUES);
|
|
priv->rx_cfg.max_queues = gve_reg_bar_read_4(priv, MAX_RX_QUEUES);
|
|
priv->tx_cfg.num_queues = priv->tx_cfg.max_queues;
|
|
priv->rx_cfg.num_queues = priv->rx_cfg.max_queues;
|
|
|
|
if (priv->default_num_queues > 0) {
|
|
priv->tx_cfg.num_queues = MIN(priv->default_num_queues,
|
|
priv->tx_cfg.num_queues);
|
|
priv->rx_cfg.num_queues = MIN(priv->default_num_queues,
|
|
priv->rx_cfg.num_queues);
|
|
}
|
|
|
|
priv->num_queues = priv->tx_cfg.num_queues + priv->rx_cfg.num_queues;
|
|
priv->mgmt_msix_idx = priv->num_queues;
|
|
}
|
|
|
|
static int
|
|
gve_alloc_adminq_and_describe_device(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
if ((err = gve_adminq_alloc(priv)) != 0)
|
|
return (err);
|
|
|
|
if ((err = gve_verify_driver_compatibility(priv)) != 0) {
|
|
device_printf(priv->dev,
|
|
"Failed to verify driver compatibility: err=%d\n", err);
|
|
goto abort;
|
|
}
|
|
|
|
if ((err = gve_adminq_describe_device(priv)) != 0)
|
|
goto abort;
|
|
|
|
gve_set_queue_cnts(priv);
|
|
|
|
priv->num_registered_pages = 0;
|
|
return (0);
|
|
|
|
abort:
|
|
gve_release_adminq(priv);
|
|
return (err);
|
|
}
|
|
|
|
void
|
|
gve_schedule_reset(struct gve_priv *priv)
|
|
{
|
|
if (gve_get_state_flag(priv, GVE_STATE_FLAG_IN_RESET))
|
|
return;
|
|
|
|
device_printf(priv->dev, "Scheduling reset task!\n");
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_DO_RESET);
|
|
taskqueue_enqueue(priv->service_tq, &priv->service_task);
|
|
}
|
|
|
|
static void
|
|
gve_destroy(struct gve_priv *priv)
|
|
{
|
|
gve_down(priv);
|
|
gve_deconfigure_resources(priv);
|
|
gve_release_adminq(priv);
|
|
}
|
|
|
|
static void
|
|
gve_restore(struct gve_priv *priv)
|
|
{
|
|
int err;
|
|
|
|
err = gve_adminq_alloc(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_configure_resources(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_up(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
return;
|
|
|
|
abort:
|
|
device_printf(priv->dev, "Restore failed!\n");
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gve_handle_reset(struct gve_priv *priv)
|
|
{
|
|
if (!gve_get_state_flag(priv, GVE_STATE_FLAG_DO_RESET))
|
|
return;
|
|
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_DO_RESET);
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_IN_RESET);
|
|
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
|
|
if_setdrvflagbits(priv->ifp, IFF_DRV_OACTIVE, IFF_DRV_RUNNING);
|
|
if_link_state_change(priv->ifp, LINK_STATE_DOWN);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_LINK_UP);
|
|
|
|
/*
|
|
* Releasing the adminq causes the NIC to destroy all resources
|
|
* registered with it, so by clearing the flags beneath we cause
|
|
* the subsequent gve_down call below to not attempt to tell the
|
|
* NIC to destroy these resources again.
|
|
*
|
|
* The call to gve_down is needed in the first place to refresh
|
|
* the state and the DMA-able memory within each driver ring.
|
|
*/
|
|
gve_release_adminq(priv);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_RESOURCES_OK);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_QPLREG_OK);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_RX_RINGS_OK);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_TX_RINGS_OK);
|
|
|
|
gve_down(priv);
|
|
gve_restore(priv);
|
|
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
|
|
priv->reset_cnt++;
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_IN_RESET);
|
|
}
|
|
|
|
static void
|
|
gve_handle_link_status(struct gve_priv *priv)
|
|
{
|
|
uint32_t status = gve_reg_bar_read_4(priv, DEVICE_STATUS);
|
|
bool link_up = status & GVE_DEVICE_STATUS_LINK_STATUS;
|
|
|
|
if (link_up == gve_get_state_flag(priv, GVE_STATE_FLAG_LINK_UP))
|
|
return;
|
|
|
|
if (link_up) {
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "Device link is up.\n");
|
|
if_link_state_change(priv->ifp, LINK_STATE_UP);
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_LINK_UP);
|
|
} else {
|
|
device_printf(priv->dev, "Device link is down.\n");
|
|
if_link_state_change(priv->ifp, LINK_STATE_DOWN);
|
|
gve_clear_state_flag(priv, GVE_STATE_FLAG_LINK_UP);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gve_service_task(void *arg, int pending)
|
|
{
|
|
struct gve_priv *priv = (struct gve_priv *)arg;
|
|
uint32_t status = gve_reg_bar_read_4(priv, DEVICE_STATUS);
|
|
|
|
if (((GVE_DEVICE_STATUS_RESET_MASK & status) != 0) &&
|
|
!gve_get_state_flag(priv, GVE_STATE_FLAG_IN_RESET)) {
|
|
device_printf(priv->dev, "Device requested reset\n");
|
|
gve_set_state_flag(priv, GVE_STATE_FLAG_DO_RESET);
|
|
}
|
|
|
|
gve_handle_reset(priv);
|
|
gve_handle_link_status(priv);
|
|
}
|
|
|
|
static int
|
|
gve_probe(device_t dev)
|
|
{
|
|
uint16_t deviceid, vendorid;
|
|
int i;
|
|
|
|
vendorid = pci_get_vendor(dev);
|
|
deviceid = pci_get_device(dev);
|
|
|
|
for (i = 0; i < nitems(gve_devs); i++) {
|
|
if (vendorid == gve_devs[i].vendor_id &&
|
|
deviceid == gve_devs[i].device_id) {
|
|
device_set_desc(dev, gve_devs[i].name);
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
}
|
|
return (ENXIO);
|
|
}
|
|
|
|
static void
|
|
gve_free_sys_res_mem(struct gve_priv *priv)
|
|
{
|
|
if (priv->msix_table != NULL)
|
|
bus_release_resource(priv->dev, SYS_RES_MEMORY,
|
|
rman_get_rid(priv->msix_table), priv->msix_table);
|
|
|
|
if (priv->db_bar != NULL)
|
|
bus_release_resource(priv->dev, SYS_RES_MEMORY,
|
|
rman_get_rid(priv->db_bar), priv->db_bar);
|
|
|
|
if (priv->reg_bar != NULL)
|
|
bus_release_resource(priv->dev, SYS_RES_MEMORY,
|
|
rman_get_rid(priv->reg_bar), priv->reg_bar);
|
|
}
|
|
|
|
static int
|
|
gve_attach(device_t dev)
|
|
{
|
|
struct gve_priv *priv;
|
|
int rid;
|
|
int err;
|
|
|
|
priv = device_get_softc(dev);
|
|
priv->dev = dev;
|
|
GVE_IFACE_LOCK_INIT(priv->gve_iface_lock);
|
|
|
|
pci_enable_busmaster(dev);
|
|
|
|
rid = PCIR_BAR(GVE_REGISTER_BAR);
|
|
priv->reg_bar = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&rid, RF_ACTIVE);
|
|
if (priv->reg_bar == NULL) {
|
|
device_printf(dev, "Failed to allocate BAR0\n");
|
|
err = ENXIO;
|
|
goto abort;
|
|
}
|
|
|
|
rid = PCIR_BAR(GVE_DOORBELL_BAR);
|
|
priv->db_bar = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&rid, RF_ACTIVE);
|
|
if (priv->db_bar == NULL) {
|
|
device_printf(dev, "Failed to allocate BAR2\n");
|
|
err = ENXIO;
|
|
goto abort;
|
|
}
|
|
|
|
rid = pci_msix_table_bar(priv->dev);
|
|
priv->msix_table = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&rid, RF_ACTIVE);
|
|
if (priv->msix_table == NULL) {
|
|
device_printf(dev, "Failed to allocate msix table\n");
|
|
err = ENXIO;
|
|
goto abort;
|
|
}
|
|
|
|
err = gve_alloc_adminq_and_describe_device(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_configure_resources(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_alloc_rings(priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
err = gve_setup_ifnet(dev, priv);
|
|
if (err != 0)
|
|
goto abort;
|
|
|
|
priv->rx_copybreak = GVE_DEFAULT_RX_COPYBREAK;
|
|
|
|
bus_write_multi_1(priv->reg_bar, DRIVER_VERSION, GVE_DRIVER_VERSION,
|
|
sizeof(GVE_DRIVER_VERSION) - 1);
|
|
|
|
TASK_INIT(&priv->service_task, 0, gve_service_task, priv);
|
|
priv->service_tq = taskqueue_create("gve service", M_WAITOK | M_ZERO,
|
|
taskqueue_thread_enqueue, &priv->service_tq);
|
|
taskqueue_start_threads(&priv->service_tq, 1, PI_NET, "%s service tq",
|
|
device_get_nameunit(priv->dev));
|
|
|
|
gve_setup_sysctl(priv);
|
|
|
|
if (bootverbose)
|
|
device_printf(priv->dev, "Successfully attached %s", GVE_DRIVER_VERSION);
|
|
return (0);
|
|
|
|
abort:
|
|
gve_free_rings(priv);
|
|
gve_deconfigure_resources(priv);
|
|
gve_release_adminq(priv);
|
|
gve_free_sys_res_mem(priv);
|
|
GVE_IFACE_LOCK_DESTROY(priv->gve_iface_lock);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
gve_detach(device_t dev)
|
|
{
|
|
struct gve_priv *priv = device_get_softc(dev);
|
|
if_t ifp = priv->ifp;
|
|
|
|
ether_ifdetach(ifp);
|
|
|
|
GVE_IFACE_LOCK_LOCK(priv->gve_iface_lock);
|
|
gve_destroy(priv);
|
|
GVE_IFACE_LOCK_UNLOCK(priv->gve_iface_lock);
|
|
|
|
gve_free_rings(priv);
|
|
gve_free_sys_res_mem(priv);
|
|
GVE_IFACE_LOCK_DESTROY(priv->gve_iface_lock);
|
|
|
|
while (taskqueue_cancel(priv->service_tq, &priv->service_task, NULL))
|
|
taskqueue_drain(priv->service_tq, &priv->service_task);
|
|
taskqueue_free(priv->service_tq);
|
|
|
|
if_free(ifp);
|
|
return (bus_generic_detach(dev));
|
|
}
|
|
|
|
static device_method_t gve_methods[] = {
|
|
DEVMETHOD(device_probe, gve_probe),
|
|
DEVMETHOD(device_attach, gve_attach),
|
|
DEVMETHOD(device_detach, gve_detach),
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t gve_driver = {
|
|
"gve",
|
|
gve_methods,
|
|
sizeof(struct gve_priv)
|
|
};
|
|
|
|
#if __FreeBSD_version < 1301503
|
|
static devclass_t gve_devclass;
|
|
|
|
DRIVER_MODULE(gve, pci, gve_driver, gve_devclass, 0, 0);
|
|
#else
|
|
DRIVER_MODULE(gve, pci, gve_driver, 0, 0);
|
|
#endif
|
|
MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, gve, gve_devs,
|
|
nitems(gve_devs));
|