/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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); 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); }