#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/dlpi.h>
#include <sys/vlan.h>
#include "viona_impl.h"
#define VIONA_NAME "Virtio Network Accelerator"
#define VIONA_CTL_MINOR 0
#define VIONA_MODULE_NAME "viona"
#define VIONA_KSTAT_CLASS "misc"
#define VIONA_KSTAT_NAME "viona_stat"
#define VIONA_S_HOSTCAPS ( \
VIRTIO_NET_F_GUEST_CSUM | \
VIRTIO_NET_F_GUEST_TSO4 | \
VIRTIO_NET_F_MRG_RXBUF | \
VIRTIO_F_RING_NOTIFY_ON_EMPTY | \
VIRTIO_F_RING_INDIRECT_DESC)
#define VIONA_CAP_HCKSUM_INTEREST \
(HCKSUM_INET_PARTIAL | \
HCKSUM_INET_FULL_V4 | \
HCKSUM_INET_FULL_V6)
static void *viona_state;
static dev_info_t *viona_dip;
static id_space_t *viona_minors;
static int viona_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **result);
static int viona_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int viona_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int viona_open(dev_t *devp, int flag, int otype, cred_t *credp);
static int viona_close(dev_t dev, int flag, int otype, cred_t *credp);
static int viona_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
cred_t *credp, int *rval);
static int viona_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
struct pollhead **phpp);
static int viona_ioc_create(viona_soft_state_t *, void *, int, cred_t *);
static int viona_ioc_delete(viona_soft_state_t *, boolean_t);
static int viona_ioc_set_notify_ioport(viona_link_t *, uint16_t);
static int viona_ioc_set_notify_mmio(viona_link_t *, void *, int);
static int viona_ioc_set_promisc(viona_link_t *, viona_promisc_t);
static int viona_ioc_get_params(viona_link_t *, void *, int);
static int viona_ioc_set_params(viona_link_t *, void *, int);
static int viona_ioc_link_setpairs(viona_link_t *, uint16_t);
static int viona_ioc_link_usepairs(viona_link_t *, uint16_t);
static int viona_ioc_ring_init(viona_link_t *, void *, int);
static int viona_ioc_ring_init_modern(viona_link_t *, void *, int);
static int viona_ioc_ring_set_state(viona_link_t *, void *, int);
static int viona_ioc_ring_get_state(viona_link_t *, void *, int);
static int viona_ioc_ring_reset(viona_link_t *, uint_t);
static int viona_ioc_ring_kick(viona_link_t *, uint_t);
static int viona_ioc_ring_pause(viona_link_t *, uint_t);
static int viona_ioc_ring_set_msi(viona_link_t *, void *, int);
static int viona_ioc_ring_intr_clear(viona_link_t *, uint_t);
static int viona_ioc_intr_poll(viona_link_t *, void *, int, int *);
static int viona_ioc_intr_poll_mq(viona_link_t *, void *, int, int *);
static void viona_params_get_defaults(viona_link_params_t *);
static struct cb_ops viona_cb_ops = {
viona_open,
viona_close,
nodev,
nodev,
nodev,
nodev,
nodev,
viona_ioctl,
nodev,
nodev,
nodev,
viona_chpoll,
ddi_prop_op,
0,
D_MP | D_NEW | D_HOTPLUG,
CB_REV,
nodev,
nodev
};
static struct dev_ops viona_ops = {
DEVO_REV,
0,
viona_info,
nulldev,
nulldev,
viona_attach,
viona_detach,
nodev,
&viona_cb_ops,
NULL,
ddi_power,
ddi_quiesce_not_needed
};
static struct modldrv modldrv = {
&mod_driverops,
VIONA_NAME,
&viona_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, &modldrv, NULL
};
int
_init(void)
{
int ret;
ret = ddi_soft_state_init(&viona_state, sizeof (viona_soft_state_t), 0);
if (ret != 0) {
return (ret);
}
viona_minors = id_space_create("viona_minors",
VIONA_CTL_MINOR + 1, UINT16_MAX);
viona_rx_init();
mutex_init(&viona_force_copy_lock, NULL, MUTEX_DRIVER, NULL);
ret = mod_install(&modlinkage);
if (ret != 0) {
ddi_soft_state_fini(&viona_state);
id_space_destroy(viona_minors);
viona_rx_fini();
mutex_destroy(&viona_force_copy_lock);
}
return (ret);
}
int
_fini(void)
{
int ret;
ret = mod_remove(&modlinkage);
if (ret != 0) {
return (ret);
}
ddi_soft_state_fini(&viona_state);
id_space_destroy(viona_minors);
viona_rx_fini();
mutex_destroy(&viona_force_copy_lock);
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
viona_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
int error;
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = (void *)viona_dip;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)0;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
break;
}
return (error);
}
static int
viona_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_create_minor_node(dip, "viona", S_IFCHR, VIONA_CTL_MINOR,
DDI_PSEUDO, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
viona_neti_attach();
viona_dip = dip;
ddi_report_dev(viona_dip);
return (DDI_SUCCESS);
}
static int
viona_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
dev_info_t *old_dip = viona_dip;
if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
VERIFY(old_dip != NULL);
viona_neti_detach();
viona_dip = NULL;
ddi_remove_minor_node(old_dip, NULL);
return (DDI_SUCCESS);
}
static int
viona_open(dev_t *devp, int flag, int otype, cred_t *credp)
{
int minor;
viona_soft_state_t *ss;
if (otype != OTYP_CHR) {
return (EINVAL);
}
#if 0
if (drv_priv(credp) != 0) {
return (EPERM);
}
#endif
if (getminor(*devp) != VIONA_CTL_MINOR) {
return (ENXIO);
}
minor = id_alloc_nosleep(viona_minors);
if (minor == -1) {
return (EBUSY);
}
if (ddi_soft_state_zalloc(viona_state, minor) != DDI_SUCCESS) {
id_free(viona_minors, minor);
return (ENOMEM);
}
ss = ddi_get_soft_state(viona_state, minor);
mutex_init(&ss->ss_lock, NULL, MUTEX_DEFAULT, NULL);
ss->ss_minor = minor;
*devp = makedevice(getmajor(*devp), minor);
return (0);
}
static int
viona_close(dev_t dev, int flag, int otype, cred_t *credp)
{
int minor;
viona_soft_state_t *ss;
if (otype != OTYP_CHR) {
return (EINVAL);
}
minor = getminor(dev);
ss = ddi_get_soft_state(viona_state, minor);
if (ss == NULL) {
return (ENXIO);
}
VERIFY0(viona_ioc_delete(ss, B_TRUE));
VERIFY(!list_link_active(&ss->ss_node));
ddi_soft_state_free(viona_state, minor);
id_free(viona_minors, minor);
return (0);
}
static int
viona_ioctl(dev_t dev, int cmd, intptr_t data, int md, cred_t *cr, int *rv)
{
viona_soft_state_t *ss;
void *dptr = (void *)data;
int err = 0;
uint64_t val;
viona_link_t *link;
ss = ddi_get_soft_state(viona_state, getminor(dev));
if (ss == NULL) {
return (ENXIO);
}
switch (cmd) {
case VNA_IOC_CREATE:
return (viona_ioc_create(ss, dptr, md, cr));
case VNA_IOC_DELETE:
return (viona_ioc_delete(ss, B_FALSE));
case VNA_IOC_VERSION:
*rv = VIONA_CURRENT_INTERFACE_VERSION;
return (0);
case VNA_IOC_DEFAULT_PARAMS:
return (viona_ioc_get_params(NULL, dptr, md));
default:
break;
}
mutex_enter(&ss->ss_lock);
if ((link = ss->ss_link) == NULL || link->l_destroyed ||
vmm_drv_release_reqd(link->l_vm_hold)) {
mutex_exit(&ss->ss_lock);
return (ENXIO);
}
switch (cmd) {
case VNA_IOC_GET_FEATURES:
val = VIONA_S_HOSTCAPS | link->l_features_hw;
if (ddi_copyout(&val, dptr, sizeof (val), md) != 0) {
err = EFAULT;
}
break;
case VNA_IOC_SET_FEATURES:
if (ddi_copyin(dptr, &val, sizeof (val), md) != 0) {
err = EFAULT;
break;
}
link->l_modern = ((val & VIRTIO_F_VERSION_1) != 0);
val &= (VIONA_S_HOSTCAPS | link->l_features_hw);
if ((val & VIRTIO_NET_F_CSUM) == 0)
val &= ~VIRTIO_NET_F_HOST_TSO4;
if ((val & VIRTIO_NET_F_GUEST_CSUM) == 0)
val &= ~VIRTIO_NET_F_GUEST_TSO4;
link->l_features = val;
break;
case VNA_IOC_GET_PAIRS:
*rv = (int)link->l_npairs;
break;
case VNA_IOC_SET_PAIRS:
if (data > UINT16_MAX)
err = EINVAL;
else
err = viona_ioc_link_setpairs(link, (uint16_t)data);
break;
case VNA_IOC_GET_USEPAIRS:
*rv = (int)link->l_usepairs;
break;
case VNA_IOC_SET_USEPAIRS:
if (data > UINT16_MAX)
err = EINVAL;
else
err = viona_ioc_link_usepairs(link, (uint16_t)data);
break;
case VNA_IOC_RING_INIT:
err = viona_ioc_ring_init(link, dptr, md);
break;
case VNA_IOC_RING_INIT_MODERN:
err = viona_ioc_ring_init_modern(link, dptr, md);
break;
case VNA_IOC_RING_RESET:
err = viona_ioc_ring_reset(link, (uint_t)data);
break;
case VNA_IOC_RING_KICK:
err = viona_ioc_ring_kick(link, (uint_t)data);
break;
case VNA_IOC_RING_SET_MSI:
err = viona_ioc_ring_set_msi(link, dptr, md);
break;
case VNA_IOC_RING_INTR_CLR:
err = viona_ioc_ring_intr_clear(link, (uint_t)data);
break;
case VNA_IOC_RING_SET_STATE:
err = viona_ioc_ring_set_state(link, dptr, md);
break;
case VNA_IOC_RING_GET_STATE:
err = viona_ioc_ring_get_state(link, dptr, md);
break;
case VNA_IOC_RING_PAUSE:
err = viona_ioc_ring_pause(link, (uint_t)data);
break;
case VNA_IOC_INTR_POLL:
err = viona_ioc_intr_poll(link, dptr, md, rv);
break;
case VNA_IOC_INTR_POLL_MQ:
err = viona_ioc_intr_poll_mq(link, dptr, md, rv);
break;
case VNA_IOC_SET_NOTIFY_IOP:
if (data < 0 || data > UINT16_MAX) {
err = EINVAL;
break;
}
err = viona_ioc_set_notify_ioport(link, (uint16_t)data);
break;
case VNA_IOC_SET_NOTIFY_MMIO:
err = viona_ioc_set_notify_mmio(link, dptr, md);
break;
case VNA_IOC_SET_PROMISC:
err = viona_ioc_set_promisc(link, (viona_promisc_t)data);
break;
case VNA_IOC_GET_PARAMS:
err = viona_ioc_get_params(link, dptr, md);
break;
case VNA_IOC_SET_PARAMS:
err = viona_ioc_set_params(link, dptr, md);
break;
case VNA_IOC_GET_MTU:
*rv = (int)link->l_mtu;
break;
case VNA_IOC_SET_MTU:
if (data < VIONA_MIN_MTU || data > VIONA_MAX_MTU)
err = EINVAL;
else
link->l_mtu = (uint16_t)data;
break;
default:
err = ENOTTY;
break;
}
mutex_exit(&ss->ss_lock);
return (err);
}
static int
viona_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
struct pollhead **phpp)
{
viona_soft_state_t *ss;
viona_link_t *link;
ss = ddi_get_soft_state(viona_state, getminor(dev));
if (ss == NULL) {
return (ENXIO);
}
mutex_enter(&ss->ss_lock);
if ((link = ss->ss_link) == NULL || link->l_destroyed) {
mutex_exit(&ss->ss_lock);
return (ENXIO);
}
*reventsp = 0;
if ((events & POLLRDBAND) != 0) {
for (uint16_t i = 0; i < VIONA_USABLE_RINGS(link); i++) {
if (link->l_vrings[i].vr_intr_enabled != 0) {
*reventsp |= POLLRDBAND;
break;
}
}
}
if ((*reventsp == 0 && !anyyet) || (events & POLLET)) {
*phpp = &link->l_pollhead;
}
mutex_exit(&ss->ss_lock);
return (0);
}
static void
viona_get_mac_capab(viona_link_t *link)
{
mac_handle_t mh = link->l_mh;
uint32_t cap = 0;
mac_capab_lso_t lso_cap;
link->l_features_hw = 0;
if (mac_capab_get(mh, MAC_CAPAB_HCKSUM, &cap)) {
if ((cap & VIONA_CAP_HCKSUM_INTEREST) != 0) {
link->l_features_hw |= VIRTIO_NET_F_CSUM;
}
link->l_cap_csum = cap;
}
if ((link->l_features_hw & VIRTIO_NET_F_CSUM) &&
mac_capab_get(mh, MAC_CAPAB_LSO, &lso_cap)) {
if ((lso_cap.lso_flags & LSO_TX_BASIC_TCP_IPV4) &&
lso_cap.lso_basic_tcp_ipv4.lso_max >= IP_MAXPACKET)
link->l_features_hw |= VIRTIO_NET_F_HOST_TSO4;
}
}
static int
viona_kstat_update(kstat_t *ksp, int rw)
{
viona_link_t *link = ksp->ks_private;
viona_kstats_t *vk = ksp->ks_data;
mutex_enter(&link->l_stats_lock);
for (uint16_t i = 0; i < VIONA_USABLE_RINGS(link); i++) {
const viona_vring_t *ring = &link->l_vrings[i];
const viona_transfer_stats_t *ring_stats = &ring->vr_stats;
const viona_transfer_stats_t *link_stats;
if (VIONA_RING_ISRX(ring)) {
link_stats = &link->l_stats.vls_rx;
vk->vk_rx_packets.value.ui64 =
link_stats->vts_packets + ring_stats->vts_packets;
vk->vk_rx_bytes.value.ui64 =
link_stats->vts_bytes + ring_stats->vts_bytes;
vk->vk_rx_errors.value.ui64 =
link_stats->vts_errors + ring_stats->vts_errors;
vk->vk_rx_drops.value.ui64 =
link_stats->vts_drops + ring_stats->vts_drops;
} else if (VIONA_RING_ISTX(ring)) {
link_stats = &link->l_stats.vls_tx;
vk->vk_tx_packets.value.ui64 =
link_stats->vts_packets + ring_stats->vts_packets;
vk->vk_tx_bytes.value.ui64 =
link_stats->vts_bytes + ring_stats->vts_bytes;
vk->vk_tx_errors.value.ui64 =
link_stats->vts_errors + ring_stats->vts_errors;
vk->vk_tx_drops.value.ui64 =
link_stats->vts_drops + ring_stats->vts_drops;
}
}
mutex_exit(&link->l_stats_lock);
return (0);
}
static int
viona_kstat_init(viona_soft_state_t *ss, const cred_t *cr)
{
zoneid_t zid = crgetzoneid(cr);
kstat_t *ksp;
ASSERT(MUTEX_HELD(&ss->ss_lock));
ASSERT3P(ss->ss_kstat, ==, NULL);
ksp = kstat_create_zone(VIONA_MODULE_NAME, ss->ss_minor,
VIONA_KSTAT_NAME, VIONA_KSTAT_CLASS, KSTAT_TYPE_NAMED,
sizeof (viona_kstats_t) / sizeof (kstat_named_t), 0, zid);
if (ksp == NULL) {
return (ENOMEM);
}
ss->ss_kstat = ksp;
if (zid != GLOBAL_ZONEID) {
kstat_zone_add(ss->ss_kstat, GLOBAL_ZONEID);
}
viona_kstats_t *vk = ksp->ks_data;
kstat_named_init(&vk->vk_rx_packets, "rx_packets", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_rx_bytes, "rx_bytes", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_rx_errors, "rx_errors", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_rx_drops, "rx_drops", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_tx_packets, "tx_packets", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_tx_bytes, "tx_bytes", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_tx_errors, "tx_errors", KSTAT_DATA_UINT64);
kstat_named_init(&vk->vk_tx_drops, "tx_drops", KSTAT_DATA_UINT64);
ksp->ks_private = ss->ss_link;
ksp->ks_update = viona_kstat_update;
kstat_install(ss->ss_kstat);
return (0);
}
static void
viona_kstat_fini(viona_soft_state_t *ss)
{
ASSERT(MUTEX_HELD(&ss->ss_lock));
if (ss->ss_kstat != NULL) {
kstat_delete(ss->ss_kstat);
ss->ss_kstat = NULL;
}
}
static void
viona_link_qfree(viona_link_t *link)
{
if (link->l_vrings == NULL)
return;
for (uint16_t i = 0; i < VIONA_NRINGS(link); i++) {
ASSERT3U(link->l_vrings[i].vr_state, ==, VRS_RESET);
viona_ring_free(&link->l_vrings[i]);
}
kmem_free(link->l_vrings, sizeof (viona_vring_t) * VIONA_NRINGS(link));
link->l_vrings = NULL;
link->l_npairs = link->l_usepairs = 0;
}
static int
viona_link_qalloc(viona_link_t *link, uint16_t pairs)
{
const uint16_t usepairs = link->l_usepairs;
ASSERT(MUTEX_HELD(&link->l_ss->ss_lock));
if (pairs < VIONA_MIN_QPAIR || pairs > VIONA_MAX_QPAIR ||
pairs < usepairs) {
return (EINVAL);
}
for (uint16_t i = 0; i < VIONA_NRINGS(link); i++) {
if (link->l_vrings[i].vr_state != VRS_RESET)
return (EBUSY);
}
viona_link_qfree(link);
link->l_npairs = pairs;
link->l_usepairs = usepairs;
link->l_vrings = kmem_zalloc(
sizeof (viona_vring_t) * VIONA_NRINGS(link), KM_SLEEP);
for (uint16_t i = 0; i < VIONA_NRINGS(link); i++)
viona_ring_alloc(link, &link->l_vrings[i]);
return (0);
}
static int
viona_ioc_create(viona_soft_state_t *ss, void *dptr, int md, cred_t *cr)
{
vioc_create_t kvc;
viona_link_t *link = NULL;
char cli_name[MAXNAMELEN];
int err = 0;
file_t *fp;
vmm_hold_t *hold = NULL;
viona_neti_t *nip = NULL;
zoneid_t zid;
mac_diag_t mac_diag = MAC_DIAG_NONE;
ASSERT(MUTEX_NOT_HELD(&ss->ss_lock));
if (ddi_copyin(dptr, &kvc, sizeof (kvc), md) != 0) {
return (EFAULT);
}
zid = crgetzoneid(cr);
nip = viona_neti_lookup_by_zid(zid);
if (nip == NULL) {
return (EIO);
}
if (!nip->vni_nethook.vnh_hooked) {
viona_neti_rele(nip);
return (EIO);
}
mutex_enter(&ss->ss_lock);
if (ss->ss_link != NULL) {
mutex_exit(&ss->ss_lock);
viona_neti_rele(nip);
return (EEXIST);
}
if ((fp = getf(kvc.c_vmfd)) == NULL) {
err = EBADF;
goto bail;
}
err = vmm_drv_hold(fp, cr, &hold);
releasef(kvc.c_vmfd);
if (err != 0) {
goto bail;
}
link = kmem_zalloc(sizeof (viona_link_t), KM_SLEEP);
link->l_ss = ss;
link->l_linkid = kvc.c_linkid;
link->l_vm_hold = hold;
link->l_mtu = VIONA_DEFAULT_MTU;
link->l_notify_mmaddr = NOTIFY_MMADDR_UNSET;
err = mac_open_by_linkid(link->l_linkid, &link->l_mh);
if (err != 0) {
goto bail;
}
viona_get_mac_capab(link);
viona_params_get_defaults(&link->l_params);
(void) snprintf(cli_name, sizeof (cli_name), "%s-%d", VIONA_MODULE_NAME,
link->l_linkid);
err = mac_client_open(link->l_mh, &link->l_mch, cli_name, 0);
if (err != 0) {
goto bail;
}
err = mac_unicast_add(link->l_mch, NULL, MAC_UNICAST_PRIMARY,
&link->l_muh, VLAN_ID_NONE, &mac_diag);
if (err != 0) {
goto bail;
}
if (viona_link_qalloc(link, 1) != 0)
goto bail;
link->l_usepairs = 1;
link->l_promisc = VIONA_PROMISC_MULTI;
if ((err = viona_rx_set(link, link->l_promisc)) != 0) {
goto bail;
}
link->l_neti = nip;
ss->ss_link = link;
if ((err = viona_kstat_init(ss, cr)) != 0) {
goto bail;
}
mutex_exit(&ss->ss_lock);
mutex_enter(&nip->vni_lock);
list_insert_tail(&nip->vni_dev_list, ss);
mutex_exit(&nip->vni_lock);
return (0);
bail:
if (link != NULL) {
viona_rx_clear(link);
if (link->l_mch != NULL) {
if (link->l_muh != NULL) {
VERIFY0(mac_unicast_remove(link->l_mch,
link->l_muh));
link->l_muh = NULL;
}
mac_client_close(link->l_mch, 0);
}
if (link->l_mh != NULL) {
mac_close(link->l_mh);
}
viona_link_qfree(link);
kmem_free(link, sizeof (viona_link_t));
ss->ss_link = NULL;
}
if (hold != NULL) {
vmm_drv_rele(hold);
}
viona_neti_rele(nip);
mutex_exit(&ss->ss_lock);
return (err);
}
static int
viona_ioc_delete(viona_soft_state_t *ss, boolean_t on_close)
{
viona_link_t *link;
viona_neti_t *nip = NULL;
mutex_enter(&ss->ss_lock);
if ((link = ss->ss_link) == NULL) {
mutex_exit(&ss->ss_lock);
return (0);
}
if (link->l_destroyed) {
VERIFY(!on_close);
mutex_exit(&ss->ss_lock);
return (EAGAIN);
}
link->l_destroyed = B_TRUE;
VERIFY0(viona_ioc_set_notify_ioport(link, 0));
VERIFY0(viona_ioc_set_notify_mmio(link, NULL, 0));
mutex_exit(&ss->ss_lock);
for (uint16_t i = 0; i < VIONA_NRINGS(link); i++)
VERIFY0(viona_ring_reset(&link->l_vrings[i], B_FALSE));
mutex_enter(&ss->ss_lock);
viona_kstat_fini(ss);
if (link->l_mch != NULL) {
viona_rx_clear(link);
if (link->l_muh != NULL) {
VERIFY0(mac_unicast_remove(link->l_mch, link->l_muh));
link->l_muh = NULL;
}
mac_client_close(link->l_mch, 0);
}
if (link->l_mh != NULL) {
mac_close(link->l_mh);
}
if (link->l_vm_hold != NULL) {
vmm_drv_rele(link->l_vm_hold);
link->l_vm_hold = NULL;
}
nip = link->l_neti;
link->l_neti = NULL;
viona_link_qfree(link);
pollhead_clean(&link->l_pollhead);
ss->ss_link = NULL;
mutex_exit(&ss->ss_lock);
mutex_enter(&nip->vni_lock);
list_remove(&nip->vni_dev_list, ss);
mutex_exit(&nip->vni_lock);
viona_neti_rele(nip);
kmem_free(link, sizeof (viona_link_t));
return (0);
}
static int
viona_ioc_ring_init(viona_link_t *link, void *udata, int md)
{
vioc_ring_init_t kri;
int err;
if (ddi_copyin(udata, &kri, sizeof (kri), md) != 0) {
return (EFAULT);
}
if (!VIONA_RING_VALID(link, kri.ri_index))
return (EINVAL);
struct viona_ring_params params = {
.vrp_pa_desc = kri.ri_qaddr,
.vrp_pa_avail = 0,
.vrp_pa_used = 0,
.vrp_size = kri.ri_qsize,
.vrp_avail_idx = 0,
.vrp_used_idx = 0,
};
if ((err = viona_ring_legacy_addr(¶ms)) != 0)
return (err);
err = viona_ring_init(link, kri.ri_index, ¶ms);
return (err);
}
static int
viona_ioc_ring_init_modern(viona_link_t *link, void *udata, int md)
{
vioc_ring_init_modern_t krim;
int err;
if (ddi_copyin(udata, &krim, sizeof (krim), md) != 0) {
return (EFAULT);
}
if (!VIONA_RING_VALID(link, krim.rim_index))
return (EINVAL);
const struct viona_ring_params params = {
.vrp_pa_desc = krim.rim_qaddr_desc,
.vrp_pa_avail = krim.rim_qaddr_avail,
.vrp_pa_used = krim.rim_qaddr_used,
.vrp_size = krim.rim_qsize,
.vrp_avail_idx = 0,
.vrp_used_idx = 0,
};
err = viona_ring_init(link, krim.rim_index, ¶ms);
return (err);
}
static int
viona_ioc_ring_set_state(viona_link_t *link, void *udata, int md)
{
vioc_ring_state_t krs;
int err;
if (ddi_copyin(udata, &krs, sizeof (krs), md) != 0) {
return (EFAULT);
}
const struct viona_ring_params params = {
.vrp_pa_desc = krs.vrs_qaddr_desc,
.vrp_pa_avail = krs.vrs_qaddr_avail,
.vrp_pa_used = krs.vrs_qaddr_used,
.vrp_size = krs.vrs_qsize,
.vrp_avail_idx = krs.vrs_avail_idx,
.vrp_used_idx = krs.vrs_used_idx,
};
err = viona_ring_init(link, krs.vrs_index, ¶ms);
return (err);
}
static int
viona_ioc_ring_get_state(viona_link_t *link, void *udata, int md)
{
vioc_ring_state_t krs;
if (ddi_copyin(udata, &krs, sizeof (krs), md) != 0) {
return (EFAULT);
}
struct viona_ring_params params;
int err = viona_ring_get_state(link, krs.vrs_index, ¶ms);
if (err != 0) {
return (err);
}
krs.vrs_qsize = params.vrp_size;
krs.vrs_qaddr_desc = params.vrp_pa_desc;
krs.vrs_qaddr_avail = params.vrp_pa_avail;
krs.vrs_qaddr_used = params.vrp_pa_used;
krs.vrs_avail_idx = params.vrp_avail_idx;
krs.vrs_used_idx = params.vrp_used_idx;
if (ddi_copyout(&krs, udata, sizeof (krs), md) != 0) {
return (EFAULT);
}
return (0);
}
static int
viona_ioc_link_setpairs(viona_link_t *link, uint16_t pairs)
{
int err;
viona_rx_clear(link);
err = viona_link_qalloc(link, pairs);
(void) viona_rx_set(link, link->l_promisc);
return (err);
}
static int
viona_ioc_link_usepairs(viona_link_t *link, uint16_t pairs)
{
if (pairs < VIONA_MIN_QPAIR || pairs > link->l_npairs)
return (EINVAL);
link->l_usepairs = pairs;
return (0);
}
static int
viona_ioc_ring_reset(viona_link_t *link, uint_t idx)
{
viona_vring_t *ring;
if (!VIONA_RING_VALID(link, idx)) {
return (EINVAL);
}
ring = &link->l_vrings[idx];
return (viona_ring_reset(ring, B_TRUE));
}
static int
viona_ioc_ring_kick(viona_link_t *link, uint_t idx)
{
viona_vring_t *ring;
int err;
if (!VIONA_RING_VALID(link, idx)) {
return (EINVAL);
}
ring = &link->l_vrings[idx];
mutex_enter(&ring->vr_lock);
switch (ring->vr_state) {
case VRS_SETUP:
case VRS_INIT:
ring->vr_state_flags |= VRSF_REQ_START;
case VRS_RUN:
cv_broadcast(&ring->vr_cv);
err = 0;
break;
default:
err = EBUSY;
break;
}
mutex_exit(&ring->vr_lock);
return (err);
}
static int
viona_ioc_ring_pause(viona_link_t *link, uint_t idx)
{
if (!VIONA_RING_VALID(link, idx)) {
return (EINVAL);
}
viona_vring_t *ring = &link->l_vrings[idx];
return (viona_ring_pause(ring));
}
static int
viona_ioc_ring_set_msi(viona_link_t *link, void *data, int md)
{
vioc_ring_msi_t vrm;
viona_vring_t *ring;
if (ddi_copyin(data, &vrm, sizeof (vrm), md) != 0) {
return (EFAULT);
}
if (!VIONA_RING_VALID(link, vrm.rm_index)) {
return (EINVAL);
}
ring = &link->l_vrings[vrm.rm_index];
mutex_enter(&ring->vr_lock);
ring->vr_msi_addr = vrm.rm_addr;
ring->vr_msi_msg = vrm.rm_msg;
mutex_exit(&ring->vr_lock);
return (0);
}
static int
viona_notify_iop(void *arg, bool in, uint16_t port, uint8_t bytes,
uint32_t *val)
{
viona_link_t *link = (viona_link_t *)arg;
if (in || port != link->l_notify_ioport) {
return (ESRCH);
}
const uint16_t vq = *val;
if (!VIONA_RING_VALID(link, vq)) {
return (ESRCH);
}
viona_vring_t *ring = &link->l_vrings[vq];
int res = 0;
mutex_enter(&ring->vr_lock);
if (ring->vr_state == VRS_RUN) {
cv_broadcast(&ring->vr_cv);
} else {
res = ESRCH;
}
mutex_exit(&ring->vr_lock);
return (res);
}
static int
viona_ioc_set_notify_ioport(viona_link_t *link, uint16_t ioport)
{
int err = 0;
if (link->l_notify_ioport != 0) {
vmm_drv_ioport_unhook(link->l_vm_hold, &link->l_notify_cookie);
link->l_notify_ioport = 0;
}
if (ioport != 0) {
err = vmm_drv_ioport_hook(link->l_vm_hold, ioport,
viona_notify_iop, (void *)link, &link->l_notify_cookie);
if (err == 0) {
link->l_notify_ioport = ioport;
}
}
return (err);
}
static int
viona_notify_mmio(void *arg, bool write, uint64_t address, int bytes,
uint64_t *val)
{
viona_link_t *link = (viona_link_t *)arg;
if (!write)
return (ESRCH);
const uint16_t vq = *val;
if (!VIONA_RING_VALID(link, vq))
return (ESRCH);
viona_vring_t *ring = &link->l_vrings[vq];
int res = 0;
mutex_enter(&ring->vr_lock);
if (ring->vr_state == VRS_RUN)
cv_broadcast(&ring->vr_cv);
else
res = ESRCH;
mutex_exit(&ring->vr_lock);
return (res);
}
static int
viona_ioc_set_notify_mmio(viona_link_t *link, void *udata, int md)
{
vioc_notify_mmio_t vim;
int err = 0;
if (link->l_notify_mmaddr != NOTIFY_MMADDR_UNSET) {
int err = vmm_drv_mmio_unhook(link->l_vm_hold,
&link->l_notify_mmcookie);
VERIFY(err == 0 || err == ENOENT);
link->l_notify_mmaddr = NOTIFY_MMADDR_UNSET;
}
if (udata == NULL)
return (0);
if (ddi_copyin(udata, &vim, sizeof (vim), md) != 0)
return (EFAULT);
err = vmm_drv_mmio_hook(link->l_vm_hold, vim.vim_address, vim.vim_size,
viona_notify_mmio, (void *)link, &link->l_notify_mmcookie);
if (err == 0) {
link->l_notify_mmaddr = vim.vim_address;
}
return (err);
}
static int
viona_ioc_set_promisc(viona_link_t *link, viona_promisc_t mode)
{
int err;
if (mode >= VIONA_PROMISC_MAX) {
return (EINVAL);
}
if (mode == link->l_promisc) {
return (0);
}
if ((err = viona_rx_set(link, mode)) != 0) {
return (err);
}
link->l_promisc = mode;
return (0);
}
#define PARAM_NM_TX_COPY_DATA "tx_copy_data"
#define PARAM_NM_TX_HEADER_PAD "tx_header_pad"
#define PARAM_ERR_INVALID_TYPE "invalid type"
#define PARAM_ERR_OUT_OF_RANGE "value out of range"
#define PARAM_ERR_UNK_KEY "unknown key"
static nvlist_t *
viona_params_to_nvlist(const viona_link_params_t *vlp)
{
nvlist_t *nvl = fnvlist_alloc();
fnvlist_add_boolean_value(nvl, PARAM_NM_TX_COPY_DATA,
vlp->vlp_tx_copy_data);
fnvlist_add_uint16(nvl, PARAM_NM_TX_HEADER_PAD,
vlp->vlp_tx_header_pad);
return (nvl);
}
static nvlist_t *
viona_params_from_nvlist(nvlist_t *nvl, viona_link_params_t *vlp)
{
nvlist_t *nverr = fnvlist_alloc();
nvpair_t *nvp = NULL;
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
const char *name = nvpair_name(nvp);
const data_type_t dtype = nvpair_type(nvp);
if (strcmp(name, PARAM_NM_TX_COPY_DATA) == 0) {
if (dtype == DATA_TYPE_BOOLEAN_VALUE) {
vlp->vlp_tx_copy_data =
fnvpair_value_boolean_value(nvp);
} else {
fnvlist_add_string(nverr, name,
PARAM_ERR_INVALID_TYPE);
}
continue;
}
if (strcmp(name, PARAM_NM_TX_HEADER_PAD) == 0) {
if (dtype == DATA_TYPE_UINT16) {
uint16_t value = fnvpair_value_uint16(nvp);
if (value > viona_max_header_pad) {
fnvlist_add_string(nverr, name,
PARAM_ERR_OUT_OF_RANGE);
} else {
vlp->vlp_tx_header_pad = value;
}
} else {
fnvlist_add_string(nverr, name,
PARAM_ERR_INVALID_TYPE);
}
continue;
}
fnvlist_add_string(nverr, name, PARAM_ERR_UNK_KEY);
}
if (!nvlist_empty(nverr)) {
return (nverr);
}
nvlist_free(nverr);
return (NULL);
}
static void
viona_params_get_defaults(viona_link_params_t *vlp)
{
vlp->vlp_tx_copy_data = viona_tx_copy_needed();
vlp->vlp_tx_header_pad = 0;
}
static int
viona_ioc_get_params(viona_link_t *link, void *udata, int md)
{
vioc_get_params_t vgp;
int err = 0;
if (ddi_copyin(udata, &vgp, sizeof (vgp), md) != 0) {
return (EFAULT);
}
nvlist_t *nvl = NULL;
if (link != NULL) {
nvl = viona_params_to_nvlist(&link->l_params);
} else {
viona_link_params_t vlp = { 0 };
viona_params_get_defaults(&vlp);
nvl = viona_params_to_nvlist(&vlp);
}
VERIFY(nvl != NULL);
size_t packed_sz;
void *packed = fnvlist_pack(nvl, &packed_sz);
nvlist_free(nvl);
if (packed_sz > vgp.vgp_param_sz) {
err = E2BIG;
}
vgp.vgp_param_sz = packed_sz;
if (err == 0 &&
ddi_copyout(packed, vgp.vgp_param, packed_sz, md) != 0) {
err = EFAULT;
}
kmem_free(packed, packed_sz);
if (ddi_copyout(&vgp, udata, sizeof (vgp), md) != 0) {
if (err != 0) {
err = EFAULT;
}
}
return (err);
}
static int
viona_ioc_set_params(viona_link_t *link, void *udata, int md)
{
vioc_set_params_t vsp;
int err = 0;
nvlist_t *nverr = NULL;
if (ddi_copyin(udata, &vsp, sizeof (vsp), md) != 0) {
return (EFAULT);
}
if (vsp.vsp_param_sz > VIONA_MAX_PARAM_NVLIST_SZ) {
err = E2BIG;
goto done;
} else if (vsp.vsp_param_sz == 0) {
err = EINVAL;
goto done;
}
const size_t packed_sz = vsp.vsp_param_sz;
void *packed = kmem_alloc(packed_sz, KM_SLEEP);
if (ddi_copyin(vsp.vsp_param, packed, packed_sz, md) != 0) {
kmem_free(packed, packed_sz);
err = EFAULT;
goto done;
}
nvlist_t *parsed = NULL;
if (nvlist_unpack(packed, packed_sz, &parsed, KM_SLEEP) == 0) {
viona_link_params_t new_params;
bcopy(&link->l_params, &new_params,
sizeof (new_params));
nverr = viona_params_from_nvlist(parsed, &new_params);
if (nverr == NULL) {
bcopy(&new_params, &link->l_params,
sizeof (new_params));
} else {
err = EINVAL;
}
} else {
err = EINVAL;
}
nvlist_free(parsed);
kmem_free(packed, packed_sz);
done:
if (nverr != NULL) {
size_t err_packed_sz;
void *err_packed = fnvlist_pack(nverr, &err_packed_sz);
if (err_packed_sz > vsp.vsp_error_sz) {
if (err != 0) {
err = E2BIG;
}
} else if (ddi_copyout(err_packed, vsp.vsp_error,
err_packed_sz, md) != 0 && err == 0) {
err = EFAULT;
}
vsp.vsp_error_sz = err_packed_sz;
nvlist_free(nverr);
kmem_free(err_packed, err_packed_sz);
} else {
vsp.vsp_error_sz = 0;
}
if (ddi_copyout(&vsp, udata, sizeof (vsp), md) != 0 && err == 0) {
err = EFAULT;
}
return (err);
}
static int
viona_ioc_ring_intr_clear(viona_link_t *link, uint_t idx)
{
if (!VIONA_RING_VALID(link, idx)) {
return (EINVAL);
}
link->l_vrings[idx].vr_intr_enabled = 0;
return (0);
}
static int
viona_ioc_intr_poll(viona_link_t *link, void *udata, int md, int *rv)
{
vioc_intr_poll_t vip = { 0 };
uint_t cnt = 0;
for (size_t i = 0;
i < ARRAY_SIZE(vip.vip_status) && i < VIONA_USABLE_RINGS(link);
i++) {
uint_t val = link->l_vrings[i].vr_intr_enabled;
vip.vip_status[i] = val;
if (val != 0)
cnt++;
}
if (ddi_copyout(&vip, udata, sizeof (vip), md) != 0)
return (EFAULT);
*rv = (int)cnt;
return (0);
}
static int
viona_ioc_intr_poll_mq(viona_link_t *link, void *udata, int md, int *rv)
{
vioc_intr_poll_mq_t vipm;
uint16_t cnt = 0;
int err = 0;
bzero(&vipm, sizeof (vipm));
if (ddi_copyin(udata, &vipm.vipm_nrings, sizeof (vipm.vipm_nrings),
md) != 0) {
return (EFAULT);
}
if (vipm.vipm_nrings < 1 || vipm.vipm_nrings > VIONA_USABLE_RINGS(link))
return (EINVAL);
for (uint_t i = 0; i < vipm.vipm_nrings; i++) {
if (link->l_vrings[i].vr_intr_enabled) {
VIONA_INTR_SET(&vipm, i);
cnt++;
}
}
if (ddi_copyout(&vipm, udata, sizeof (vipm), md) != 0)
err = EFAULT;
else
*rv = (int)cnt;
return (err);
}