vnet.c revision 1ae0874509b6811fdde1dfd46f0d93fd09867a3f
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/stream.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/ethernet.h>
#include <sys/dlpi.h>
#include <net/if.h>
#include <sys/mac.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/strsun.h>
#include <sys/note.h>
#include <sys/vnet.h>
/*
* Function prototypes.
*/
/* DDI entrypoints */
static int vnetdevinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int vnetattach(dev_info_t *, ddi_attach_cmd_t);
static int vnetdetach(dev_info_t *, ddi_detach_cmd_t);
/* MAC entrypoints */
static uint64_t vnet_m_stat(void *arg, enum mac_stat stat);
static int vnet_m_start(void *);
static void vnet_m_stop(void *);
static int vnet_m_promisc(void *, boolean_t);
static int vnet_m_multicst(void *, boolean_t, const uint8_t *);
static int vnet_m_unicst(void *, const uint8_t *);
static void vnet_m_resources(void *);
static void vnet_m_ioctl(void *, queue_t *, mblk_t *);
mblk_t *vnet_m_tx(void *, mblk_t *);
/* vnet internal functions */
static int vnet_mac_register(vnet_t *);
static int vnet_read_mac_address(vnet_t *vnetp);
static void vnet_add_vptl(vnet_t *vnetp, vp_tl_t *vp_tlp);
static void vnet_del_vptl(vnet_t *vnetp, vp_tl_t *vp_tlp);
static vp_tl_t *vnet_get_vptl(vnet_t *vnetp, const char *devname);
static fdb_t *vnet_lookup_fdb(fdb_fanout_t *fdbhp, uint8_t *macaddr);
/* exported functions */
void vnet_add_fdb(void *arg, uint8_t *macaddr, mac_tx_t m_tx, void *txarg);
void vnet_del_fdb(void *arg, uint8_t *macaddr);
void vnet_modify_fdb(void *arg, uint8_t *macaddr, mac_tx_t m_tx, void *txarg);
void vnet_add_def_rte(void *arg, mac_tx_t m_tx, void *txarg);
void vnet_del_def_rte(void *arg);
/* externs */
extern int vgen_init(void *vnetp, dev_info_t *vnetdip, void *vnetmacp,
const uint8_t *macaddr, mac_t **vgenmacp);
extern void vgen_uninit(void *arg);
/*
* Linked list of "vnet_t" structures - one per instance.
*/
static vnet_t *vnet_headp = NULL;
static krwlock_t vnet_rw;
/* Tunables */
uint32_t vnet_ntxds = VNET_NTXDS; /* power of 2 transmit descriptors */
uint32_t vnet_reclaim_lowat = VNET_RECLAIM_LOWAT; /* tx recl low watermark */
uint32_t vnet_reclaim_hiwat = VNET_RECLAIM_HIWAT; /* tx recl high watermark */
uint32_t vnet_ldcwd_interval = VNET_LDCWD_INTERVAL; /* watchdog freq in msec */
uint32_t vnet_ldcwd_txtimeout = VNET_LDCWD_TXTIMEOUT; /* tx timeout in msec */
uint32_t vnet_ldc_qlen = VNET_LDC_QLEN; /* ldc qlen */
uint32_t vnet_nfdb_hash = VNET_NFDB_HASH; /* size of fdb hash table */
/*
* Property names
*/
static char macaddr_propname[] = "local-mac-address";
static struct ether_addr etherbroadcastaddr = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
/*
* MIB II broadcast/multicast packets
*/
#define IS_BROADCAST(ehp) \
(ether_cmp(&ehp->ether_dhost, &etherbroadcastaddr) == 0)
#define IS_MULTICAST(ehp) \
((ehp->ether_dhost.ether_addr_octet[0] & 01) == 1)
/*
* This is the string displayed by modinfo(1m).
*/
static char vnet_ident[] = "vnet driver v1.0";
extern struct mod_ops mod_driverops;
static struct cb_ops cb_vnetops = {
nulldev, /* cb_open */
nulldev, /* cb_close */
nodev, /* 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 */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_stream */
(int)(D_MP) /* cb_flag */
};
static struct dev_ops vnetops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
NULL, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
vnetattach, /* devo_attach */
vnetdetach, /* devo_detach */
nodev, /* devo_reset */
&cb_vnetops, /* devo_cb_ops */
(struct bus_ops *)NULL /* devo_bus_ops */
};
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
vnet_ident, /* ID string */
&vnetops /* driver specific ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
/*
* Print debug messages - set to 0xf to enable all msgs
*/
int _vnet_dbglevel = 0x8;
void
_vnetdebug_printf(void *arg, const char *fmt, ...)
{
char buf[512];
va_list ap;
vnet_t *vnetp = (vnet_t *)arg;
va_start(ap, fmt);
(void) vsprintf(buf, fmt, ap);
va_end(ap);
if (vnetp == NULL)
cmn_err(CE_CONT, "%s\n", buf);
else
cmn_err(CE_CONT, "vnet%d: %s\n", vnetp->instance, buf);
}
#ifdef DEBUG
/*
* XXX: any changes to the definitions below need corresponding changes in
* vnet_gen.c
*/
/*
* debug levels:
* DBG_LEVEL1: Function entry/exit tracing
* DBG_LEVEL2: Info messages
* DBG_LEVEL3: Warning messages
* DBG_LEVEL4: Error messages
*/
enum { DBG_LEVEL1 = 0x01, DBG_LEVEL2 = 0x02, DBG_LEVEL3 = 0x04,
DBG_LEVEL4 = 0x08 };
#define DBG1(_s) do { \
if ((_vnet_dbglevel & DBG_LEVEL1) != 0) { \
_vnetdebug_printf _s; \
} \
_NOTE(CONSTCOND) } while (0)
#define DBG2(_s) do { \
if ((_vnet_dbglevel & DBG_LEVEL2) != 0) { \
_vnetdebug_printf _s; \
} \
_NOTE(CONSTCOND) } while (0)
#define DWARN(_s) do { \
if ((_vnet_dbglevel & DBG_LEVEL3) != 0) { \
_vnetdebug_printf _s; \
} \
_NOTE(CONSTCOND) } while (0)
#define DERR(_s) do { \
if ((_vnet_dbglevel & DBG_LEVEL4) != 0) { \
_vnetdebug_printf _s; \
} \
_NOTE(CONSTCOND) } while (0)
#else
#define DBG1(_s) if (0) _vnetdebug_printf _s
#define DBG2(_s) if (0) _vnetdebug_printf _s
#define DWARN(_s) if (0) _vnetdebug_printf _s
#define DERR(_s) if (0) _vnetdebug_printf _s
#endif
/* _init(9E): initialize the loadable module */
int
_init(void)
{
int status;
DBG1((NULL, "_init: enter\n"));
mac_init_ops(&vnetops, "vnet");
status = mod_install(&modlinkage);
if (status != 0) {
mac_fini_ops(&vnetops);
}
DBG1((NULL, "_init: exit\n"));
return (status);
}
/* _fini(9E): prepare the module for unloading. */
int
_fini(void)
{
int status;
DBG1((NULL, "_fini: enter\n"));
status = mod_remove(&modlinkage);
if (status != 0)
return (status);
mac_fini_ops(&vnetops);
DBG1((NULL, "_fini: exit\n"));
return (status);
}
/* _info(9E): return information about the loadable module */
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* attach(9E): attach a device to the system.
* called once for each instance of the device on the system.
*/
static int
vnetattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
mac_t *macp;
vnet_t *vnetp;
vp_tl_t *vp_tlp;
int instance;
int status;
enum { AST_init = 0x0, AST_vnet_alloc = 0x1,
AST_mac_alloc = 0x2, AST_read_macaddr = 0x4,
AST_vgen_init = 0x8, AST_vptl_alloc = 0x10,
AST_fdbh_alloc = 0x20 }
attach_state;
mac_t *vgenmacp = NULL;
uint32_t nfdbh = 0;
attach_state = AST_init;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
case DDI_PM_RESUME:
default:
goto vnet_attach_fail;
}
instance = ddi_get_instance(dip);
DBG1((NULL, "vnetattach: instance(%d) enter\n", instance));
/* allocate vnet_t and mac_t structures */
vnetp = kmem_zalloc(sizeof (vnet_t), KM_SLEEP);
attach_state |= AST_vnet_alloc;
macp = kmem_zalloc(sizeof (mac_t), KM_SLEEP);
attach_state |= AST_mac_alloc;
/* setup links to vnet_t from both devinfo and mac_t */
ddi_set_driver_private(dip, (caddr_t)vnetp);
macp->m_driver = vnetp;
vnetp->dip = dip;
vnetp->macp = macp;
vnetp->instance = instance;
/* read the mac address */
status = vnet_read_mac_address(vnetp);
if (status != DDI_SUCCESS) {
goto vnet_attach_fail;
}
attach_state |= AST_read_macaddr;
/*
* Initialize the generic vnet proxy transport. This is the first
* and default transport used by vnet. The generic transport
* is provided by using sun4v LDC (logical domain channel). On success,
* vgen_init() provides a pointer to mac_t of generic transport.
* Currently, this generic layer provides network connectivity to other
* vnets within ldoms and also to remote hosts oustide ldoms through
* the virtual switch (vsw) device on domain0. In the future, when
* physical adapters that are able to share their resources (such as
* dma channels) with guest domains become available, the vnet device
* will use hardware specific driver to communicate directly over the
* physical device to reach remote hosts without going through vswitch.
*/
status = vgen_init(vnetp, vnetp->dip, vnetp->macp,
(uint8_t *)vnetp->curr_macaddr, &vgenmacp);
if (status != DDI_SUCCESS) {
DERR((vnetp, "vgen_init() failed\n"));
goto vnet_attach_fail;
}
attach_state |= AST_vgen_init;
vp_tlp = kmem_zalloc(sizeof (vp_tl_t), KM_SLEEP);
vp_tlp->macp = vgenmacp;
(void) snprintf(vp_tlp->name, MAXNAMELEN, "%s%u", "vgen", instance);
(void) strcpy(vnetp->vgen_name, vp_tlp->name);
/* add generic transport to the list of vnet proxy transports */
vnet_add_vptl(vnetp, vp_tlp);
attach_state |= AST_vptl_alloc;
nfdbh = vnet_nfdb_hash;
if ((nfdbh < VNET_NFDB_HASH) || (nfdbh > VNET_NFDB_HASH_MAX)) {
vnetp->nfdb_hash = VNET_NFDB_HASH;
}
else
vnetp->nfdb_hash = nfdbh;
/* allocate fdb hash table, with an extra slot for default route */
vnetp->fdbhp = kmem_zalloc(sizeof (fdb_fanout_t) *
(vnetp->nfdb_hash + 1), KM_SLEEP);
attach_state |= AST_fdbh_alloc;
/* register with MAC layer */
status = vnet_mac_register(vnetp);
if (status != DDI_SUCCESS) {
goto vnet_attach_fail;
}
/* add to the list of vnet devices */
WRITE_ENTER(&vnet_rw);
vnetp->nextp = vnet_headp;
vnet_headp = vnetp;
RW_EXIT(&vnet_rw);
DBG1((NULL, "vnetattach: instance(%d) exit\n", instance));
return (DDI_SUCCESS);
vnet_attach_fail:
if (attach_state & AST_fdbh_alloc) {
kmem_free(vnetp->fdbhp,
sizeof (fdb_fanout_t) * (vnetp->nfdb_hash + 1));
}
if (attach_state & AST_vptl_alloc) {
WRITE_ENTER(&vnetp->trwlock);
vnet_del_vptl(vnetp, vp_tlp);
RW_EXIT(&vnetp->trwlock);
}
if (attach_state & AST_vgen_init) {
vgen_uninit(vgenmacp->m_driver);
}
if (attach_state & AST_mac_alloc) {
KMEM_FREE(macp);
}
if (attach_state & AST_vnet_alloc) {
KMEM_FREE(vnetp);
}
return (DDI_FAILURE);
}
/*
* detach(9E): detach a device from the system.
*/
static int
vnetdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
vnet_t *vnetp;
vnet_t **vnetpp;
vp_tl_t *vp_tlp;
int instance;
instance = ddi_get_instance(dip);
DBG1((NULL, "vnetdetach: instance(%d) enter\n", instance));
vnetp = ddi_get_driver_private(dip);
if (vnetp == NULL) {
goto vnet_detach_fail;
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
case DDI_PM_SUSPEND:
default:
goto vnet_detach_fail;
}
/*
* Unregister from the MAC subsystem. This can fail, in
* particular if there are DLPI style-2 streams still open -
* in which case we just return failure.
*/
if (mac_unregister(vnetp->macp) != 0)
goto vnet_detach_fail;
/* unlink from instance(vnet_t) list */
WRITE_ENTER(&vnet_rw);
for (vnetpp = &vnet_headp; *vnetpp; vnetpp = &(*vnetpp)->nextp) {
if (*vnetpp == vnetp) {
*vnetpp = vnetp->nextp;
break;
}
}
RW_EXIT(&vnet_rw);
/* uninit and free vnet proxy transports */
WRITE_ENTER(&vnetp->trwlock);
while ((vp_tlp = vnetp->tlp) != NULL) {
if (strcmp(vnetp->vgen_name, vp_tlp->name) == 0) {
/* uninitialize generic transport */
vgen_uninit(vp_tlp->macp->m_driver);
}
vnet_del_vptl(vnetp, vp_tlp);
}
RW_EXIT(&vnetp->trwlock);
KMEM_FREE(vnetp->macp);
KMEM_FREE(vnetp);
return (DDI_SUCCESS);
vnet_detach_fail:
return (DDI_FAILURE);
}
/* enable the device for transmit/receive */
static int
vnet_m_start(void *arg)
{
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_t *vp_macp;
DBG1((vnetp, "vnet_m_start: enter\n"));
/*
* XXX
* Currently, we only have generic transport. m_start() invokes
* vgen_start() which enables ports/channels in vgen and
* initiates handshake with peer vnets and vsw. In the future when we
* have support for hardware specific transports, this information
* needs to be propagted back to vnet from vgen and we need to revisit
* this code (see comments in vnet_attach()).
*
*/
WRITE_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
vp_macp = vp_tlp->macp;
vp_macp->m_start(vp_macp->m_driver);
}
RW_EXIT(&vnetp->trwlock);
DBG1((vnetp, "vnet_m_start: exit\n"));
return (VNET_SUCCESS);
}
/* stop transmit/receive for the device */
static void
vnet_m_stop(void *arg)
{
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_t *vp_macp;
DBG1((vnetp, "vnet_m_stop: enter\n"));
WRITE_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
vp_macp = vp_tlp->macp;
vp_macp->m_stop(vp_macp->m_driver);
}
RW_EXIT(&vnetp->trwlock);
DBG1((vnetp, "vnet_m_stop: exit\n"));
}
/* set the unicast mac address of the device */
static int
vnet_m_unicst(void *arg, const uint8_t *macaddr)
{
_NOTE(ARGUNUSED(macaddr))
vnet_t *vnetp = arg;
DBG1((vnetp, "vnet_m_unicst: enter\n"));
/*
* XXX: setting mac address dynamically is not supported.
*/
#if 0
bcopy(macaddr, vnetp->curr_macaddr, ETHERADDRL);
#endif
DBG1((vnetp, "vnet_m_unicst: exit\n"));
return (VNET_SUCCESS);
}
/* enable/disable a multicast address */
static int
vnet_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
_NOTE(ARGUNUSED(add, mca))
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_t *vp_macp;
int rv = VNET_SUCCESS;
DBG1((vnetp, "vnet_m_multicst: enter\n"));
READ_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
if (strcmp(vnetp->vgen_name, vp_tlp->name) == 0) {
vp_macp = vp_tlp->macp;
rv = vp_macp->m_multicst(vp_macp->m_driver, add, mca);
break;
}
}
RW_EXIT(&vnetp->trwlock);
DBG1((vnetp, "vnet_m_multicst: exit\n"));
return (rv);
}
/* set or clear promiscuous mode on the device */
static int
vnet_m_promisc(void *arg, boolean_t on)
{
_NOTE(ARGUNUSED(on))
vnet_t *vnetp = arg;
DBG1((vnetp, "vnet_m_promisc: enter\n"));
/*
* XXX: setting promiscuous mode is not supported, just return success.
*/
DBG1((vnetp, "vnet_m_promisc: exit\n"));
return (VNET_SUCCESS);
}
/*
* Transmit a chain of packets. This function provides switching functionality
* based on the destination mac address to reach other guests (within ldoms) or
* external hosts.
*/
mblk_t *
vnet_m_tx(void *arg, mblk_t *mp)
{
vnet_t *vnetp;
mblk_t *next;
uint32_t fdbhash;
fdb_t *fdbp;
fdb_fanout_t *fdbhp;
struct ether_header *ehp;
uint8_t *macaddr;
mblk_t *resid_mp;
vnetp = (vnet_t *)arg;
DBG1((vnetp, "vnet_m_tx: enter\n"));
ASSERT(mp != NULL);
while (mp != NULL) {
next = mp->b_next;
mp->b_next = NULL;
/* get the destination mac address in the eth header */
ehp = (struct ether_header *)mp->b_rptr;
macaddr = (uint8_t *)&ehp->ether_dhost;
/* Calculate hash value and fdb fanout */
fdbhash = MACHASH(macaddr, vnetp->nfdb_hash);
fdbhp = &(vnetp->fdbhp[fdbhash]);
READ_ENTER(&fdbhp->rwlock);
fdbp = vnet_lookup_fdb(fdbhp, macaddr);
if (fdbp) {
/*
* If the destination is in FDB, the destination is
* a vnet device within ldoms and directly reachable,
* invoke the tx function in the fdb entry.
*/
resid_mp = fdbp->m_tx(fdbp->txarg, mp);
if (resid_mp != NULL) {
/* m_tx failed */
mp->b_next = next;
RW_EXIT(&fdbhp->rwlock);
break;
}
RW_EXIT(&fdbhp->rwlock);
} else {
/* destination is not in FDB */
RW_EXIT(&fdbhp->rwlock);
/*
* If the destination is broadcast/multicast
* or an unknown unicast address, forward the
* packet to vsw, using the last slot in fdb which is
* reserved for default route.
*/
fdbhp = &(vnetp->fdbhp[vnetp->nfdb_hash]);
READ_ENTER(&fdbhp->rwlock);
fdbp = fdbhp->headp;
if (fdbp) {
resid_mp = fdbp->m_tx(fdbp->txarg, mp);
if (resid_mp != NULL) {
/* m_tx failed */
mp->b_next = next;
RW_EXIT(&fdbhp->rwlock);
break;
}
} else {
/* drop the packet */
freemsg(mp);
}
RW_EXIT(&fdbhp->rwlock);
}
mp = next;
}
DBG1((vnetp, "vnet_m_tx: exit\n"));
return (mp);
}
/* register resources with mac layer */
static void
vnet_m_resources(void *arg)
{
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_t *vp_macp;
DBG1((vnetp, "vnet_m_resources: enter\n"));
WRITE_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
vp_macp = vp_tlp->macp;
vp_macp->m_resources(vp_macp->m_driver);
}
RW_EXIT(&vnetp->trwlock);
DBG1((vnetp, "vnet_m_resources: exit\n"));
}
/*
* vnet specific ioctls
*/
static void
vnet_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
vnet_t *vnetp = (vnet_t *)arg;
struct iocblk *iocp;
int cmd;
DBG1((vnetp, "vnet_m_ioctl: enter\n"));
iocp = (struct iocblk *)mp->b_rptr;
iocp->ioc_error = 0;
cmd = iocp->ioc_cmd;
switch (cmd) {
default:
miocnak(wq, mp, 0, EINVAL);
break;
}
DBG1((vnetp, "vnet_m_ioctl: exit\n"));
}
/* get statistics from the device */
uint64_t
vnet_m_stat(void *arg, enum mac_stat stat)
{
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_t *vp_macp;
uint64_t val = 0;
DBG1((vnetp, "vnet_m_stat: enter\n"));
/*
* get the specified statistic from each transport
* and return the aggregate val
*/
READ_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
vp_macp = vp_tlp->macp;
val += vp_macp->m_stat(vp_macp->m_driver, stat);
}
RW_EXIT(&vnetp->trwlock);
DBG1((vnetp, "vnet_m_stat: exit\n"));
return (val);
}
/* wrapper function for mac_register() */
static int
vnet_mac_register(vnet_t *vnetp)
{
mac_info_t *mip;
mac_t *macp;
macp = vnetp->macp;
mip = &(macp->m_info);
mip->mi_media = DL_ETHER;
mip->mi_sdu_min = 0;
mip->mi_sdu_max = ETHERMTU;
mip->mi_cksum = 0;
mip->mi_poll = 0; /* DL_CAPAB_POLL ? */
mip->mi_addr_length = ETHERADDRL;
bcopy(&etherbroadcastaddr, mip->mi_brdcst_addr, ETHERADDRL);
bcopy(vnetp->curr_macaddr, mip->mi_unicst_addr, ETHERADDRL);
MAC_STAT_MIB(mip->mi_stat);
mip->mi_stat[MAC_STAT_UNKNOWNS] = B_FALSE;
MAC_STAT_ETHER(mip->mi_stat);
mip->mi_stat[MAC_STAT_SQE_ERRORS] = B_FALSE;
mip->mi_stat[MAC_STAT_MACRCV_ERRORS] = B_FALSE;
macp->m_stat = vnet_m_stat;
macp->m_start = vnet_m_start;
macp->m_stop = vnet_m_stop;
macp->m_promisc = vnet_m_promisc;
macp->m_multicst = vnet_m_multicst;
macp->m_unicst = vnet_m_unicst;
macp->m_resources = vnet_m_resources;
macp->m_ioctl = vnet_m_ioctl;
macp->m_tx = vnet_m_tx;
macp->m_dip = vnetp->dip;
macp->m_ident = MAC_IDENT;
/*
* Finally, we're ready to register ourselves with the MAC layer
* interface; if this succeeds, we're all ready to start()
*/
if (mac_register(macp) != 0) {
KMEM_FREE(macp);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/* add vp_tl to the list */
static void
vnet_add_vptl(vnet_t *vnetp, vp_tl_t *vp_tlp)
{
vp_tl_t *ttlp;
WRITE_ENTER(&vnetp->trwlock);
if (vnetp->tlp == NULL) {
vnetp->tlp = vp_tlp;
} else {
ttlp = vnetp->tlp;
while (ttlp->nextp)
ttlp = ttlp->nextp;
ttlp->nextp = vp_tlp;
}
RW_EXIT(&vnetp->trwlock);
}
/* remove vp_tl from the list */
static void
vnet_del_vptl(vnet_t *vnetp, vp_tl_t *vp_tlp)
{
vp_tl_t *ttlp, **pretlp;
boolean_t found = B_FALSE;
pretlp = &vnetp->tlp;
ttlp = *pretlp;
while (ttlp) {
if (ttlp == vp_tlp) {
found = B_TRUE;
(*pretlp) = ttlp->nextp;
ttlp->nextp = NULL;
break;
}
pretlp = &(ttlp->nextp);
ttlp = *pretlp;
}
if (found) {
KMEM_FREE(vp_tlp);
}
}
/* get vp_tl corresponding to the given name */
static vp_tl_t *
vnet_get_vptl(vnet_t *vnetp, const char *name)
{
vp_tl_t *tlp;
tlp = vnetp->tlp;
while (tlp) {
if (strcmp(tlp->name, name) == 0) {
return (tlp);
}
tlp = tlp->nextp;
}
DWARN((vnetp,
"vnet_get_vptl: can't find vp_tl with name (%s)\n", name));
return (NULL);
}
/* read the mac address of the device */
static int
vnet_read_mac_address(vnet_t *vnetp)
{
uchar_t *macaddr;
uint32_t size;
int rv;
rv = ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, vnetp->dip,
DDI_PROP_DONTPASS, macaddr_propname, &macaddr, &size);
if ((rv != DDI_PROP_SUCCESS) || (size != ETHERADDRL)) {
DWARN((vnetp,
"vnet_read_mac_address: prop_lookup failed (%s) err (%d)\n",
macaddr_propname, rv));
return (DDI_FAILURE);
}
bcopy(macaddr, (caddr_t)vnetp->vendor_addr, ETHERADDRL);
bcopy(macaddr, (caddr_t)vnetp->curr_macaddr, ETHERADDRL);
ddi_prop_free(macaddr);
return (DDI_SUCCESS);
}
/*
* Functions below are called only by generic transport to add/remove/modify
* entries in forwarding database. See comments in vgen_port_init(vnet_gen.c).
*/
/* add an entry into the forwarding database */
void
vnet_add_fdb(void *arg, uint8_t *macaddr, mac_tx_t m_tx, void *txarg)
{
vnet_t *vnetp = (vnet_t *)arg;
uint32_t fdbhash;
fdb_t *fdbp;
fdb_fanout_t *fdbhp;
/* Calculate hash value and fdb fanout */
fdbhash = MACHASH(macaddr, vnetp->nfdb_hash);
fdbhp = &(vnetp->fdbhp[fdbhash]);
WRITE_ENTER(&fdbhp->rwlock);
fdbp = kmem_zalloc(sizeof (fdb_t), KM_NOSLEEP);
if (fdbp == NULL) {
RW_EXIT(&fdbhp->rwlock);
return;
}
bcopy(macaddr, (caddr_t)fdbp->macaddr, ETHERADDRL);
fdbp->m_tx = m_tx;
fdbp->txarg = txarg;
fdbp->nextp = fdbhp->headp;
fdbhp->headp = fdbp;
RW_EXIT(&fdbhp->rwlock);
}
/* delete an entry from the forwarding database */
void
vnet_del_fdb(void *arg, uint8_t *macaddr)
{
vnet_t *vnetp = (vnet_t *)arg;
uint32_t fdbhash;
fdb_t *fdbp;
fdb_t **pfdbp;
fdb_fanout_t *fdbhp;
/* Calculate hash value and fdb fanout */
fdbhash = MACHASH(macaddr, vnetp->nfdb_hash);
fdbhp = &(vnetp->fdbhp[fdbhash]);
WRITE_ENTER(&fdbhp->rwlock);
for (pfdbp = &fdbhp->headp; (fdbp = *pfdbp) != NULL;
pfdbp = &fdbp->nextp) {
if (bcmp(fdbp->macaddr, macaddr, ETHERADDRL) == 0) {
/* Unlink it from the list */
*pfdbp = fdbp->nextp;
KMEM_FREE(fdbp);
break;
}
}
RW_EXIT(&fdbhp->rwlock);
}
/* modify an existing entry in the forwarding database */
void
vnet_modify_fdb(void *arg, uint8_t *macaddr, mac_tx_t m_tx, void *txarg)
{
vnet_t *vnetp = (vnet_t *)arg;
uint32_t fdbhash;
fdb_t *fdbp;
fdb_fanout_t *fdbhp;
/* Calculate hash value and fdb fanout */
fdbhash = MACHASH(macaddr, vnetp->nfdb_hash);
fdbhp = &(vnetp->fdbhp[fdbhash]);
WRITE_ENTER(&fdbhp->rwlock);
for (fdbp = fdbhp->headp; fdbp != NULL; fdbp = fdbp->nextp) {
if (bcmp(fdbp->macaddr, macaddr, ETHERADDRL) == 0) {
/* change the entry to have new tx params */
fdbp->m_tx = m_tx;
fdbp->txarg = txarg;
break;
}
}
RW_EXIT(&fdbhp->rwlock);
}
/* look up an fdb entry based on the mac address, caller holds lock */
static fdb_t *
vnet_lookup_fdb(fdb_fanout_t *fdbhp, uint8_t *macaddr)
{
fdb_t *fdbp = NULL;
for (fdbp = fdbhp->headp; fdbp != NULL; fdbp = fdbp->nextp) {
if (bcmp(fdbp->macaddr, macaddr, ETHERADDRL) == 0) {
break;
}
}
return (fdbp);
}
/* add default route entry into the forwarding database */
void
vnet_add_def_rte(void *arg, mac_tx_t m_tx, void *txarg)
{
vnet_t *vnetp = (vnet_t *)arg;
fdb_t *fdbp;
fdb_fanout_t *fdbhp;
/*
* The last hash list is reserved for default route entry,
* and for now, we have only one entry in this list.
*/
fdbhp = &(vnetp->fdbhp[vnetp->nfdb_hash]);
WRITE_ENTER(&fdbhp->rwlock);
if (fdbhp->headp) {
DWARN((vnetp,
"vnet_add_def_rte: default rte already exists\n"));
RW_EXIT(&fdbhp->rwlock);
return;
}
fdbp = kmem_zalloc(sizeof (fdb_t), KM_NOSLEEP);
if (fdbp == NULL) {
RW_EXIT(&fdbhp->rwlock);
return;
}
bzero(fdbp->macaddr, ETHERADDRL);
fdbp->m_tx = m_tx;
fdbp->txarg = txarg;
fdbp->nextp = NULL;
fdbhp->headp = fdbp;
RW_EXIT(&fdbhp->rwlock);
}
/* delete default route entry from the forwarding database */
void
vnet_del_def_rte(void *arg)
{
vnet_t *vnetp = (vnet_t *)arg;
fdb_t *fdbp;
fdb_fanout_t *fdbhp;
/*
* The last hash list is reserved for default route entry,
* and for now, we have only one entry in this list.
*/
fdbhp = &(vnetp->fdbhp[vnetp->nfdb_hash]);
WRITE_ENTER(&fdbhp->rwlock);
if (fdbhp->headp == NULL) {
RW_EXIT(&fdbhp->rwlock);
return;
}
fdbp = fdbhp->headp;
KMEM_FREE(fdbp);
fdbhp->headp = NULL;
RW_EXIT(&fdbhp->rwlock);
}