vnet.c revision 445b4c2ed2d52ef648ae6b36e4f5e14ff3d234af
/*
* 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/mac_ether.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 int vnet_m_stat(void *, uint_t, uint64_t *);
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 *);
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, boolean_t upgrade);
void vnet_add_def_rte(void *arg, mac_tx_t m_tx, void *txarg);
void vnet_del_def_rte(void *arg);
void vnet_rx(void *arg, mac_resource_handle_t mrh, mblk_t *mp);
void vnet_tx_update(void *arg);
/* externs */
extern int vgen_init(void *vnetp, dev_info_t *vnetdip, const uint8_t *macaddr,
mac_register_t **vgenmacp);
extern int vgen_uninit(void *arg);
static mac_callbacks_t vnet_m_callbacks = {
0,
vnet_m_stat,
vnet_m_start,
vnet_m_stop,
vnet_m_promisc,
vnet_m_multicst,
vnet_m_unicst,
vnet_m_tx,
NULL,
NULL,
NULL
};
/*
* 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_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_mtu = VNET_LDC_MTU; /* ldc mtu */
uint32_t vnet_nfdb_hash = VNET_NFDB_HASH; /* size of fdb hash table */
uint32_t vnet_nrbufs = VNET_NRBUFS; /* number of receive buffers */
/*
* Property names
*/
static char macaddr_propname[] = "local-mac-address";
/*
* This is the string displayed by modinfo(1m).
*/
static char vnet_ident[] = "vnet driver v%I%";
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
/*
* NOTE: 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)
{
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_register_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;
/* setup links to vnet_t from both devinfo and mac_t */
ddi_set_driver_private(dip, (caddr_t)vnetp);
vnetp->dip = dip;
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, (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) {
(void) vgen_uninit(vgenmacp->m_driver);
}
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;
int rv;
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;
}
/* 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 */
rv = vgen_uninit(vp_tlp->macp->m_driver);
if (rv != DDI_SUCCESS) {
RW_EXIT(&vnetp->trwlock);
goto vnet_detach_fail;
}
}
vnet_del_vptl(vnetp, vp_tlp);
}
RW_EXIT(&vnetp->trwlock);
/*
* 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->mh) != 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);
kmem_free(vnetp->fdbhp,
sizeof (fdb_fanout_t) * (vnetp->nfdb_hash + 1));
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_register_t *vp_macp;
mac_callbacks_t *cbp;
DBG1((vnetp, "vnet_m_start: enter\n"));
/*
* NOTE:
* 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;
cbp = vp_macp->m_callbacks;
cbp->mc_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_register_t *vp_macp;
mac_callbacks_t *cbp;
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;
cbp = vp_macp->m_callbacks;
cbp->mc_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"));
/*
* NOTE: setting mac address dynamically is not supported.
*/
DBG1((vnetp, "vnet_m_unicst: exit\n"));
return (VNET_FAILURE);
}
/* 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_register_t *vp_macp;
mac_callbacks_t *cbp;
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;
cbp = vp_macp->m_callbacks;
rv = cbp->mc_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"));
/*
* NOTE: 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);
}
/* get statistics from the device */
int
vnet_m_stat(void *arg, uint_t stat, uint64_t *val)
{
vnet_t *vnetp = arg;
vp_tl_t *vp_tlp;
mac_register_t *vp_macp;
mac_callbacks_t *cbp;
uint64_t val_total = 0;
DBG1((vnetp, "vnet_m_stat: enter\n"));
/*
* get the specified statistic from each transport and return the
* aggregate val. This obviously only works for counters.
*/
if ((IS_MAC_STAT(stat) && !MAC_STAT_ISACOUNTER(stat)) ||
(IS_MACTYPE_STAT(stat) && !ETHER_STAT_ISACOUNTER(stat))) {
return (ENOTSUP);
}
READ_ENTER(&vnetp->trwlock);
for (vp_tlp = vnetp->tlp; vp_tlp != NULL; vp_tlp = vp_tlp->nextp) {
vp_macp = vp_tlp->macp;
cbp = vp_macp->m_callbacks;
if (cbp->mc_getstat(vp_macp->m_driver, stat, val) == 0)
val_total += *val;
}
RW_EXIT(&vnetp->trwlock);
*val = val_total;
DBG1((vnetp, "vnet_m_stat: exit\n"));
return (0);
}
/* wrapper function for mac_register() */
static int
vnet_mac_register(vnet_t *vnetp)
{
mac_register_t *macp;
int err;
if ((macp = mac_alloc(MAC_VERSION)) == NULL)
return (DDI_FAILURE);
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_driver = vnetp;
macp->m_dip = vnetp->dip;
macp->m_src_addr = vnetp->curr_macaddr;
macp->m_callbacks = &vnet_m_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = ETHERMTU;
/*
* Finally, we're ready to register ourselves with the MAC layer
* interface; if this succeeds, we're all ready to start()
*/
err = mac_register(macp, &vnetp->mh);
mac_free(macp);
return (err == 0 ? DDI_SUCCESS : DDI_FAILURE);
}
/* 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,
boolean_t upgrade)
{
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]);
if (upgrade == B_TRUE) {
/*
* Caller already holds the lock as a reader. This can
* occur if this function is invoked in the context
* of transmit routine - vnet_m_tx(), where the lock
* is held as a reader before calling the transmit
* function of an fdb entry (fdbp->m_tx).
* See comments in vgen_ldcsend() in vnet_gen.c
*/
if (!rw_tryupgrade(&fdbhp->rwlock)) {
RW_EXIT(&fdbhp->rwlock);
WRITE_ENTER(&fdbhp->rwlock);
}
} else {
/* Caller does not hold the lock */
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;
}
}
if (upgrade == B_TRUE) {
/* restore the caller as a reader */
rw_downgrade(&fdbhp->rwlock);
} else {
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);
}
void
vnet_rx(void *arg, mac_resource_handle_t mrh, mblk_t *mp)
{
vnet_t *vnetp = arg;
mac_rx(vnetp->mh, mrh, mp);
}
void
vnet_tx_update(void *arg)
{
vnet_t *vnetp = arg;
mac_tx_update(vnetp->mh);
}