#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/ksynch.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/debug.h>
#include <sys/pci.h>
#include <sys/ethernet.h>
#include <sys/vlan.h>
#include <sys/sysmacros.h>
#include <sys/smbios.h>
#include <sys/dlpi.h>
#include <sys/taskq.h>
#include <sys/pattr.h>
#include <sys/strsun.h>
#include <sys/random.h>
#include <sys/containerof.h>
#include <sys/stream.h>
#include <inet/tcp.h>
#include <sys/mac.h>
#include <sys/mac_provider.h>
#include <sys/mac_ether.h>
#include "virtio.h"
#include "vioif.h"
int vioif_fake_promisc_success = 1;
static int vioif_quiesce(dev_info_t *);
static int vioif_attach(dev_info_t *, ddi_attach_cmd_t);
static int vioif_detach(dev_info_t *, ddi_detach_cmd_t);
static boolean_t vioif_has_feature(vioif_t *, uint64_t);
static void vioif_reclaim_restart(vioif_t *);
static int vioif_m_stat(void *, uint_t, uint64_t *);
static void vioif_m_stop(void *);
static int vioif_m_start(void *);
static int vioif_m_multicst(void *, boolean_t, const uint8_t *);
static int vioif_m_setpromisc(void *, boolean_t);
static int vioif_m_unicst(void *, const uint8_t *);
static mblk_t *vioif_m_tx(void *, mblk_t *);
static int vioif_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
const void *);
static int vioif_m_getprop(void *, const char *, mac_prop_id_t, uint_t, void *);
static void vioif_m_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
static boolean_t vioif_m_getcapab(void *, mac_capab_t, void *);
static uint_t vioif_add_rx(vioif_t *);
static void vioif_get_data(vioif_t *);
static struct cb_ops vioif_cb_ops = {
.cb_rev = CB_REV,
.cb_flag = D_MP | D_NEW,
.cb_open = nulldev,
.cb_close = nulldev,
.cb_strategy = nodev,
.cb_print = nodev,
.cb_dump = nodev,
.cb_read = nodev,
.cb_write = nodev,
.cb_ioctl = nodev,
.cb_devmap = nodev,
.cb_mmap = nodev,
.cb_segmap = nodev,
.cb_chpoll = nochpoll,
.cb_prop_op = ddi_prop_op,
.cb_str = NULL,
.cb_aread = nodev,
.cb_awrite = nodev,
};
static struct dev_ops vioif_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_attach = vioif_attach,
.devo_detach = vioif_detach,
.devo_quiesce = vioif_quiesce,
.devo_cb_ops = &vioif_cb_ops,
.devo_getinfo = NULL,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_reset = nodev,
.devo_bus_ops = NULL,
.devo_power = NULL,
};
static struct modldrv vioif_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "VIRTIO network driver",
.drv_dev_ops = &vioif_dev_ops
};
static struct modlinkage vioif_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &vioif_modldrv, NULL }
};
static mac_callbacks_t vioif_mac_callbacks = {
.mc_getstat = vioif_m_stat,
.mc_start = vioif_m_start,
.mc_stop = vioif_m_stop,
.mc_setpromisc = vioif_m_setpromisc,
.mc_multicst = vioif_m_multicst,
.mc_unicst = vioif_m_unicst,
.mc_tx = vioif_m_tx,
.mc_callbacks = (MC_GETCAPAB | MC_SETPROP |
MC_GETPROP | MC_PROPINFO),
.mc_getcapab = vioif_m_getcapab,
.mc_setprop = vioif_m_setprop,
.mc_getprop = vioif_m_getprop,
.mc_propinfo = vioif_m_propinfo,
};
static const uchar_t vioif_broadcast[ETHERADDRL] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
uint_t vioif_reclaim_ms = 200;
int vioif_allowed_int_types = -1;
ddi_dma_attr_t vioif_dma_attr_bufs = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0x0000000000000000,
.dma_attr_addr_hi = 0xFFFFFFFFFFFFFFFF,
.dma_attr_count_max = 0x00000000FFFFFFFF,
.dma_attr_align = VIOIF_HEADER_ALIGN,
.dma_attr_burstsizes = 1,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0x00000000FFFFFFFF,
.dma_attr_seg = 0x00000000FFFFFFFF,
.dma_attr_sgllen = 0,
.dma_attr_granular = 1,
.dma_attr_flags = 0
};
ddi_dma_attr_t vioif_dma_attr_external = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0x0000000000000000,
.dma_attr_addr_hi = 0xFFFFFFFFFFFFFFFF,
.dma_attr_count_max = 0x00000000FFFFFFFF,
.dma_attr_align = 1,
.dma_attr_burstsizes = 1,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0x00000000FFFFFFFF,
.dma_attr_seg = 0x00000000FFFFFFFF,
.dma_attr_sgllen = VIOIF_MAX_SEGS - 1,
.dma_attr_granular = 1,
.dma_attr_flags = 0
};
#define VIOIF_MACPROP_TXCOPY_THRESH "_txcopy_thresh"
#define VIOIF_MACPROP_TXCOPY_THRESH_DEF 300
#define VIOIF_MACPROP_TXCOPY_THRESH_MAX 640
#define VIOIF_MACPROP_RXCOPY_THRESH "_rxcopy_thresh"
#define VIOIF_MACPROP_RXCOPY_THRESH_DEF 300
#define VIOIF_MACPROP_RXCOPY_THRESH_MAX 640
static char *vioif_priv_props[] = {
VIOIF_MACPROP_TXCOPY_THRESH,
VIOIF_MACPROP_RXCOPY_THRESH,
NULL
};
static vioif_txbuf_t *
vioif_txbuf_alloc(vioif_t *vif)
{
vioif_txbuf_t *tb;
VERIFY(MUTEX_HELD(&vif->vif_mutex));
if ((tb = list_remove_head(&vif->vif_txbufs)) != NULL) {
vif->vif_ntxbufs_alloc++;
}
return (tb);
}
static void
vioif_txbuf_free(vioif_t *vif, vioif_txbuf_t *tb)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY3U(vif->vif_ntxbufs_alloc, >, 0);
vif->vif_ntxbufs_alloc--;
virtio_chain_clear(tb->tb_chain);
list_insert_head(&vif->vif_txbufs, tb);
}
static vioif_rxbuf_t *
vioif_rxbuf_alloc(vioif_t *vif)
{
vioif_rxbuf_t *rb;
VERIFY(MUTEX_HELD(&vif->vif_mutex));
if ((rb = list_remove_head(&vif->vif_rxbufs)) != NULL) {
vif->vif_nrxbufs_alloc++;
}
return (rb);
}
static void
vioif_rxbuf_free(vioif_t *vif, vioif_rxbuf_t *rb)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY3U(vif->vif_nrxbufs_alloc, >, 0);
vif->vif_nrxbufs_alloc--;
virtio_chain_clear(rb->rb_chain);
list_insert_head(&vif->vif_rxbufs, rb);
}
static void
vioif_rx_free_callback(caddr_t free_arg)
{
vioif_rxbuf_t *rb = (vioif_rxbuf_t *)free_arg;
vioif_t *vif = rb->rb_vioif;
mutex_enter(&vif->vif_mutex);
vioif_rxbuf_free(vif, rb);
VERIFY3U(vif->vif_nrxbufs_onloan, >, 0);
vif->vif_nrxbufs_onloan--;
(void) vioif_add_rx(vif);
mutex_exit(&vif->vif_mutex);
}
static vioif_ctrlbuf_t *
vioif_ctrlbuf_alloc(vioif_t *vif)
{
vioif_ctrlbuf_t *cb;
VERIFY(MUTEX_HELD(&vif->vif_mutex));
if ((cb = list_remove_head(&vif->vif_ctrlbufs)) != NULL) {
vif->vif_nctrlbufs_alloc++;
}
return (cb);
}
static void
vioif_ctrlbuf_free(vioif_t *vif, vioif_ctrlbuf_t *cb)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY3U(vif->vif_nctrlbufs_alloc, >, 0);
vif->vif_nctrlbufs_alloc--;
virtio_chain_clear(cb->cb_chain);
list_insert_head(&vif->vif_ctrlbufs, cb);
}
static void
vioif_free_bufs(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY3U(vif->vif_ntxbufs_alloc, ==, 0);
for (uint_t i = 0; i < vif->vif_txbufs_capacity; i++) {
vioif_txbuf_t *tb = &vif->vif_txbufs_mem[i];
VERIFY(list_link_active(&tb->tb_link));
list_remove(&vif->vif_txbufs, tb);
VERIFY3P(tb->tb_mp, ==, NULL);
if (tb->tb_dma != NULL) {
virtio_dma_free(tb->tb_dma);
tb->tb_dma = NULL;
}
if (tb->tb_chain != NULL) {
virtio_chain_free(tb->tb_chain);
tb->tb_chain = NULL;
}
if (tb->tb_dmaext != NULL) {
for (uint_t j = 0; j < tb->tb_dmaext_capacity; j++) {
if (tb->tb_dmaext[j] != NULL) {
virtio_dma_free(
tb->tb_dmaext[j]);
tb->tb_dmaext[j] = NULL;
}
}
kmem_free(tb->tb_dmaext,
sizeof (virtio_dma_t *) * tb->tb_dmaext_capacity);
tb->tb_dmaext = NULL;
tb->tb_dmaext_capacity = 0;
}
}
VERIFY(list_is_empty(&vif->vif_txbufs));
if (vif->vif_txbufs_mem != NULL) {
kmem_free(vif->vif_txbufs_mem,
sizeof (vioif_txbuf_t) * vif->vif_txbufs_capacity);
vif->vif_txbufs_mem = NULL;
vif->vif_txbufs_capacity = 0;
}
VERIFY3U(vif->vif_nrxbufs_alloc, ==, 0);
for (uint_t i = 0; i < vif->vif_rxbufs_capacity; i++) {
vioif_rxbuf_t *rb = &vif->vif_rxbufs_mem[i];
VERIFY(list_link_active(&rb->rb_link));
list_remove(&vif->vif_rxbufs, rb);
if (rb->rb_dma != NULL) {
virtio_dma_free(rb->rb_dma);
rb->rb_dma = NULL;
}
if (rb->rb_chain != NULL) {
virtio_chain_free(rb->rb_chain);
rb->rb_chain = NULL;
}
}
VERIFY(list_is_empty(&vif->vif_rxbufs));
if (vif->vif_rxbufs_mem != NULL) {
kmem_free(vif->vif_rxbufs_mem,
sizeof (vioif_rxbuf_t) * vif->vif_rxbufs_capacity);
vif->vif_rxbufs_mem = NULL;
vif->vif_rxbufs_capacity = 0;
}
if (vif->vif_has_ctrlq) {
VERIFY3U(vif->vif_nctrlbufs_alloc, ==, 0);
for (uint_t i = 0; i < vif->vif_ctrlbufs_capacity; i++) {
vioif_ctrlbuf_t *cb = &vif->vif_ctrlbufs_mem[i];
VERIFY(list_link_active(&cb->cb_link));
list_remove(&vif->vif_ctrlbufs, cb);
if (cb->cb_dma != NULL) {
virtio_dma_free(cb->cb_dma);
cb->cb_dma = NULL;
}
if (cb->cb_chain != NULL) {
virtio_chain_free(cb->cb_chain);
cb->cb_chain = NULL;
}
}
VERIFY(list_is_empty(&vif->vif_ctrlbufs));
if (vif->vif_ctrlbufs_mem != NULL) {
kmem_free(vif->vif_ctrlbufs_mem,
sizeof (vioif_ctrlbuf_t) *
vif->vif_ctrlbufs_capacity);
vif->vif_ctrlbufs_mem = NULL;
vif->vif_ctrlbufs_capacity = 0;
}
}
}
static int
vioif_alloc_bufs(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
vif->vif_txbufs_capacity = MIN(VIRTIO_NET_TX_BUFS,
virtio_queue_size(vif->vif_tx_vq));
vif->vif_txbufs_mem = kmem_zalloc(
sizeof (vioif_txbuf_t) * vif->vif_txbufs_capacity, KM_SLEEP);
list_create(&vif->vif_txbufs, sizeof (vioif_txbuf_t),
offsetof(vioif_txbuf_t, tb_link));
vif->vif_rxbufs_capacity = MIN(VIRTIO_NET_RX_BUFS,
virtio_queue_size(vif->vif_rx_vq));
vif->vif_rxbufs_mem = kmem_zalloc(
sizeof (vioif_rxbuf_t) * vif->vif_rxbufs_capacity, KM_SLEEP);
list_create(&vif->vif_rxbufs, sizeof (vioif_rxbuf_t),
offsetof(vioif_rxbuf_t, rb_link));
if (vif->vif_has_ctrlq) {
vif->vif_ctrlbufs_capacity = MIN(VIRTIO_NET_CTRL_BUFS,
virtio_queue_size(vif->vif_ctrl_vq));
vif->vif_ctrlbufs_mem = kmem_zalloc(
sizeof (vioif_ctrlbuf_t) * vif->vif_ctrlbufs_capacity,
KM_SLEEP);
}
list_create(&vif->vif_ctrlbufs, sizeof (vioif_ctrlbuf_t),
offsetof(vioif_ctrlbuf_t, cb_link));
vif->vif_nrxbufs_onloan_max = vif->vif_rxbufs_capacity / 2;
for (uint_t i = 0; i < vif->vif_txbufs_capacity; i++) {
list_insert_tail(&vif->vif_txbufs, &vif->vif_txbufs_mem[i]);
}
for (uint_t i = 0; i < vif->vif_rxbufs_capacity; i++) {
list_insert_tail(&vif->vif_rxbufs, &vif->vif_rxbufs_mem[i]);
}
for (uint_t i = 0; i < vif->vif_ctrlbufs_capacity; i++) {
list_insert_tail(&vif->vif_ctrlbufs, &vif->vif_ctrlbufs_mem[i]);
}
ddi_dma_attr_t attr = vioif_dma_attr_bufs;
attr.dma_attr_sgllen = 1;
for (vioif_txbuf_t *tb = list_head(&vif->vif_txbufs); tb != NULL;
tb = list_next(&vif->vif_txbufs, tb)) {
if ((tb->tb_dma = virtio_dma_alloc(vif->vif_virtio,
VIOIF_TX_INLINE_SIZE, &attr,
DDI_DMA_STREAMING | DDI_DMA_WRITE, KM_SLEEP)) == NULL) {
goto fail;
}
VERIFY3U(virtio_dma_ncookies(tb->tb_dma), ==, 1);
if ((tb->tb_chain = virtio_chain_alloc(vif->vif_tx_vq,
KM_SLEEP)) == NULL) {
goto fail;
}
virtio_chain_data_set(tb->tb_chain, tb);
tb->tb_dmaext_capacity = VIOIF_MAX_SEGS - 1;
tb->tb_dmaext = kmem_zalloc(
sizeof (virtio_dma_t *) * tb->tb_dmaext_capacity,
KM_SLEEP);
}
for (vioif_ctrlbuf_t *cb = list_head(&vif->vif_ctrlbufs); cb != NULL;
cb = list_next(&vif->vif_ctrlbufs, cb)) {
if ((cb->cb_dma = virtio_dma_alloc(vif->vif_virtio,
VIOIF_CTRL_SIZE, &attr,
DDI_DMA_STREAMING | DDI_DMA_RDWR, KM_SLEEP)) == NULL) {
goto fail;
}
VERIFY3U(virtio_dma_ncookies(cb->cb_dma), ==, 1);
if ((cb->cb_chain = virtio_chain_alloc(vif->vif_ctrl_vq,
KM_SLEEP)) == NULL) {
goto fail;
}
virtio_chain_data_set(cb->cb_chain, cb);
}
attr.dma_attr_sgllen = VIOIF_MAX_SEGS - 1;
for (vioif_rxbuf_t *rb = list_head(&vif->vif_rxbufs); rb != NULL;
rb = list_next(&vif->vif_rxbufs, rb)) {
if ((rb->rb_dma = virtio_dma_alloc(vif->vif_virtio,
VIOIF_RX_BUF_SIZE, &attr, DDI_DMA_STREAMING | DDI_DMA_READ,
KM_SLEEP)) == NULL) {
goto fail;
}
if ((rb->rb_chain = virtio_chain_alloc(vif->vif_rx_vq,
KM_SLEEP)) == NULL) {
goto fail;
}
virtio_chain_data_set(rb->rb_chain, rb);
VERIFY3U(virtio_dma_cookie_size(rb->rb_dma, 0), >=,
VIOIF_HEADER_SKIP + 1);
VERIFY3U((uintptr_t)virtio_dma_va(rb->rb_dma,
VIOIF_HEADER_SKIP) % 4, ==, 2);
rb->rb_vioif = vif;
rb->rb_frtn.free_func = vioif_rx_free_callback;
rb->rb_frtn.free_arg = (caddr_t)rb;
}
return (0);
fail:
vioif_free_bufs(vif);
return (ENOMEM);
}
static int
vioif_ctrlq_req(vioif_t *vif, uint8_t class, uint8_t cmd, void *data,
size_t datalen)
{
vioif_ctrlbuf_t *cb = NULL;
virtio_chain_t *vic = NULL;
uint8_t *p = NULL;
uint64_t pa = 0;
uint8_t *ackp = NULL;
struct virtio_net_ctrlq_hdr hdr = {
.vnch_class = class,
.vnch_command = cmd,
};
const size_t hdrlen = sizeof (hdr);
const size_t acklen = 1;
size_t totlen = hdrlen + datalen + acklen;
int r = DDI_SUCCESS;
VERIFY(vif->vif_has_ctrlq);
mutex_enter(&vif->vif_mutex);
cb = vioif_ctrlbuf_alloc(vif);
if (cb == NULL) {
vif->vif_noctrlbuf++;
mutex_exit(&vif->vif_mutex);
r = DDI_FAILURE;
goto done;
}
mutex_exit(&vif->vif_mutex);
if (totlen > virtio_dma_size(cb->cb_dma)) {
vif->vif_ctrlbuf_toosmall++;
r = DDI_FAILURE;
goto done;
}
p = virtio_dma_va(cb->cb_dma, 0);
bzero(p, virtio_dma_size(cb->cb_dma));
bcopy(&hdr, p, sizeof (hdr));
pa = virtio_dma_cookie_pa(cb->cb_dma, 0);
if ((r = virtio_chain_append(cb->cb_chain,
pa, hdrlen, VIRTIO_DIR_DEVICE_READS)) != DDI_SUCCESS) {
goto done;
}
p = virtio_dma_va(cb->cb_dma, hdrlen);
bcopy(data, p, datalen);
if ((r = virtio_chain_append(cb->cb_chain,
pa + hdrlen, datalen, VIRTIO_DIR_DEVICE_READS)) != DDI_SUCCESS) {
goto done;
}
ackp = virtio_dma_va(cb->cb_dma, hdrlen + datalen);
if ((r = virtio_chain_append(cb->cb_chain,
pa + hdrlen + datalen, acklen,
VIRTIO_DIR_DEVICE_WRITES)) != DDI_SUCCESS) {
goto done;
}
virtio_dma_sync(cb->cb_dma, DDI_DMA_SYNC_FORDEV);
virtio_chain_submit(cb->cb_chain, B_TRUE);
mutex_enter(&vif->vif_mutex);
while ((vic = virtio_queue_poll(vif->vif_ctrl_vq)) == NULL) {
mutex_exit(&vif->vif_mutex);
delay(drv_usectohz(1000));
mutex_enter(&vif->vif_mutex);
}
virtio_dma_sync(cb->cb_dma, DDI_DMA_SYNC_FORCPU);
VERIFY3P(virtio_chain_data(vic), ==, cb);
mutex_exit(&vif->vif_mutex);
if (*ackp != VIRTIO_NET_CQ_OK) {
r = DDI_FAILURE;
}
done:
mutex_enter(&vif->vif_mutex);
vioif_ctrlbuf_free(vif, cb);
mutex_exit(&vif->vif_mutex);
return (r);
}
static int
vioif_m_multicst(void *arg, boolean_t add, const uint8_t *mcst_addr)
{
return (0);
}
static int
vioif_m_setpromisc(void *arg, boolean_t on)
{
vioif_t *vif = arg;
uint8_t val = on ? 1 : 0;
if (!vif->vif_has_ctrlq_rx) {
if (vioif_fake_promisc_success)
return (0);
return (ENOTSUP);
}
return (vioif_ctrlq_req(vif, VIRTIO_NET_CTRL_RX,
VIRTIO_NET_CTRL_RX_PROMISC, &val, sizeof (val)));
}
static int
vioif_m_unicst(void *arg, const uint8_t *mac)
{
return (ENOTSUP);
}
static uint_t
vioif_add_rx(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
if (vif->vif_runstate != VIOIF_RUNSTATE_RUNNING) {
return (0);
}
uint_t num_added = 0;
vioif_rxbuf_t *rb;
while ((rb = vioif_rxbuf_alloc(vif)) != NULL) {
if (virtio_chain_append(rb->rb_chain,
virtio_dma_cookie_pa(rb->rb_dma, 0), vif->vif_rxbuf_hdrlen,
VIRTIO_DIR_DEVICE_WRITES) != DDI_SUCCESS) {
goto fail;
}
for (uint_t n = 0; n < virtio_dma_ncookies(rb->rb_dma); n++) {
uint64_t pa = virtio_dma_cookie_pa(rb->rb_dma, n);
size_t sz = virtio_dma_cookie_size(rb->rb_dma, n);
if (n == 0) {
pa += VIOIF_HEADER_SKIP;
VERIFY3U(sz, >, VIOIF_HEADER_SKIP);
sz -= VIOIF_HEADER_SKIP;
}
if (virtio_chain_append(rb->rb_chain, pa, sz,
VIRTIO_DIR_DEVICE_WRITES) != DDI_SUCCESS) {
goto fail;
}
}
virtio_chain_submit(rb->rb_chain, B_FALSE);
num_added++;
continue;
fail:
vioif_rxbuf_free(vif, rb);
vif->vif_norecvbuf++;
break;
}
if (num_added > 0) {
virtio_queue_flush(vif->vif_rx_vq);
}
return (num_added);
}
static uint_t
vioif_process_rx(vioif_t *vif)
{
virtio_chain_t *vic;
mblk_t *mphead = NULL, *lastmp = NULL, *mp;
uint_t num_processed = 0;
VERIFY(MUTEX_HELD(&vif->vif_mutex));
while ((vic = virtio_queue_poll(vif->vif_rx_vq)) != NULL) {
size_t len = virtio_chain_received_length(vic);
vioif_rxbuf_t *rb = virtio_chain_data(vic);
virtio_dma_sync(rb->rb_dma, DDI_DMA_SYNC_FORCPU);
if (vif->vif_runstate != VIOIF_RUNSTATE_RUNNING) {
vioif_rxbuf_free(vif, rb);
continue;
}
if (len < vif->vif_rxbuf_hdrlen) {
vif->vif_rxfail_chain_undersize++;
vif->vif_ierrors++;
vioif_rxbuf_free(vif, rb);
continue;
}
len -= vif->vif_rxbuf_hdrlen;
if (len < vif->vif_rxcopy_thresh ||
vif->vif_nrxbufs_onloan >= vif->vif_nrxbufs_onloan_max) {
mutex_exit(&vif->vif_mutex);
if ((mp = allocb(len, 0)) == NULL) {
mutex_enter(&vif->vif_mutex);
vif->vif_norecvbuf++;
vif->vif_ierrors++;
vioif_rxbuf_free(vif, rb);
continue;
}
bcopy(virtio_dma_va(rb->rb_dma, VIOIF_HEADER_SKIP),
mp->b_rptr, len);
mp->b_wptr = mp->b_rptr + len;
mutex_enter(&vif->vif_mutex);
vioif_rxbuf_free(vif, rb);
} else {
mutex_exit(&vif->vif_mutex);
if ((mp = desballoc(virtio_dma_va(rb->rb_dma,
VIOIF_HEADER_SKIP), len, 0,
&rb->rb_frtn)) == NULL) {
mutex_enter(&vif->vif_mutex);
vif->vif_norecvbuf++;
vif->vif_ierrors++;
vioif_rxbuf_free(vif, rb);
continue;
}
mp->b_wptr = mp->b_rptr + len;
mutex_enter(&vif->vif_mutex);
vif->vif_nrxbufs_onloan++;
}
if (mp->b_rptr[0] & 0x1) {
if (bcmp(mp->b_rptr, vioif_broadcast, ETHERADDRL) != 0)
vif->vif_multircv++;
else
vif->vif_brdcstrcv++;
}
vif->vif_rbytes += len;
vif->vif_ipackets++;
if (lastmp == NULL) {
mphead = mp;
} else {
lastmp->b_next = mp;
}
lastmp = mp;
num_processed++;
}
if (mphead != NULL) {
if (vif->vif_runstate == VIOIF_RUNSTATE_RUNNING) {
mutex_exit(&vif->vif_mutex);
mac_rx(vif->vif_mac_handle, NULL, mphead);
mutex_enter(&vif->vif_mutex);
} else {
freemsgchain(mphead);
}
}
return (num_processed);
}
static uint_t
vioif_reclaim_used_tx(vioif_t *vif)
{
virtio_chain_t *vic;
uint_t num_reclaimed = 0;
VERIFY(MUTEX_NOT_HELD(&vif->vif_mutex));
while ((vic = virtio_queue_poll(vif->vif_tx_vq)) != NULL) {
vioif_txbuf_t *tb = virtio_chain_data(vic);
if (tb->tb_mp != NULL) {
for (uint_t i = 0; i < tb->tb_dmaext_capacity; i++) {
if (tb->tb_dmaext[i] == NULL) {
continue;
}
virtio_dma_unbind(tb->tb_dmaext[i]);
}
freemsg(tb->tb_mp);
tb->tb_mp = NULL;
}
mutex_enter(&vif->vif_mutex);
vioif_txbuf_free(vif, tb);
mutex_exit(&vif->vif_mutex);
num_reclaimed++;
}
if (num_reclaimed > 0) {
boolean_t do_update = B_FALSE;
mutex_enter(&vif->vif_mutex);
vif->vif_stat_tx_reclaim += num_reclaimed;
if (vif->vif_tx_corked) {
vif->vif_tx_corked = B_FALSE;
virtio_queue_no_interrupt(vif->vif_tx_vq, B_TRUE);
do_update = B_TRUE;
}
mutex_exit(&vif->vif_mutex);
if (do_update) {
mac_tx_update(vif->vif_mac_handle);
}
}
return (num_reclaimed);
}
static void
vioif_reclaim_periodic(void *arg)
{
vioif_t *vif = arg;
uint_t num_reclaimed;
num_reclaimed = vioif_reclaim_used_tx(vif);
mutex_enter(&vif->vif_mutex);
vif->vif_tx_reclaim_tid = 0;
if (num_reclaimed != 0 || virtio_queue_nactive(vif->vif_tx_vq) != 0) {
if (!vif->vif_tx_drain) {
vioif_reclaim_restart(vif);
}
}
mutex_exit(&vif->vif_mutex);
}
static void
vioif_reclaim_restart(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY(!vif->vif_tx_drain);
if (vif->vif_tx_reclaim_tid == 0) {
vif->vif_tx_reclaim_tid = timeout(vioif_reclaim_periodic, vif,
MSEC_TO_TICK_ROUNDUP(vioif_reclaim_ms));
}
}
static void
vioif_tx_drain(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
VERIFY3S(vif->vif_runstate, ==, VIOIF_RUNSTATE_STOPPING);
vif->vif_tx_drain = B_TRUE;
if (vif->vif_tx_reclaim_tid != 0) {
timeout_id_t tid = vif->vif_tx_reclaim_tid;
vif->vif_tx_reclaim_tid = 0;
mutex_exit(&vif->vif_mutex);
(void) untimeout(tid);
mutex_enter(&vif->vif_mutex);
}
virtio_queue_no_interrupt(vif->vif_tx_vq, B_TRUE);
while (vif->vif_ntxbufs_alloc > 0) {
mutex_exit(&vif->vif_mutex);
(void) vioif_reclaim_used_tx(vif);
delay(5);
mutex_enter(&vif->vif_mutex);
}
VERIFY(!vif->vif_tx_corked);
VERIFY3U(vif->vif_tx_reclaim_tid, ==, 0);
VERIFY3U(virtio_queue_nactive(vif->vif_tx_vq), ==, 0);
}
static int
vioif_tx_inline(vioif_t *vif, vioif_txbuf_t *tb, mblk_t *mp, size_t msg_size)
{
VERIFY(MUTEX_NOT_HELD(&vif->vif_mutex));
VERIFY3U(msg_size, <=, virtio_dma_size(tb->tb_dma) - VIOIF_HEADER_SKIP);
mcopymsg(mp, virtio_dma_va(tb->tb_dma, VIOIF_HEADER_SKIP));
if (virtio_chain_append(tb->tb_chain,
virtio_dma_cookie_pa(tb->tb_dma, 0) + VIOIF_HEADER_SKIP,
msg_size, VIRTIO_DIR_DEVICE_READS) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
vioif_tx_external(vioif_t *vif, vioif_txbuf_t *tb, mblk_t *mp, size_t msg_size)
{
VERIFY(MUTEX_NOT_HELD(&vif->vif_mutex));
mblk_t *nmp = mp;
tb->tb_ndmaext = 0;
while (nmp != NULL) {
size_t len;
if ((len = MBLKL(nmp)) == 0) {
nmp = nmp->b_cont;
continue;
}
if (tb->tb_ndmaext >= tb->tb_dmaext_capacity) {
mutex_enter(&vif->vif_mutex);
vif->vif_txfail_indirect_limit++;
vif->vif_notxbuf++;
mutex_exit(&vif->vif_mutex);
goto fail;
}
if (tb->tb_dmaext[tb->tb_ndmaext] == NULL) {
if ((tb->tb_dmaext[tb->tb_ndmaext] =
virtio_dma_alloc_nomem(vif->vif_virtio,
&vioif_dma_attr_external, KM_SLEEP)) == NULL) {
mutex_enter(&vif->vif_mutex);
vif->vif_notxbuf++;
mutex_exit(&vif->vif_mutex);
goto fail;
}
}
virtio_dma_t *extdma = tb->tb_dmaext[tb->tb_ndmaext++];
if (virtio_dma_bind(extdma, nmp->b_rptr, len,
DDI_DMA_WRITE | DDI_DMA_STREAMING, KM_SLEEP) !=
DDI_SUCCESS) {
mutex_enter(&vif->vif_mutex);
vif->vif_txfail_dma_bind++;
mutex_exit(&vif->vif_mutex);
goto fail;
}
for (uint_t n = 0; n < virtio_dma_ncookies(extdma); n++) {
uint64_t pa = virtio_dma_cookie_pa(extdma, n);
size_t sz = virtio_dma_cookie_size(extdma, n);
if (virtio_chain_append(tb->tb_chain, pa, sz,
VIRTIO_DIR_DEVICE_READS) != DDI_SUCCESS) {
mutex_enter(&vif->vif_mutex);
vif->vif_txfail_indirect_limit++;
vif->vif_notxbuf++;
mutex_exit(&vif->vif_mutex);
goto fail;
}
}
nmp = nmp->b_cont;
}
tb->tb_mp = mp;
return (DDI_SUCCESS);
fail:
for (uint_t n = 0; n < tb->tb_ndmaext; n++) {
if (tb->tb_dmaext[n] != NULL) {
virtio_dma_unbind(tb->tb_dmaext[n]);
}
}
tb->tb_ndmaext = 0;
freemsg(mp);
return (DDI_FAILURE);
}
static boolean_t
vioif_send(vioif_t *vif, mblk_t *mp)
{
VERIFY(MUTEX_NOT_HELD(&vif->vif_mutex));
vioif_txbuf_t *tb = NULL;
struct virtio_net_hdr *vnh = NULL;
size_t msg_size = 0;
uint32_t csum_start;
uint32_t csum_stuff;
uint32_t csum_flags;
uint32_t lso_flags;
uint32_t lso_mss;
mblk_t *nmp;
int ret;
boolean_t lso_required = B_FALSE;
struct ether_header *ether = (void *)mp->b_rptr;
for (nmp = mp; nmp; nmp = nmp->b_cont)
msg_size += MBLKL(nmp);
if (vif->vif_tx_tso4 || vif->vif_tx_tso6) {
mac_lso_get(mp, &lso_mss, &lso_flags);
lso_required = (lso_flags & HW_LSO) != 0;
}
mutex_enter(&vif->vif_mutex);
if ((tb = vioif_txbuf_alloc(vif)) == NULL) {
vif->vif_notxbuf++;
goto fail;
}
mutex_exit(&vif->vif_mutex);
vnh = virtio_dma_va(tb->tb_dma, 0);
bzero(vnh, VIOIF_HEADER_SKIP);
if (vif->vif_rxbuf_hdrlen >
offsetof(struct virtio_net_hdr, vnh_num_buffers)) {
vnh->vnh_num_buffers = 1;
}
if (virtio_chain_append(tb->tb_chain,
virtio_dma_cookie_pa(tb->tb_dma, 0), vif->vif_rxbuf_hdrlen,
VIRTIO_DIR_DEVICE_READS) != DDI_SUCCESS) {
mutex_enter(&vif->vif_mutex);
vif->vif_notxbuf++;
goto fail;
}
mac_hcksum_get(mp, &csum_start, &csum_stuff, NULL, NULL, &csum_flags);
if (csum_flags & HCK_PARTIALCKSUM) {
int eth_hsize;
ASSERT(vif->vif_tx_csum);
ASSERT(!(csum_flags & HCK_IPV4_HDRCKSUM));
ASSERT(!(csum_flags & HCK_FULLCKSUM));
if (ether->ether_type == htons(ETHERTYPE_VLAN)) {
eth_hsize = sizeof (struct ether_vlan_header);
} else {
eth_hsize = sizeof (struct ether_header);
}
vnh->vnh_flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
vnh->vnh_csum_start = eth_hsize + csum_start;
vnh->vnh_csum_offset = csum_stuff - csum_start;
}
if (lso_required) {
mac_ether_offload_flags_t needed;
mac_ether_offload_info_t meo;
uint32_t cksum;
size_t len;
mblk_t *pullmp = NULL;
tcpha_t *tcpha;
mac_ether_offload_info(mp, &meo);
needed = MEOI_L2INFO_SET | MEOI_L3INFO_SET | MEOI_L4INFO_SET;
if ((meo.meoi_flags & needed) != needed) {
goto fail;
}
if (meo.meoi_l4proto != IPPROTO_TCP) {
goto fail;
}
if (meo.meoi_l3proto == ETHERTYPE_IP && vif->vif_tx_tso4) {
vnh->vnh_gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
} else if (meo.meoi_l3proto == ETHERTYPE_IPV6 &&
vif->vif_tx_tso6) {
vnh->vnh_gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
} else {
goto fail;
}
if (MBLKL(mp) < vnh->vnh_hdr_len) {
pullmp = msgpullup(mp, vnh->vnh_hdr_len);
if (pullmp == NULL)
goto fail;
tcpha = (tcpha_t *)(pullmp->b_rptr + meo.meoi_l2hlen +
meo.meoi_l3hlen);
} else {
tcpha = (tcpha_t *)(mp->b_rptr + meo.meoi_l2hlen +
meo.meoi_l3hlen);
}
len = meo.meoi_len - meo.meoi_l2hlen - meo.meoi_l3hlen;
cksum = ntohs(tcpha->tha_sum) + len;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum = (cksum >> 16) + (cksum & 0xffff);
tcpha->tha_sum = htons(cksum);
if (tcpha->tha_flags & TH_CWR) {
vnh->vnh_gso_type |= VIRTIO_NET_HDR_GSO_ECN;
}
vnh->vnh_gso_size = (uint16_t)lso_mss;
vnh->vnh_hdr_len = meo.meoi_l2hlen + meo.meoi_l3hlen +
meo.meoi_l4hlen;
freemsg(pullmp);
}
if ((ether->ether_dhost.ether_addr_octet[0] & 0x01) != 0) {
mutex_enter(&vif->vif_mutex);
if (ether_cmp(ðer->ether_dhost, vioif_broadcast) == 0) {
vif->vif_brdcstxmt++;
} else {
vif->vif_multixmt++;
}
mutex_exit(&vif->vif_mutex);
}
if (msg_size < vif->vif_txcopy_thresh) {
ret = vioif_tx_inline(vif, tb, mp, msg_size);
} else {
ret = vioif_tx_external(vif, tb, mp, msg_size);
}
mp = NULL;
mutex_enter(&vif->vif_mutex);
if (ret != DDI_SUCCESS) {
goto fail;
}
vif->vif_opackets++;
vif->vif_obytes += msg_size;
mutex_exit(&vif->vif_mutex);
virtio_dma_sync(tb->tb_dma, DDI_DMA_SYNC_FORDEV);
virtio_chain_submit(tb->tb_chain, B_TRUE);
return (B_TRUE);
fail:
vif->vif_oerrors++;
if (tb != NULL) {
vioif_txbuf_free(vif, tb);
}
mutex_exit(&vif->vif_mutex);
return (mp == NULL);
}
static mblk_t *
vioif_m_tx(void *arg, mblk_t *mp)
{
vioif_t *vif = arg;
mblk_t *nmp;
if (virtio_queue_nactive(vif->vif_tx_vq) != 0) {
(void) vioif_reclaim_used_tx(vif);
}
while (mp != NULL) {
nmp = mp->b_next;
mp->b_next = NULL;
if (!vioif_send(vif, mp)) {
mp->b_next = nmp;
if (vioif_reclaim_used_tx(vif) != 0) {
continue;
}
mutex_enter(&vif->vif_mutex);
vif->vif_tx_corked = B_TRUE;
virtio_queue_no_interrupt(vif->vif_tx_vq, B_FALSE);
vioif_reclaim_restart(vif);
mutex_exit(&vif->vif_mutex);
return (mp);
}
mp = nmp;
}
mutex_enter(&vif->vif_mutex);
vioif_reclaim_restart(vif);
mutex_exit(&vif->vif_mutex);
return (NULL);
}
static int
vioif_m_start(void *arg)
{
vioif_t *vif = arg;
mutex_enter(&vif->vif_mutex);
VERIFY3S(vif->vif_runstate, ==, VIOIF_RUNSTATE_STOPPED);
vif->vif_runstate = VIOIF_RUNSTATE_RUNNING;
virtio_queue_no_interrupt(vif->vif_rx_vq, B_FALSE);
vif->vif_tx_drain = B_FALSE;
(void) vioif_add_rx(vif);
vioif_get_data(vif);
mutex_exit(&vif->vif_mutex);
return (DDI_SUCCESS);
}
static void
vioif_m_stop(void *arg)
{
vioif_t *vif = arg;
mutex_enter(&vif->vif_mutex);
VERIFY3S(vif->vif_runstate, ==, VIOIF_RUNSTATE_RUNNING);
vif->vif_runstate = VIOIF_RUNSTATE_STOPPING;
vioif_tx_drain(vif);
virtio_queue_no_interrupt(vif->vif_rx_vq, B_TRUE);
vif->vif_runstate = VIOIF_RUNSTATE_STOPPED;
mutex_exit(&vif->vif_mutex);
}
static link_duplex_t
vioif_spec_to_duplex(uint8_t duplex)
{
switch (duplex) {
case VIRTIO_NET_CONFIG_DUPLEX_HALF:
return (LINK_DUPLEX_HALF);
case VIRTIO_NET_CONFIG_DUPLEX_FULL:
return (LINK_DUPLEX_FULL);
case VIRTIO_NET_CONFIG_DUPLEX_UNKNOWN:
default:
return (LINK_DUPLEX_UNKNOWN);
}
}
static link_state_t
vioif_spec_to_state(uint16_t status)
{
return ((status & VIRTIO_NET_CONFIG_STATUS_LINK_UP) ?
LINK_STATE_UP : LINK_STATE_DOWN);
}
static int
vioif_m_stat(void *arg, uint_t stat, uint64_t *val)
{
vioif_t *vif = arg;
switch (stat) {
case MAC_STAT_IERRORS:
*val = vif->vif_ierrors;
break;
case MAC_STAT_OERRORS:
*val = vif->vif_oerrors;
break;
case MAC_STAT_MULTIRCV:
*val = vif->vif_multircv;
break;
case MAC_STAT_BRDCSTRCV:
*val = vif->vif_brdcstrcv;
break;
case MAC_STAT_MULTIXMT:
*val = vif->vif_multixmt;
break;
case MAC_STAT_BRDCSTXMT:
*val = vif->vif_brdcstxmt;
break;
case MAC_STAT_IPACKETS:
*val = vif->vif_ipackets;
break;
case MAC_STAT_RBYTES:
*val = vif->vif_rbytes;
break;
case MAC_STAT_OPACKETS:
*val = vif->vif_opackets;
break;
case MAC_STAT_OBYTES:
*val = vif->vif_obytes;
break;
case MAC_STAT_NORCVBUF:
*val = vif->vif_norecvbuf;
break;
case MAC_STAT_NOXMTBUF:
*val = vif->vif_notxbuf;
break;
case MAC_STAT_IFSPEED:
if (vif->vif_speed == VIRTIO_NET_CONFIG_SPEED_UNKNOWN)
*val = 1000000000ULL;
else
*val = vif->vif_speed * 1000000ULL;
break;
case ETHER_STAT_LINK_DUPLEX:
*val = vioif_spec_to_duplex(vif->vif_duplex);
break;
default:
return (ENOTSUP);
}
return (DDI_SUCCESS);
}
static int
vioif_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_valsize, const void *pr_val)
{
vioif_t *vif = arg;
switch (pr_num) {
case MAC_PROP_MTU: {
int r;
uint32_t mtu;
if (pr_valsize < sizeof (mtu)) {
return (EOVERFLOW);
}
bcopy(pr_val, &mtu, sizeof (mtu));
if (mtu < ETHERMIN || mtu > vif->vif_mtu_max) {
return (EINVAL);
}
mutex_enter(&vif->vif_mutex);
if ((r = mac_maxsdu_update(vif->vif_mac_handle, mtu)) == 0) {
vif->vif_mtu = mtu;
}
mutex_exit(&vif->vif_mutex);
return (r);
}
case MAC_PROP_PRIVATE: {
long max, result;
uint_t *resp;
char *endptr;
if (strcmp(pr_name, VIOIF_MACPROP_TXCOPY_THRESH) == 0) {
max = VIOIF_MACPROP_TXCOPY_THRESH_MAX;
resp = &vif->vif_txcopy_thresh;
} else if (strcmp(pr_name, VIOIF_MACPROP_RXCOPY_THRESH) == 0) {
max = VIOIF_MACPROP_RXCOPY_THRESH_MAX;
resp = &vif->vif_rxcopy_thresh;
} else {
return (ENOTSUP);
}
if (pr_val == NULL) {
return (EINVAL);
}
if (ddi_strtol(pr_val, &endptr, 10, &result) != 0 ||
*endptr != '\0' || result < 0 || result > max) {
return (EINVAL);
}
mutex_enter(&vif->vif_mutex);
*resp = result;
mutex_exit(&vif->vif_mutex);
return (0);
}
default:
return (ENOTSUP);
}
}
static int
vioif_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_valsize, void *pr_val)
{
vioif_t *vif = arg;
switch (pr_num) {
case MAC_PROP_DUPLEX: {
link_duplex_t duplex;
if (pr_valsize < sizeof (link_duplex_t))
return (EOVERFLOW);
duplex = vioif_spec_to_duplex(vif->vif_duplex);
bcopy(&duplex, pr_val, sizeof (link_duplex_t));
break;
}
case MAC_PROP_SPEED: {
uint64_t speed;
if (pr_valsize < sizeof (uint64_t))
return (EOVERFLOW);
speed = (uint64_t)vif->vif_speed * 1000000ULL;
bcopy(&speed, pr_val, sizeof (uint64_t));
break;
}
case MAC_PROP_STATUS: {
link_state_t state;
if (pr_valsize < sizeof (link_state_t))
return (EOVERFLOW);
state = vioif_spec_to_state(vif->vif_status);
bcopy(&state, pr_val, sizeof (link_state_t));
break;
}
case MAC_PROP_MTU:
if (pr_valsize < sizeof (uint32_t))
return (EOVERFLOW);
bcopy(&vif->vif_mtu, pr_val, sizeof (uint32_t));
break;
case MAC_PROP_PRIVATE: {
uint_t value;
if (strcmp(pr_name, VIOIF_MACPROP_TXCOPY_THRESH) == 0) {
value = vif->vif_txcopy_thresh;
} else if (strcmp(pr_name, VIOIF_MACPROP_RXCOPY_THRESH) == 0) {
value = vif->vif_rxcopy_thresh;
} else {
return (ENOTSUP);
}
if (snprintf(pr_val, pr_valsize, "%u", value) >= pr_valsize) {
return (EOVERFLOW);
}
break;
}
default:
return (ENOTSUP);
}
return (0);
}
static void
vioif_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num,
mac_prop_info_handle_t prh)
{
vioif_t *vif = arg;
char valstr[64];
int value;
switch (pr_num) {
case MAC_PROP_DUPLEX:
case MAC_PROP_SPEED:
case MAC_PROP_STATUS:
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
break;
case MAC_PROP_MTU:
mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
mac_prop_info_set_range_uint32(prh, ETHERMIN, vif->vif_mtu_max);
return;
case MAC_PROP_PRIVATE:
if (strcmp(pr_name, VIOIF_MACPROP_TXCOPY_THRESH) == 0) {
value = VIOIF_MACPROP_TXCOPY_THRESH_DEF;
} else if (strcmp(pr_name, VIOIF_MACPROP_RXCOPY_THRESH) == 0) {
value = VIOIF_MACPROP_RXCOPY_THRESH_DEF;
} else {
return;
}
mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
(void) snprintf(valstr, sizeof (valstr), "%d", value);
mac_prop_info_set_default_str(prh, valstr);
return;
default:
return;
}
}
static boolean_t
vioif_m_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
vioif_t *vif = arg;
switch (cap) {
case MAC_CAPAB_HCKSUM: {
if (!vif->vif_tx_csum) {
return (B_FALSE);
}
*(uint32_t *)cap_data = HCKSUM_INET_PARTIAL;
return (B_TRUE);
}
case MAC_CAPAB_LSO: {
if (!vif->vif_tx_tso4) {
return (B_FALSE);
}
mac_capab_lso_t *lso = cap_data;
lso->lso_flags = LSO_TX_BASIC_TCP_IPV4 | LSO_TX_BASIC_TCP_IPV6;
lso->lso_basic_tcp_ipv4.lso_max = VIOIF_RX_DATA_SIZE;
lso->lso_basic_tcp_ipv6.lso_max = VIOIF_RX_DATA_SIZE;
return (B_TRUE);
}
default:
return (B_FALSE);
}
}
static boolean_t
vioif_has_feature(vioif_t *vif, uint64_t feature)
{
return (virtio_features_present(vif->vif_virtio, feature));
}
static void
vioif_get_mac(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
if (vioif_has_feature(vif, VIRTIO_NET_F_MAC)) {
uint8_t gen = virtio_dev_getgen(vif->vif_virtio);
do {
for (uint_t i = 0; i < ETHERADDRL; i++) {
vif->vif_mac[i] =
virtio_dev_get8(vif->vif_virtio,
VIRTIO_NET_CONFIG_MAC + i);
}
} while (gen != virtio_dev_getgen(vif->vif_virtio));
vif->vif_mac_from_host = 1;
return;
}
(void) random_get_pseudo_bytes(vif->vif_mac, ETHERADDRL);
vif->vif_mac[0] &= ~1;
vif->vif_mac[1] |= 2;
for (uint_t i = 0; i < ETHERADDRL; i++) {
virtio_dev_put8(vif->vif_virtio, VIRTIO_NET_CONFIG_MAC + i,
vif->vif_mac[i]);
}
vif->vif_mac_from_host = 0;
dev_err(vif->vif_dip, CE_NOTE, "!Generated a random MAC address: "
"%02x:%02x:%02x:%02x:%02x:%02x",
(uint_t)vif->vif_mac[0], (uint_t)vif->vif_mac[1],
(uint_t)vif->vif_mac[2], (uint_t)vif->vif_mac[3],
(uint_t)vif->vif_mac[4], (uint_t)vif->vif_mac[5]);
}
static void
vioif_get_data(vioif_t *vif)
{
link_state_t orig_state, new_state;
VERIFY(MUTEX_HELD(&vif->vif_mutex));
orig_state = vioif_spec_to_state(vif->vif_status);
if (vioif_has_feature(vif, VIRTIO_NET_F_STATUS)) {
vif->vif_status = virtio_dev_get16(vif->vif_virtio,
VIRTIO_NET_CONFIG_STATUS);
} else {
vif->vif_status = VIRTIO_NET_CONFIG_STATUS_LINK_UP;
}
new_state = vioif_spec_to_state(vif->vif_status);
if (new_state == LINK_STATE_UP) {
if (vioif_has_feature(vif, VIRTIO_NET_F_SPEED_DUPLEX)) {
vif->vif_speed = virtio_dev_get32(vif->vif_virtio,
VIRTIO_NET_CONFIG_SPEED);
vif->vif_duplex = virtio_dev_get8(vif->vif_virtio,
VIRTIO_NET_CONFIG_DUPLEX);
} else {
vif->vif_speed = VIRTIO_NET_CONFIG_SPEED_UNKNOWN;
vif->vif_duplex = VIRTIO_NET_CONFIG_DUPLEX_FULL;
}
} else {
vif->vif_speed = 0;
vif->vif_duplex = VIRTIO_NET_CONFIG_DUPLEX_UNKNOWN;
}
if (vif->vif_speed > INT32_MAX)
vif->vif_speed = VIRTIO_NET_CONFIG_SPEED_UNKNOWN;
if (orig_state != new_state)
mac_link_update(vif->vif_mac_handle, new_state);
}
static uint_t
vioif_rx_handler(caddr_t arg0, caddr_t arg1)
{
vioif_t *vif = (vioif_t *)arg0;
mutex_enter(&vif->vif_mutex);
(void) vioif_process_rx(vif);
(void) vioif_add_rx(vif);
mutex_exit(&vif->vif_mutex);
return (DDI_INTR_CLAIMED);
}
static uint_t
vioif_tx_handler(caddr_t arg0, caddr_t arg1)
{
vioif_t *vif = (vioif_t *)arg0;
(void) vioif_reclaim_used_tx(vif);
return (DDI_INTR_CLAIMED);
}
static void
vioif_check_features(vioif_t *vif)
{
VERIFY(MUTEX_HELD(&vif->vif_mutex));
vif->vif_tx_csum = 0;
vif->vif_tx_tso4 = 0;
vif->vif_tx_tso6 = 0;
if (vioif_has_feature(vif, VIRTIO_NET_F_CSUM)) {
vif->vif_tx_csum = 1;
boolean_t gso = vioif_has_feature(vif, VIRTIO_NET_F_GSO);
boolean_t tso4 = vioif_has_feature(vif, VIRTIO_NET_F_HOST_TSO4);
boolean_t tso6 = vioif_has_feature(vif, VIRTIO_NET_F_HOST_TSO6);
boolean_t ecn = vioif_has_feature(vif, VIRTIO_NET_F_HOST_ECN);
if (gso) {
vif->vif_tx_tso4 = 1;
vif->vif_tx_tso6 = 1;
}
if (tso4 && ecn) {
vif->vif_tx_tso4 = 1;
}
if (tso6 && ecn) {
vif->vif_tx_tso6 = 1;
}
}
if (vioif_has_feature(vif, VIRTIO_NET_F_CTRL_VQ)) {
vif->vif_has_ctrlq = 1;
if (vioif_has_feature(vif, VIRTIO_NET_F_CTRL_RX))
vif->vif_has_ctrlq_rx = 1;
}
}
static int
vioif_select_interrupt_types(void)
{
id_t id;
smbios_system_t sys;
smbios_info_t info;
if (vioif_allowed_int_types != -1) {
return (vioif_allowed_int_types);
}
if (ksmbios == NULL ||
(id = smbios_info_system(ksmbios, &sys)) == SMB_ERR ||
smbios_info_common(ksmbios, id, &info) == SMB_ERR) {
return (VIRTIO_ANY_INTR_TYPE);
}
if (strcmp(info.smbi_manufacturer, "Google") == 0 &&
strcmp(info.smbi_product, "Google Compute Engine") == 0) {
return (DDI_INTR_TYPE_FIXED);
}
return (VIRTIO_ANY_INTR_TYPE);
}
static uint_t
vioif_cfgchange(caddr_t arg0, caddr_t arg1 __unused)
{
vioif_t *vif = (vioif_t *)arg0;
mutex_enter(&vif->vif_mutex);
vioif_get_data(vif);
mutex_exit(&vif->vif_mutex);
return (DDI_INTR_CLAIMED);
}
static int
vioif_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int ret;
vioif_t *vif;
virtio_t *vio;
mac_register_t *macp = NULL;
uint64_t features;
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if ((vio = virtio_init(dip)) == NULL) {
return (DDI_FAILURE);
}
features = VIRTIO_NET_WANTED_FEATURES;
if (virtio_modern(vio))
features |= VIRTIO_NET_WANTED_FEATURES_MODERN;
if (!virtio_init_features(vio, features, B_TRUE)) {
virtio_fini(vio, B_TRUE);
return (DDI_FAILURE);
}
vif = kmem_zalloc(sizeof (*vif), KM_SLEEP);
vif->vif_dip = dip;
vif->vif_virtio = vio;
vif->vif_runstate = VIOIF_RUNSTATE_STOPPED;
ddi_set_driver_private(dip, vif);
if ((vif->vif_rx_vq = virtio_queue_alloc(vio, VIRTIO_NET_VIRTQ_RX,
"rx", vioif_rx_handler, vif, B_FALSE, VIOIF_MAX_SEGS)) == NULL ||
(vif->vif_tx_vq = virtio_queue_alloc(vio, VIRTIO_NET_VIRTQ_TX,
"tx", vioif_tx_handler, vif, B_FALSE, VIOIF_MAX_SEGS)) == NULL) {
goto fail_virtio;
}
if (vioif_has_feature(vif, VIRTIO_NET_F_CTRL_VQ) &&
(vif->vif_ctrl_vq = virtio_queue_alloc(vio,
VIRTIO_NET_VIRTQ_CONTROL, "ctrlq", NULL, vif,
B_FALSE, VIOIF_MAX_SEGS)) == NULL) {
goto fail_virtio;
}
virtio_register_cfgchange_handler(vio, vioif_cfgchange, vif);
if (virtio_init_complete(vio, vioif_select_interrupt_types()) !=
DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to complete Virtio init");
goto fail_virtio;
}
virtio_queue_no_interrupt(vif->vif_rx_vq, B_TRUE);
virtio_queue_no_interrupt(vif->vif_tx_vq, B_TRUE);
if (vif->vif_ctrl_vq != NULL)
virtio_queue_no_interrupt(vif->vif_ctrl_vq, B_TRUE);
mutex_init(&vif->vif_mutex, NULL, MUTEX_DRIVER, virtio_intr_pri(vio));
mutex_enter(&vif->vif_mutex);
vioif_get_mac(vif);
vif->vif_duplex = VIRTIO_NET_CONFIG_DUPLEX_UNKNOWN;
vif->vif_speed = VIRTIO_NET_CONFIG_SPEED_UNKNOWN;
vif->vif_rxcopy_thresh = VIOIF_MACPROP_RXCOPY_THRESH_DEF;
vif->vif_txcopy_thresh = VIOIF_MACPROP_TXCOPY_THRESH_DEF;
vif->vif_rxbuf_hdrlen = VIRTIO_NET_HDR_LEN(virtio_modern(vio));
if (vioif_has_feature(vif, VIRTIO_NET_F_MTU)) {
vif->vif_mtu_max = virtio_dev_get16(vio, VIRTIO_NET_CONFIG_MTU);
} else {
vif->vif_mtu_max = ETHERMTU;
}
vif->vif_mtu = ETHERMTU;
if (vif->vif_mtu > vif->vif_mtu_max) {
vif->vif_mtu = vif->vif_mtu_max;
}
vioif_check_features(vif);
if (vioif_alloc_bufs(vif) != 0) {
mutex_exit(&vif->vif_mutex);
dev_err(dip, CE_WARN, "failed to allocate memory");
goto fail_virtio;
}
mutex_exit(&vif->vif_mutex);
if (virtio_interrupts_enable(vio) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to enable interrupts");
goto fail_bufs;
}
if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
dev_err(dip, CE_WARN, "failed to allocate a mac_register");
goto fail_bufs;
}
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_driver = vif;
macp->m_dip = dip;
macp->m_src_addr = vif->vif_mac;
macp->m_callbacks = &vioif_mac_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = vif->vif_mtu;
macp->m_margin = VLAN_TAGSZ;
macp->m_priv_props = vioif_priv_props;
if ((ret = mac_register(macp, &vif->vif_mac_handle)) != 0) {
dev_err(dip, CE_WARN, "mac_register() failed (%d)", ret);
goto fail_mac;
}
mac_free(macp);
mutex_enter(&vif->vif_mutex);
vioif_get_data(vif);
mutex_exit(&vif->vif_mutex);
return (DDI_SUCCESS);
fail_mac:
mac_free(macp);
fail_bufs:
mutex_enter(&vif->vif_mutex);
vioif_free_bufs(vif);
mutex_exit(&vif->vif_mutex);
fail_virtio:
(void) virtio_fini(vio, B_TRUE);
kmem_free(vif, sizeof (*vif));
return (DDI_FAILURE);
}
static int
vioif_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int r;
vioif_t *vif;
if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
if ((vif = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
mutex_enter(&vif->vif_mutex);
if (vif->vif_runstate != VIOIF_RUNSTATE_STOPPED) {
dev_err(dip, CE_WARN, "!NIC still running, cannot detach");
mutex_exit(&vif->vif_mutex);
return (DDI_FAILURE);
}
VERIFY3U(vif->vif_ntxbufs_alloc, ==, 0);
if (vif->vif_nrxbufs_onloan > 0) {
dev_err(dip, CE_WARN, "!%u receive buffers still loaned, "
"cannot detach", vif->vif_nrxbufs_onloan);
mutex_exit(&vif->vif_mutex);
return (DDI_FAILURE);
}
if ((r = mac_unregister(vif->vif_mac_handle)) != 0) {
dev_err(dip, CE_WARN, "!MAC unregister failed (%d)", r);
return (DDI_FAILURE);
}
virtio_shutdown(vif->vif_virtio);
for (;;) {
virtio_chain_t *vic;
if ((vic = virtio_queue_evacuate(vif->vif_rx_vq)) == NULL) {
break;
}
vioif_rxbuf_t *rb = virtio_chain_data(vic);
vioif_rxbuf_free(vif, rb);
}
vioif_free_bufs(vif);
(void) virtio_fini(vif->vif_virtio, B_FALSE);
mutex_exit(&vif->vif_mutex);
mutex_destroy(&vif->vif_mutex);
kmem_free(vif, sizeof (*vif));
return (DDI_SUCCESS);
}
static int
vioif_quiesce(dev_info_t *dip)
{
vioif_t *vif;
if ((vif = ddi_get_driver_private(dip)) == NULL)
return (DDI_FAILURE);
return (virtio_quiesce(vif->vif_virtio));
}
int
_init(void)
{
int ret;
mac_init_ops(&vioif_dev_ops, "vioif");
if ((ret = mod_install(&vioif_modlinkage)) != DDI_SUCCESS) {
mac_fini_ops(&vioif_dev_ops);
}
return (ret);
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&vioif_modlinkage)) == DDI_SUCCESS) {
mac_fini_ops(&vioif_dev_ops);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&vioif_modlinkage, modinfop));
}