summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/dls/dls_mgmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/dls/dls_mgmt.c')
-rw-r--r--usr/src/uts/common/io/dls/dls_mgmt.c1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/dls/dls_mgmt.c b/usr/src/uts/common/io/dls/dls_mgmt.c
new file mode 100644
index 0000000000..aff6ba26b1
--- /dev/null
+++ b/usr/src/uts/common/io/dls/dls_mgmt.c
@@ -0,0 +1,1562 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Datalink management routines.
+ */
+
+#include <sys/types.h>
+#include <sys/door.h>
+#include <sys/zone.h>
+#include <sys/modctl.h>
+#include <sys/file.h>
+#include <sys/modhash.h>
+#include <sys/kstat.h>
+#include <sys/vnode.h>
+#include <sys/cmn_err.h>
+#include <sys/vlan.h>
+#include <sys/softmac.h>
+#include <sys/dls.h>
+#include <sys/dls_impl.h>
+
+static kmem_cache_t *i_dls_devnet_cachep;
+static kmutex_t i_dls_mgmt_lock;
+static krwlock_t i_dls_devnet_lock;
+static mod_hash_t *i_dls_devnet_id_hash;
+static mod_hash_t *i_dls_devnet_hash;
+
+boolean_t devnet_need_rebuild;
+
+#define VLAN_HASHSZ 67 /* prime */
+
+/* Upcall door handle */
+static door_handle_t dls_mgmt_dh = NULL;
+
+/*
+ * This structure is used to keep the <linkid, macname, vid> mapping.
+ */
+typedef struct dls_devnet_s {
+ datalink_id_t dd_vlanid;
+ datalink_id_t dd_linkid;
+ char dd_mac[MAXNAMELEN];
+ uint16_t dd_vid;
+ char dd_spa[MAXSPALEN];
+ boolean_t dd_explicit;
+ kstat_t *dd_ksp;
+
+ uint32_t dd_ref;
+
+ kmutex_t dd_mutex;
+ kcondvar_t dd_cv;
+ uint32_t dd_tref;
+
+ kmutex_t dd_zid_mutex;
+ zoneid_t dd_zid;
+} dls_devnet_t;
+
+/*ARGSUSED*/
+static int
+i_dls_devnet_constructor(void *buf, void *arg, int kmflag)
+{
+ dls_devnet_t *ddp = buf;
+
+ bzero(buf, sizeof (dls_devnet_t));
+ mutex_init(&ddp->dd_mutex, NULL, MUTEX_DEFAULT, NULL);
+ mutex_init(&ddp->dd_zid_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&ddp->dd_cv, NULL, CV_DEFAULT, NULL);
+ return (0);
+}
+
+/*ARGSUSED*/
+static void
+i_dls_devnet_destructor(void *buf, void *arg)
+{
+ dls_devnet_t *ddp = buf;
+
+ ASSERT(ddp->dd_ksp == NULL);
+ ASSERT(ddp->dd_ref == 0);
+ ASSERT(ddp->dd_tref == 0);
+ ASSERT(!ddp->dd_explicit);
+ mutex_destroy(&ddp->dd_mutex);
+ mutex_destroy(&ddp->dd_zid_mutex);
+ cv_destroy(&ddp->dd_cv);
+}
+
+/*
+ * Module initialization and finalization functions.
+ */
+void
+dls_mgmt_init(void)
+{
+ mutex_init(&i_dls_mgmt_lock, NULL, MUTEX_DEFAULT, NULL);
+ rw_init(&i_dls_devnet_lock, NULL, RW_DEFAULT, NULL);
+
+ /*
+ * Create a kmem_cache of dls_devnet_t structures.
+ */
+ i_dls_devnet_cachep = kmem_cache_create("dls_devnet_cache",
+ sizeof (dls_devnet_t), 0, i_dls_devnet_constructor,
+ i_dls_devnet_destructor, NULL, NULL, NULL, 0);
+ ASSERT(i_dls_devnet_cachep != NULL);
+
+ /*
+ * Create a hash table, keyed by dd_vlanid, of dls_devnet_t.
+ */
+ i_dls_devnet_id_hash = mod_hash_create_idhash("dls_devnet_id_hash",
+ VLAN_HASHSZ, mod_hash_null_valdtor);
+
+ /*
+ * Create a hash table, keyed by dd_spa.
+ */
+ i_dls_devnet_hash = mod_hash_create_extended("dls_devnet_hash",
+ VLAN_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
+ mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
+
+ devnet_need_rebuild = B_FALSE;
+}
+
+void
+dls_mgmt_fini(void)
+{
+ mod_hash_destroy_hash(i_dls_devnet_hash);
+ mod_hash_destroy_hash(i_dls_devnet_id_hash);
+ kmem_cache_destroy(i_dls_devnet_cachep);
+ rw_destroy(&i_dls_devnet_lock);
+ mutex_destroy(&i_dls_mgmt_lock);
+}
+
+int
+dls_mgmt_door_set(boolean_t start)
+{
+ int err;
+
+ /* handle daemon restart */
+ mutex_enter(&i_dls_mgmt_lock);
+ if (dls_mgmt_dh != NULL) {
+ door_ki_rele(dls_mgmt_dh);
+ dls_mgmt_dh = NULL;
+ }
+
+ if (start && ((err = door_ki_open(DLMGMT_DOOR, &dls_mgmt_dh)) != 0)) {
+ mutex_exit(&i_dls_mgmt_lock);
+ return (err);
+ }
+
+ mutex_exit(&i_dls_mgmt_lock);
+
+ /*
+ * Create and associate <link name, linkid> mapping for network devices
+ * which are already attached before the daemon is started.
+ */
+ if (start)
+ softmac_recreate();
+ return (0);
+}
+
+static boolean_t
+i_dls_mgmt_door_revoked(door_handle_t dh)
+{
+ struct door_info info;
+ extern int sys_shutdown;
+
+ ASSERT(dh != NULL);
+
+ if (sys_shutdown) {
+ cmn_err(CE_NOTE, "dls_mgmt_door: shutdown observed\n");
+ return (B_TRUE);
+ }
+
+ if (door_ki_info(dh, &info) != 0)
+ return (B_TRUE);
+
+ return ((info.di_attributes & DOOR_REVOKED) != 0);
+}
+
+/*
+ * Upcall to the datalink management daemon (dlmgmtd).
+ */
+static int
+i_dls_mgmt_upcall(void *arg, size_t asize, void *rbuf, size_t *rsizep)
+{
+ door_arg_t darg, save_arg;
+ struct dlmgmt_null_retval_s *retvalp;
+ door_handle_t dh;
+ int err = EINVAL;
+ int retry = 0;
+
+#define MAXRETRYNUM 3
+
+ ASSERT(arg);
+ darg.data_ptr = arg;
+ darg.data_size = asize;
+ darg.desc_ptr = NULL;
+ darg.desc_num = 0;
+ darg.rbuf = rbuf;
+ darg.rsize = *rsizep;
+ save_arg = darg;
+
+retry:
+ mutex_enter(&i_dls_mgmt_lock);
+ dh = dls_mgmt_dh;
+ if ((dh == NULL) || i_dls_mgmt_door_revoked(dh)) {
+ mutex_exit(&i_dls_mgmt_lock);
+ return (EBADF);
+ }
+ door_ki_hold(dh);
+ mutex_exit(&i_dls_mgmt_lock);
+
+ for (;;) {
+ retry++;
+ if ((err = door_ki_upcall(dh, &darg)) == 0)
+ break;
+
+ /*
+ * handle door call errors
+ */
+ darg = save_arg;
+ switch (err) {
+ case EINTR:
+ /*
+ * If the operation which caused this door upcall gets
+ * interrupted, return directly.
+ */
+ goto done;
+ case EAGAIN:
+ /*
+ * Repeat upcall if the maximum attempt limit has not
+ * been reached.
+ */
+ if (retry < MAXRETRYNUM) {
+ delay(2 * hz);
+ break;
+ }
+ cmn_err(CE_WARN, "dls: dlmgmtd fatal error %d\n", err);
+ goto done;
+ default:
+ /* A fatal door error */
+ if (i_dls_mgmt_door_revoked(dh)) {
+ cmn_err(CE_NOTE,
+ "dls: dlmgmtd door service revoked\n");
+
+ if (retry < MAXRETRYNUM) {
+ door_ki_rele(dh);
+ goto retry;
+ }
+ }
+ cmn_err(CE_WARN, "dls: dlmgmtd fatal error %d\n", err);
+ goto done;
+ }
+ }
+
+ if (darg.rbuf != rbuf) {
+ /*
+ * The size of the input rbuf was not big enough, so the
+ * upcall allocated the rbuf itself. If this happens, assume
+ * that this was an invalid door call request.
+ */
+ kmem_free(darg.rbuf, darg.rsize);
+ err = ENOSPC;
+ goto done;
+ }
+
+ if (darg.rsize > *rsizep || darg.rsize < sizeof (uint_t)) {
+ err = EINVAL;
+ goto done;
+ }
+
+ /* LINTED E_BAD_PTR_CAST_ALIGN */
+ retvalp = (struct dlmgmt_null_retval_s *)darg.rbuf;
+ if (retvalp->lr_err != 0) {
+ err = retvalp->lr_err;
+ goto done;
+ }
+
+ *rsizep = darg.rsize;
+
+done:
+ door_ki_rele(dh);
+ return (err);
+}
+
+/*
+ * Request the datalink management daemon to create a link with the attributes
+ * below. Upon success, zero is returned and linkidp contains the linkid for
+ * the new link; otherwise, an errno is returned.
+ *
+ * - dev physical dev_t. required for all physical links,
+ * including GLDv3 links. It will be used to force the
+ * attachment of a physical device, hence the
+ * registration of its mac
+ * - class datalink class
+ * - media type media type; DL_OTHER means unknown
+ * - vid VLAN ID (for VLANs)
+ * - persist whether to persist the datalink
+ */
+int
+dls_mgmt_create(const char *devname, dev_t dev, datalink_class_t class,
+ uint32_t media, boolean_t persist, datalink_id_t *linkidp)
+{
+ dlmgmt_upcall_arg_create_t create;
+ dlmgmt_create_retval_t retval;
+ size_t rsize;
+ int err;
+
+ create.ld_cmd = DLMGMT_CMD_DLS_CREATE;
+ create.ld_class = class;
+ create.ld_media = media;
+ create.ld_phymaj = getmajor(dev);
+ create.ld_phyinst = getminor(dev);
+ create.ld_persist = persist;
+ if (strlcpy(create.ld_devname, devname, MAXNAMELEN) >= MAXNAMELEN)
+ return (EINVAL);
+
+ rsize = sizeof (retval);
+
+ err = i_dls_mgmt_upcall(&create, sizeof (create), &retval, &rsize);
+ if (err == 0)
+ *linkidp = retval.lr_linkid;
+ return (err);
+}
+
+/*
+ * Request the datalink management daemon to destroy the specified link.
+ * Returns zero upon success, or an errno upon failure.
+ */
+int
+dls_mgmt_destroy(datalink_id_t linkid, boolean_t persist)
+{
+ dlmgmt_upcall_arg_destroy_t destroy;
+ dlmgmt_destroy_retval_t retval;
+ size_t rsize;
+
+ destroy.ld_cmd = DLMGMT_CMD_DLS_DESTROY;
+ destroy.ld_linkid = linkid;
+ destroy.ld_persist = persist;
+ rsize = sizeof (retval);
+
+ return (i_dls_mgmt_upcall(&destroy, sizeof (destroy), &retval, &rsize));
+}
+
+/*
+ * Request the datalink management daemon to verify/update the information
+ * for a physical link. Upon success, get its linkid.
+ *
+ * - media type media type
+ * - novanity whether this physical datalink supports vanity naming.
+ * physical links that do not use the GLDv3 MAC plugin
+ * cannot suport vanity naming
+ *
+ * This function could fail with ENOENT or EEXIST. Two cases return EEXIST:
+ *
+ * 1. A link with devname already exists, but the media type does not match.
+ * In this case, mediap will bee set to the media type of the existing link.
+ * 2. A link with devname already exists, but its link name does not match
+ * the device name, although this link does not support vanity naming.
+ */
+int
+dls_mgmt_update(const char *devname, uint32_t media, boolean_t novanity,
+ uint32_t *mediap, datalink_id_t *linkidp)
+{
+ dlmgmt_upcall_arg_update_t update;
+ dlmgmt_update_retval_t retval;
+ size_t rsize;
+ int err;
+
+ update.ld_cmd = DLMGMT_CMD_DLS_UPDATE;
+
+ if (strlcpy(update.ld_devname, devname, MAXNAMELEN) >= MAXNAMELEN)
+ return (EINVAL);
+
+ update.ld_media = media;
+ update.ld_novanity = novanity;
+ rsize = sizeof (retval);
+
+ err = i_dls_mgmt_upcall(&update, sizeof (update), &retval, &rsize);
+ if (err == EEXIST) {
+ *linkidp = retval.lr_linkid;
+ *mediap = retval.lr_media;
+ } else if (err == 0) {
+ *linkidp = retval.lr_linkid;
+ }
+
+ return (err);
+}
+
+/*
+ * Request the datalink management daemon to get the information for a link.
+ * Returns zero upon success, or an errno upon failure.
+ *
+ * Only fills in information for argument pointers that are non-NULL.
+ * Note that the link argument is expected to be MAXLINKNAMELEN bytes.
+ */
+int
+dls_mgmt_get_linkinfo(datalink_id_t linkid, char *link,
+ datalink_class_t *classp, uint32_t *mediap, uint32_t *flagsp)
+{
+ dlmgmt_door_getname_t getname;
+ dlmgmt_getname_retval_t retval;
+ size_t rsize;
+ int err, len;
+
+ getname.ld_cmd = DLMGMT_CMD_GETNAME;
+ getname.ld_linkid = linkid;
+ rsize = sizeof (retval);
+
+ err = i_dls_mgmt_upcall(&getname, sizeof (getname), &retval, &rsize);
+ if (err != 0)
+ return (err);
+
+ len = strlen(retval.lr_link);
+ if (len <= 1 || len >= MAXLINKNAMELEN)
+ return (EINVAL);
+
+ if (link != NULL)
+ (void) strlcpy(link, retval.lr_link, MAXLINKNAMELEN);
+ if (classp != NULL)
+ *classp = retval.lr_class;
+ if (mediap != NULL)
+ *mediap = retval.lr_media;
+ if (flagsp != NULL)
+ *flagsp = retval.lr_flags;
+ return (0);
+}
+
+/*
+ * Request the datalink management daemon to get the linkid for a link.
+ * Returns a non-zero error code on failure. The linkid argument is only
+ * set on success (when zero is returned.)
+ */
+int
+dls_mgmt_get_linkid(const char *link, datalink_id_t *linkid)
+{
+ dlmgmt_door_getlinkid_t getlinkid;
+ dlmgmt_getlinkid_retval_t retval;
+ size_t rsize;
+ int err;
+
+ getlinkid.ld_cmd = DLMGMT_CMD_GETLINKID;
+ (void) strlcpy(getlinkid.ld_link, link, MAXLINKNAMELEN);
+ rsize = sizeof (retval);
+
+ err = i_dls_mgmt_upcall(&getlinkid, sizeof (getlinkid), &retval,
+ &rsize);
+ if (err == 0)
+ *linkid = retval.lr_linkid;
+ return (err);
+}
+
+datalink_id_t
+dls_mgmt_get_next(datalink_id_t linkid, datalink_class_t class,
+ datalink_media_t dmedia, uint32_t flags)
+{
+ dlmgmt_door_getnext_t getnext;
+ dlmgmt_getnext_retval_t retval;
+ size_t rsize;
+
+ getnext.ld_cmd = DLMGMT_CMD_GETNEXT;
+ getnext.ld_class = class;
+ getnext.ld_dmedia = dmedia;
+ getnext.ld_flags = flags;
+ getnext.ld_linkid = linkid;
+ rsize = sizeof (retval);
+
+ if (i_dls_mgmt_upcall(&getnext, sizeof (getnext), &retval, &rsize) != 0)
+ return (DATALINK_INVALID_LINKID);
+
+ return (retval.lr_linkid);
+}
+
+static int
+i_dls_mgmt_get_linkattr(const datalink_id_t linkid, const char *attr,
+ void *attrval, size_t *attrszp)
+{
+ dlmgmt_upcall_arg_getattr_t getattr;
+ dlmgmt_getattr_retval_t *retvalp;
+ size_t oldsize, size;
+ int err;
+
+ getattr.ld_cmd = DLMGMT_CMD_DLS_GETATTR;
+ getattr.ld_linkid = linkid;
+ (void) strlcpy(getattr.ld_attr, attr, MAXLINKATTRLEN);
+
+ oldsize = size = *attrszp + sizeof (dlmgmt_getattr_retval_t) - 1;
+ retvalp = kmem_zalloc(oldsize, KM_SLEEP);
+
+ err = i_dls_mgmt_upcall(&getattr, sizeof (getattr), retvalp, &size);
+ if (err == 0) {
+ ASSERT(size <= oldsize);
+ *attrszp = size + 1 - sizeof (dlmgmt_getattr_retval_t);
+ bcopy(retvalp->lr_attr, attrval, *attrszp);
+ }
+
+ kmem_free(retvalp, oldsize);
+ return (err);
+}
+
+/*
+ * Note that this function can only get devp successfully for non-VLAN link.
+ */
+int
+dls_mgmt_get_phydev(datalink_id_t linkid, dev_t *devp)
+{
+ uint64_t maj, inst;
+ size_t attrsz = sizeof (uint64_t);
+
+ if (i_dls_mgmt_get_linkattr(linkid, FPHYMAJ, &maj, &attrsz) != 0 ||
+ attrsz != sizeof (uint64_t) ||
+ i_dls_mgmt_get_linkattr(linkid, FPHYINST, &inst, &attrsz) != 0 ||
+ attrsz != sizeof (uint64_t)) {
+ return (EINVAL);
+ }
+
+ *devp = makedevice((major_t)maj, (minor_t)inst);
+ return (0);
+}
+
+/*
+ * Hold the vanity naming structure (dls_devnet_t) temporarily. The request to
+ * delete the dls_devnet_t will wait until the temporary reference is released.
+ */
+int
+dls_devnet_hold_tmp(datalink_id_t linkid, dls_dl_handle_t *ddhp)
+{
+ dls_devnet_t *ddp;
+ dls_dev_handle_t ddh = NULL;
+ dev_t phydev = 0;
+ int err;
+
+ /*
+ * Hold this link to prevent it being detached (if physical link).
+ */
+ if (dls_mgmt_get_phydev(linkid, &phydev) == 0)
+ (void) softmac_hold_device(phydev, &ddh);
+
+ rw_enter(&i_dls_devnet_lock, RW_READER);
+ if ((err = mod_hash_find(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)linkid, (mod_hash_val_t *)&ddp)) != 0) {
+ ASSERT(err == MH_ERR_NOTFOUND);
+ rw_exit(&i_dls_devnet_lock);
+ softmac_rele_device(ddh);
+ return (ENOENT);
+ }
+
+ /*
+ * At least one reference was held when this datalink was created.
+ */
+ ASSERT(ddp->dd_ref > 0);
+ mutex_enter(&ddp->dd_mutex);
+ ddp->dd_tref++;
+ mutex_exit(&ddp->dd_mutex);
+ rw_exit(&i_dls_devnet_lock);
+ softmac_rele_device(ddh);
+
+done:
+ *ddhp = ddp;
+ return (0);
+}
+
+void
+dls_devnet_rele_tmp(dls_dl_handle_t dlh)
+{
+ dls_devnet_t *ddp = dlh;
+
+ mutex_enter(&ddp->dd_mutex);
+ ASSERT(ddp->dd_tref != 0);
+ if (--ddp->dd_tref == 0)
+ cv_signal(&ddp->dd_cv);
+ mutex_exit(&ddp->dd_mutex);
+}
+
+/*
+ * "link" kstats related functions.
+ */
+
+/*
+ * Query the "link" kstats.
+ */
+static int
+dls_devnet_stat_update(kstat_t *ksp, int rw)
+{
+ dls_devnet_t *ddp = ksp->ks_private;
+ dls_vlan_t *dvp;
+ int err;
+
+ err = dls_vlan_hold(ddp->dd_mac, ddp->dd_vid, &dvp, B_FALSE, B_FALSE);
+ if (err != 0)
+ return (err);
+
+ err = dls_stat_update(ksp, dvp, rw);
+ dls_vlan_rele(dvp);
+ return (err);
+}
+
+/*
+ * Create the "link" kstats.
+ */
+static void
+dls_devnet_stat_create(dls_devnet_t *ddp)
+{
+ char link[MAXLINKNAMELEN];
+ kstat_t *ksp;
+
+ if ((dls_mgmt_get_linkinfo(ddp->dd_vlanid, link,
+ NULL, NULL, NULL)) != 0) {
+ return;
+ }
+
+ if (dls_stat_create("link", 0, link, dls_devnet_stat_update,
+ ddp, &ksp) != 0) {
+ return;
+ }
+
+ ASSERT(ksp != NULL);
+ ddp->dd_ksp = ksp;
+}
+
+/*
+ * Destroy the "link" kstats.
+ */
+static void
+dls_devnet_stat_destroy(dls_devnet_t *ddp)
+{
+ if (ddp->dd_ksp == NULL)
+ return;
+
+ kstat_delete(ddp->dd_ksp);
+ ddp->dd_ksp = NULL;
+}
+
+/*
+ * The link has been renamed. Destroy the old non-legacy kstats ("link kstats")
+ * and create the new set using the new name.
+ */
+static void
+dls_devnet_stat_rename(dls_devnet_t *ddp, const char *link)
+{
+ kstat_t *ksp;
+
+ if (ddp->dd_ksp != NULL) {
+ kstat_delete(ddp->dd_ksp);
+ ddp->dd_ksp = NULL;
+ }
+
+ if (dls_stat_create("link", 0, link, dls_devnet_stat_update,
+ ddp, &ksp) != 0) {
+ return;
+ }
+
+ ASSERT(ksp != NULL);
+ ddp->dd_ksp = ksp;
+}
+
+/*
+ * Associate a linkid with a given link (identified by <macname/vid>)
+ *
+ * Several cases:
+ * a. implicit VLAN creation: (non-NULL "vlan")
+ * b. explicit VLAN creation: (NULL "vlan")
+ * c. explicit non-VLAN creation:
+ * (NULL "vlan" and linkid could be INVALID_LINKID if the physical device
+ * was created before the daemon was started)
+ */
+static int
+dls_devnet_set(const char *macname, uint16_t vid,
+ datalink_id_t vlan_linkid, datalink_id_t linkid, const char *vlan,
+ dls_devnet_t **ddpp)
+{
+ dls_devnet_t *ddp = NULL;
+ char spa[MAXSPALEN];
+ boolean_t explicit = (vlan == NULL);
+ datalink_class_t class;
+ int err;
+
+ ASSERT(vid != VLAN_ID_NONE || explicit);
+ ASSERT(vlan_linkid != DATALINK_INVALID_LINKID || !explicit ||
+ vid == VLAN_ID_NONE);
+
+ (void) snprintf(spa, MAXSPALEN, "%s/%d", macname, vid);
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ if ((err = mod_hash_find(i_dls_devnet_hash,
+ (mod_hash_key_t)spa, (mod_hash_val_t *)&ddp)) == 0) {
+ char link[MAXLINKNAMELEN];
+
+ if (explicit) {
+ if ((vid != VLAN_ID_NONE) ||
+ (ddp->dd_vlanid != DATALINK_INVALID_LINKID)) {
+ err = EEXIST;
+ goto done;
+ }
+
+ /*
+ * This might be a physical link that has already
+ * been created, but which does not have a vlan_linkid
+ * because dlmgmtd was not running when it was created.
+ */
+ if ((err = dls_mgmt_get_linkinfo(vlan_linkid, NULL,
+ &class, NULL, NULL)) != 0) {
+ goto done;
+ }
+
+ if (class != DATALINK_CLASS_PHYS) {
+ err = EINVAL;
+ goto done;
+ }
+
+ goto newphys;
+ }
+
+ /*
+ * Implicit VLAN, but the same name has already
+ * been associated with another linkid. Check if the name
+ * of that link matches the given VLAN name.
+ */
+ ASSERT(vid != VLAN_ID_NONE);
+ if ((err = dls_mgmt_get_linkinfo(ddp->dd_vlanid, link,
+ NULL, NULL, NULL)) != 0) {
+ goto done;
+ }
+
+ if (strcmp(link, vlan) != 0) {
+ err = EEXIST;
+ goto done;
+ }
+
+ /*
+ * This is not an implicit created VLAN any more, return
+ * this existing datalink.
+ */
+ ASSERT(ddp->dd_ref > 0);
+ ddp->dd_ref++;
+ goto done;
+ }
+
+ /*
+ * Request the daemon to create a new vlan_linkid for this implicitly
+ * created vlan.
+ */
+ if (!explicit && ((err = dls_mgmt_create(vlan, 0,
+ DATALINK_CLASS_VLAN, DL_ETHER, B_FALSE, &vlan_linkid)) != 0)) {
+ goto done;
+ }
+
+ ddp = kmem_cache_alloc(i_dls_devnet_cachep, KM_SLEEP);
+ ddp->dd_vid = vid;
+ ddp->dd_explicit = explicit;
+ ddp->dd_tref = 0;
+ ddp->dd_ref++;
+ ddp->dd_zid = GLOBAL_ZONEID;
+ (void) strncpy(ddp->dd_mac, macname, MAXNAMELEN);
+ (void) snprintf(ddp->dd_spa, MAXSPALEN, "%s/%d", macname, vid);
+ VERIFY(mod_hash_insert(i_dls_devnet_hash,
+ (mod_hash_key_t)ddp->dd_spa, (mod_hash_val_t)ddp) == 0);
+
+newphys:
+
+ ddp->dd_vlanid = vlan_linkid;
+ if (ddp->dd_vlanid != DATALINK_INVALID_LINKID) {
+ ddp->dd_linkid = linkid;
+
+ VERIFY(mod_hash_insert(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)vlan_linkid,
+ (mod_hash_val_t)ddp) == 0);
+ devnet_need_rebuild = B_TRUE;
+ dls_devnet_stat_create(ddp);
+ }
+ err = 0;
+done:
+ rw_exit(&i_dls_devnet_lock);
+ if (err == 0 && ddpp != NULL)
+ *ddpp = ddp;
+ return (err);
+}
+
+static void
+dls_devnet_unset_common(dls_devnet_t *ddp)
+{
+ mod_hash_val_t val;
+
+ ASSERT(RW_WRITE_HELD(&i_dls_devnet_lock));
+
+ ASSERT(ddp->dd_ref == 0);
+
+ /*
+ * Remove this dls_devnet_t from the hash table.
+ */
+ VERIFY(mod_hash_remove(i_dls_devnet_hash,
+ (mod_hash_key_t)ddp->dd_spa, &val) == 0);
+
+ if (ddp->dd_vlanid != DATALINK_INVALID_LINKID) {
+ VERIFY(mod_hash_remove(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)ddp->dd_vlanid, &val) == 0);
+
+ dls_devnet_stat_destroy(ddp);
+ devnet_need_rebuild = B_TRUE;
+ }
+
+ /*
+ * Wait until all temporary references are released.
+ */
+ mutex_enter(&ddp->dd_mutex);
+ while (ddp->dd_tref != 0)
+ cv_wait(&ddp->dd_cv, &ddp->dd_mutex);
+ mutex_exit(&ddp->dd_mutex);
+
+ if (!ddp->dd_explicit) {
+ ASSERT(ddp->dd_vid != VLAN_ID_NONE);
+ ASSERT(ddp->dd_vlanid != DATALINK_INVALID_LINKID);
+ (void) dls_mgmt_destroy(ddp->dd_vlanid, B_FALSE);
+ }
+
+ ddp->dd_vlanid = DATALINK_INVALID_LINKID;
+ ddp->dd_zid = GLOBAL_ZONEID;
+ ddp->dd_explicit = B_FALSE;
+ kmem_cache_free(i_dls_devnet_cachep, ddp);
+}
+
+/*
+ * Disassociate a linkid with a given link (identified by <macname/vid>)
+ */
+static int
+dls_devnet_unset(const char *macname, uint16_t vid, datalink_id_t *id)
+{
+ dls_devnet_t *ddp;
+ char spa[MAXSPALEN];
+ int err;
+
+ (void) snprintf(spa, MAXSPALEN, "%s/%d", macname, vid);
+
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ if ((err = mod_hash_find(i_dls_devnet_hash,
+ (mod_hash_key_t)spa, (mod_hash_val_t *)&ddp)) != 0) {
+ ASSERT(err == MH_ERR_NOTFOUND);
+ rw_exit(&i_dls_devnet_lock);
+ return (ENOENT);
+ }
+
+ ASSERT(ddp->dd_ref != 0);
+
+ if (ddp->dd_ref != 1) {
+ rw_exit(&i_dls_devnet_lock);
+ return (EBUSY);
+ }
+
+ ddp->dd_ref--;
+
+ if (id != NULL)
+ *id = ddp->dd_vlanid;
+
+ dls_devnet_unset_common(ddp);
+ rw_exit(&i_dls_devnet_lock);
+ return (0);
+}
+
+static int
+dls_devnet_hold(datalink_id_t linkid, dls_devnet_t **ddpp)
+{
+ dls_devnet_t *ddp;
+ dev_t phydev = 0;
+ dls_dev_handle_t ddh = NULL;
+ int err;
+
+ /*
+ * Hold this link to prevent it being detached in case of a
+ * physical link.
+ */
+ if (dls_mgmt_get_phydev(linkid, &phydev) == 0)
+ (void) softmac_hold_device(phydev, &ddh);
+
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ if ((err = mod_hash_find(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)linkid, (mod_hash_val_t *)&ddp)) != 0) {
+ ASSERT(err == MH_ERR_NOTFOUND);
+ rw_exit(&i_dls_devnet_lock);
+ softmac_rele_device(ddh);
+ return (ENOENT);
+ }
+
+ ASSERT(ddp->dd_ref > 0);
+ ddp->dd_ref++;
+ rw_exit(&i_dls_devnet_lock);
+ softmac_rele_device(ddh);
+
+done:
+ *ddpp = ddp;
+ return (0);
+}
+
+/*
+ * This funtion is called when a DLS client tries to open a device node.
+ * This dev_t could a result of a /dev/net node access (returned by
+ * devnet_create_rvp->dls_devnet_open()) or a direct /dev node access.
+ * In both cases, this function returns 0. In the first case, bump the
+ * reference count of the dls_devnet_t structure, so that it will not be
+ * freed when devnet_inactive_callback->dls_devnet_close() is called
+ * (Note that devnet_inactive_callback() is called right after dld_open,
+ * not when the /dev/net access is done). In the second case, ddhp would
+ * be NULL.
+ *
+ * To undo this function, call dls_devnet_close() in the first case, and call
+ * dls_vlan_rele() in the second case.
+ */
+int
+dls_devnet_open_by_dev(dev_t dev, dls_vlan_t **dvpp, dls_dl_handle_t *ddhp)
+{
+ dls_dev_handle_t ddh = NULL;
+ char spa[MAXSPALEN];
+ dls_devnet_t *ddp;
+ dls_vlan_t *dvp;
+ int err;
+
+ /*
+ * Hold this link to prevent it being detached in case of a
+ * GLDv3 physical link.
+ */
+ if (getminor(dev) - 1 < MAC_MAX_MINOR)
+ (void) softmac_hold_device(dev, &ddh);
+
+ /*
+ * Found the dls_vlan_t with the given dev.
+ */
+ err = dls_vlan_hold_by_dev(dev, &dvp);
+ softmac_rele_device(ddh);
+
+ if (err != 0)
+ return (err);
+
+ (void) snprintf(spa, MAXSPALEN, "%s/%d",
+ dvp->dv_dlp->dl_name, dvp->dv_id);
+
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ if ((err = mod_hash_find(i_dls_devnet_hash,
+ (mod_hash_key_t)spa, (mod_hash_val_t *)&ddp)) != 0) {
+ ASSERT(err == MH_ERR_NOTFOUND);
+ rw_exit(&i_dls_devnet_lock);
+ *ddhp = NULL;
+ *dvpp = dvp;
+ return (0);
+ }
+
+ ASSERT(ddp->dd_ref > 0);
+ ddp->dd_ref++;
+ rw_exit(&i_dls_devnet_lock);
+ *ddhp = ddp;
+ *dvpp = dvp;
+ return (0);
+}
+
+static void
+dls_devnet_rele(dls_devnet_t *ddp)
+{
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ ASSERT(ddp->dd_ref != 0);
+ if (--ddp->dd_ref != 0) {
+ rw_exit(&i_dls_devnet_lock);
+ return;
+ }
+ /*
+ * This should only happen for implicitly-created VLAN.
+ */
+ ASSERT(ddp->dd_vid != VLAN_ID_NONE);
+ dls_devnet_unset_common(ddp);
+ rw_exit(&i_dls_devnet_lock);
+}
+
+static int
+dls_devnet_hold_by_name(const char *link, dls_devnet_t **ddpp, zoneid_t zid)
+{
+ char link_under[MAXLINKNAMELEN];
+ char drv[MAXLINKNAMELEN];
+ uint_t ppa;
+ major_t major;
+ dev_t phy_dev, tmp_dev;
+ uint_t vid;
+ datalink_id_t linkid;
+ dls_devnet_t *ddp;
+ dls_dev_handle_t ddh;
+ int err;
+
+ if ((err = dls_mgmt_get_linkid(link, &linkid)) == 0)
+ return (dls_devnet_hold(linkid, ddpp));
+
+ /*
+ * If we failed to get the link's linkid because the dlmgmtd daemon
+ * has not been started, return ENOENT so that the application can
+ * fallback to open the /dev node.
+ */
+ if (err == EBADF)
+ return (ENOENT);
+
+ if (err != ENOENT)
+ return (err);
+
+ if (ddi_parse(link, drv, &ppa) != DDI_SUCCESS)
+ return (ENOENT);
+
+ if ((vid = DLS_PPA2VID(ppa)) > VLAN_ID_MAX)
+ return (ENOENT);
+
+ ppa = (uint_t)DLS_PPA2INST(ppa);
+ (void) snprintf(link_under, sizeof (link_under), "%s%d", drv, ppa);
+
+ if (vid != VLAN_ID_NONE) {
+ /*
+ * Only global zone can implicitly create a VLAN.
+ */
+ if (zid != GLOBAL_ZONEID)
+ return (ENOENT);
+
+ /*
+ * This is potentially an implicitly-created VLAN. Hold the
+ * link this VLAN is created on.
+ */
+ if (dls_mgmt_get_linkid(link_under, &linkid) == 0 &&
+ dls_devnet_hold_tmp(linkid, &ddp) == 0) {
+ if (ddp->dd_vid != VLAN_ID_NONE) {
+ dls_devnet_rele_tmp(ddp);
+ return (ENOENT);
+ }
+ goto implicit;
+ }
+ }
+
+ /*
+ * If this link (or the link that an implicit vlan is created on)
+ * (a) is a physical device, (b) this is the first boot, (c) the MAC
+ * is not registered yet, and (d) we cannot find its linkid, then the
+ * linkname is the same as the devname.
+ *
+ * First filter out invalid names.
+ */
+ if ((major = ddi_name_to_major(drv)) == (major_t)-1)
+ return (ENOENT);
+
+ phy_dev = makedevice(major, (minor_t)ppa + 1);
+ if (softmac_hold_device(phy_dev, &ddh) != 0)
+ return (ENOENT);
+
+ /*
+ * At this time, the MAC should be registered, check its phy_dev using
+ * the given name.
+ */
+ if ((err = dls_mgmt_get_linkid(link_under, &linkid)) != 0 ||
+ (err = dls_mgmt_get_phydev(linkid, &tmp_dev)) != 0) {
+ softmac_rele_device(ddh);
+ return (err);
+ }
+ if (tmp_dev != phy_dev) {
+ softmac_rele_device(ddh);
+ return (ENOENT);
+ }
+
+ if (vid == VLAN_ID_NONE) {
+ /*
+ * For non-VLAN, we are done.
+ */
+ err = dls_devnet_hold(linkid, ddpp);
+ softmac_rele_device(ddh);
+ return (err);
+ }
+
+ /*
+ * If this is an implicit VLAN, temporarily hold this non-VLAN.
+ */
+ VERIFY(dls_devnet_hold_tmp(linkid, &ddp) == 0);
+ softmac_rele_device(ddh);
+ ASSERT(ddp->dd_vid == VLAN_ID_NONE);
+
+ /*
+ * Again, this is potentially an implicitly-created VLAN.
+ */
+
+implicit:
+ ASSERT(vid != VLAN_ID_NONE);
+ err = dls_devnet_set(ddp->dd_mac, vid, DATALINK_INVALID_LINKID,
+ linkid, link, ddpp);
+ dls_devnet_rele_tmp(ddp);
+ return (err);
+}
+
+/*
+ * Get linkid for the given dev.
+ */
+int
+dls_devnet_dev2linkid(dev_t dev, datalink_id_t *linkidp)
+{
+ dls_vlan_t *dvp;
+ dls_devnet_t *ddp;
+ char spa[MAXSPALEN];
+ int err;
+
+ if ((err = dls_vlan_hold_by_dev(dev, &dvp)) != 0)
+ return (err);
+
+ (void) snprintf(spa, MAXSPALEN, "%s/%d",
+ dvp->dv_dlp->dl_name, dvp->dv_id);
+
+ rw_enter(&i_dls_devnet_lock, RW_READER);
+ if (mod_hash_find(i_dls_devnet_hash, (mod_hash_key_t)spa,
+ (mod_hash_val_t *)&ddp) != 0) {
+ rw_exit(&i_dls_devnet_lock);
+ dls_vlan_rele(dvp);
+ return (ENOENT);
+ }
+
+ *linkidp = ddp->dd_vlanid;
+ rw_exit(&i_dls_devnet_lock);
+ dls_vlan_rele(dvp);
+ return (0);
+}
+
+/*
+ * Get the link's physical dev_t. It this is a VLAN, get the dev_t of the
+ * link this VLAN is created on.
+ */
+int
+dls_devnet_phydev(datalink_id_t vlanid, dev_t *devp)
+{
+ dls_devnet_t *ddp;
+ int err;
+
+ if ((err = dls_devnet_hold_tmp(vlanid, &ddp)) != 0)
+ return (err);
+
+ err = dls_mgmt_get_phydev(ddp->dd_linkid, devp);
+ dls_devnet_rele_tmp(ddp);
+ return (err);
+}
+
+/*
+ * Handle the renaming requests. There are two rename cases:
+ *
+ * 1. Request to rename a valid link (id1) to an non-existent link name
+ * (id2). In this case id2 is DATALINK_INVALID_LINKID. Just check whether
+ * id1 is held by any applications.
+ *
+ * In this case, the link's kstats need to be updated using the given name.
+ *
+ * 2. Request to rename a valid link (id1) to the name of a REMOVED
+ * physical link (id2). In this case, check htat id1 and its associated
+ * mac is not held by any application, and update the link's linkid to id2.
+ *
+ * This case does not change the <link name, linkid> mapping, so the link's
+ * kstats need to be updated with using name associated the given id2.
+ */
+int
+dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link)
+{
+ dls_dev_handle_t ddh = NULL;
+ char linkname[MAXLINKNAMELEN];
+ int err = 0;
+ dev_t phydev = 0;
+ dls_devnet_t *ddp;
+ mac_handle_t mh;
+ mod_hash_val_t val;
+
+ /*
+ * In the second case, id2 must be a REMOVED physical link.
+ */
+ if ((id2 != DATALINK_INVALID_LINKID) &&
+ (dls_mgmt_get_phydev(id2, &phydev) == 0) &&
+ softmac_hold_device(phydev, &ddh) == 0) {
+ softmac_rele_device(ddh);
+ return (EEXIST);
+ }
+
+ /*
+ * Hold id1 to prevent it from being detached (if a physical link).
+ */
+ if (dls_mgmt_get_phydev(id1, &phydev) == 0)
+ (void) softmac_hold_device(phydev, &ddh);
+
+ rw_enter(&i_dls_devnet_lock, RW_WRITER);
+ if ((err = mod_hash_find(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)id1, (mod_hash_val_t *)&ddp)) != 0) {
+ ASSERT(err == MH_ERR_NOTFOUND);
+ err = ENOENT;
+ goto done;
+ }
+
+ /*
+ * Return EBUSY if any applications have this link open.
+ */
+ if ((ddp->dd_explicit && ddp->dd_ref > 1) ||
+ (!ddp->dd_explicit && ddp->dd_ref > 0)) {
+ err = EBUSY;
+ goto done;
+ }
+
+ if (id2 == DATALINK_INVALID_LINKID) {
+ (void) strlcpy(linkname, link, sizeof (linkname));
+ goto done;
+ }
+
+ /*
+ * The second case, check whether the MAC is used by any MAC
+ * user. This must be a physical link so ddh must not be NULL.
+ */
+ if (ddh == NULL) {
+ err = EINVAL;
+ goto done;
+ }
+
+ if ((err = mac_open(ddp->dd_mac, &mh)) != 0)
+ goto done;
+
+ /*
+ * We release the reference of the MAC which mac_open() is
+ * holding. Note that this mac will not be unregistered
+ * because the physical device is hold.
+ */
+ mac_close(mh);
+
+ /*
+ * Check if there is any other MAC clients, if not, hold this mac
+ * exclusively until we are done.
+ */
+ if ((err = mac_hold_exclusive(mh)) != 0)
+ goto done;
+
+ /*
+ * Update the link's linkid.
+ */
+ if ((err = mod_hash_find(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)id2, &val)) != MH_ERR_NOTFOUND) {
+ mac_rele_exclusive(mh);
+ err = EEXIST;
+ goto done;
+ }
+
+ err = dls_mgmt_get_linkinfo(id2, linkname, NULL, NULL, NULL);
+ if (err != 0) {
+ mac_rele_exclusive(mh);
+ goto done;
+ }
+
+ (void) mod_hash_remove(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)id1, &val);
+
+ ddp->dd_vlanid = id2;
+ (void) mod_hash_insert(i_dls_devnet_id_hash,
+ (mod_hash_key_t)(uintptr_t)ddp->dd_vlanid, (mod_hash_val_t)ddp);
+
+ mac_rele_exclusive(mh);
+
+done:
+ /*
+ * Change the name of the kstat based on the new link name.
+ */
+ if (err == 0)
+ dls_devnet_stat_rename(ddp, linkname);
+
+ rw_exit(&i_dls_devnet_lock);
+ softmac_rele_device(ddh);
+ return (err);
+}
+
+int
+dls_devnet_setzid(const char *link, zoneid_t zid)
+{
+ dls_devnet_t *ddp;
+ int err;
+ zoneid_t old_zid;
+
+ if ((err = dls_devnet_hold_by_name(link, &ddp, GLOBAL_ZONEID)) != 0)
+ return (err);
+
+ mutex_enter(&ddp->dd_zid_mutex);
+ if ((old_zid = ddp->dd_zid) == zid) {
+ mutex_exit(&ddp->dd_zid_mutex);
+ dls_devnet_rele(ddp);
+ return (0);
+ }
+
+ if ((err = dls_vlan_setzid(ddp->dd_mac, ddp->dd_vid, zid)) != 0) {
+ mutex_exit(&ddp->dd_zid_mutex);
+ dls_devnet_rele(ddp);
+ return (err);
+ }
+
+ ddp->dd_zid = zid;
+ devnet_need_rebuild = B_TRUE;
+ mutex_exit(&ddp->dd_zid_mutex);
+
+ /*
+ * Keep this open reference only if it belonged to the global zone
+ * and is now assigned to a non-global zone.
+ */
+ if (old_zid != GLOBAL_ZONEID || zid == GLOBAL_ZONEID)
+ dls_devnet_rele(ddp);
+
+ /*
+ * Then release this link if it belonged to an non-global zone
+ * but is now assigned back to the global zone.
+ */
+ if (old_zid != GLOBAL_ZONEID && zid == GLOBAL_ZONEID)
+ dls_devnet_rele(ddp);
+
+ return (0);
+}
+
+int
+dls_devnet_getzid(datalink_id_t linkid, zoneid_t *zidp)
+{
+ dls_devnet_t *ddp;
+ int err;
+
+ if ((err = dls_devnet_hold_tmp(linkid, &ddp)) != 0)
+ return (err);
+
+ mutex_enter(&ddp->dd_zid_mutex);
+ *zidp = ddp->dd_zid;
+ mutex_exit(&ddp->dd_zid_mutex);
+
+ dls_devnet_rele_tmp(ddp);
+ return (0);
+}
+
+/*
+ * Access a vanity naming node.
+ */
+int
+dls_devnet_open(const char *link, dls_dl_handle_t *dhp, dev_t *devp)
+{
+ dls_devnet_t *ddp;
+ dls_vlan_t *dvp;
+ zoneid_t zid = getzoneid();
+ int err;
+
+ if ((err = dls_devnet_hold_by_name(link, &ddp, zid)) != 0)
+ return (err);
+
+ /*
+ * Opening a link that does not belong to the current non-global zone
+ * is not allowed.
+ */
+ if (zid != GLOBAL_ZONEID && ddp->dd_zid != zid) {
+ dls_devnet_rele(ddp);
+ return (ENOENT);
+ }
+
+ err = dls_vlan_hold(ddp->dd_mac, ddp->dd_vid, &dvp, B_FALSE, B_TRUE);
+ if (err != 0) {
+ dls_devnet_rele(ddp);
+ return (err);
+ }
+
+ *dhp = ddp;
+ *devp = dvp->dv_dev;
+ return (0);
+}
+
+/*
+ * Close access to a vanity naming node.
+ */
+void
+dls_devnet_close(dls_dl_handle_t dlh)
+{
+ dls_devnet_t *ddp = dlh;
+ dls_vlan_t *dvp;
+
+ /*
+ * The VLAN is hold in dls_open_devnet_link().
+ */
+ VERIFY((dls_vlan_hold(ddp->dd_mac, ddp->dd_vid, &dvp, B_FALSE,
+ B_FALSE)) == 0);
+ dls_vlan_rele(dvp);
+ dls_vlan_rele(dvp);
+ dls_devnet_rele(ddp);
+}
+
+/*
+ * This is used by /dev/net to rebuild the nodes for readdir(). It is not
+ * critical and no protection is needed.
+ */
+boolean_t
+dls_devnet_rebuild()
+{
+ boolean_t updated = devnet_need_rebuild;
+
+ devnet_need_rebuild = B_FALSE;
+ return (updated);
+}
+
+int
+dls_devnet_create(mac_handle_t mh, datalink_id_t linkid)
+{
+ int err;
+
+ if ((err = dls_vlan_create(mac_name(mh), 0, B_FALSE)) != 0)
+ return (err);
+
+ err = dls_devnet_set(mac_name(mh), 0, linkid, linkid, NULL, NULL);
+ if (err != 0)
+ (void) dls_vlan_destroy(mac_name(mh), 0);
+
+ return (err);
+}
+
+/*
+ * Set the linkid of the dls_devnet_t and add it into the i_dls_devnet_id_hash.
+ * This is called in the case that the dlmgmtd daemon is started later than
+ * the physical devices get attached, and the linkid is only known after the
+ * daemon starts.
+ */
+int
+dls_devnet_recreate(mac_handle_t mh, datalink_id_t linkid)
+{
+ ASSERT(linkid != DATALINK_INVALID_LINKID);
+ return (dls_devnet_set(mac_name(mh), 0, linkid, linkid, NULL, NULL));
+}
+
+int
+dls_devnet_destroy(mac_handle_t mh, datalink_id_t *idp)
+{
+ int err;
+
+ *idp = DATALINK_INVALID_LINKID;
+ err = dls_devnet_unset(mac_name(mh), 0, idp);
+ if (err != 0 && err != ENOENT)
+ return (err);
+
+ if ((err = dls_vlan_destroy(mac_name(mh), 0)) == 0)
+ return (0);
+
+ (void) dls_devnet_set(mac_name(mh), 0, *idp, *idp, NULL, NULL);
+ return (err);
+}
+
+int
+dls_devnet_create_vlan(datalink_id_t vlanid, datalink_id_t linkid,
+ uint16_t vid, boolean_t force)
+{
+ dls_devnet_t *lnddp, *ddp;
+ dls_vlan_t *dvp;
+ int err;
+
+ /*
+ * Hold the link the VLAN is being created on (which must not be a
+ * VLAN).
+ */
+ ASSERT(vid != VLAN_ID_NONE);
+ if ((err = dls_devnet_hold_tmp(linkid, &lnddp)) != 0)
+ return (err);
+
+ if (lnddp->dd_vid != VLAN_ID_NONE) {
+ err = EINVAL;
+ goto done;
+ }
+
+ /*
+ * A new link.
+ */
+ err = dls_devnet_set(lnddp->dd_mac, vid, vlanid, linkid, NULL, &ddp);
+ if (err != 0)
+ goto done;
+
+ /*
+ * Hold the dls_vlan_t (and create it if needed).
+ */
+ err = dls_vlan_hold(ddp->dd_mac, ddp->dd_vid, &dvp, force, B_TRUE);
+ if (err != 0)
+ VERIFY(dls_devnet_unset(lnddp->dd_mac, vid, NULL) == 0);
+
+done:
+ dls_devnet_rele_tmp(lnddp);
+ return (err);
+}
+
+int
+dls_devnet_destroy_vlan(datalink_id_t vlanid)
+{
+ char macname[MAXNAMELEN];
+ uint16_t vid;
+ dls_devnet_t *ddp;
+ dls_vlan_t *dvp;
+ int err;
+
+ if ((err = dls_devnet_hold_tmp(vlanid, &ddp)) != 0)
+ return (err);
+
+ if (ddp->dd_vid == VLAN_ID_NONE) {
+ dls_devnet_rele_tmp(ddp);
+ return (EINVAL);
+ }
+
+ if (!ddp->dd_explicit) {
+ dls_devnet_rele_tmp(ddp);
+ return (EBUSY);
+ }
+
+ (void) strncpy(macname, ddp->dd_mac, MAXNAMELEN);
+ vid = ddp->dd_vid;
+
+ /*
+ * It is safe to release the temporary reference we just held, as the
+ * reference from VLAN creation is still held.
+ */
+ dls_devnet_rele_tmp(ddp);
+
+ if ((err = dls_devnet_unset(macname, vid, NULL)) != 0)
+ return (err);
+
+ /*
+ * This VLAN has already been held as the result of VLAN creation.
+ */
+ VERIFY(dls_vlan_hold(macname, vid, &dvp, B_FALSE, B_FALSE) == 0);
+
+ /*
+ * Release the reference which was held when this VLAN was created,
+ * and the reference which was just held.
+ */
+ dls_vlan_rele(dvp);
+ dls_vlan_rele(dvp);
+ return (0);
+}
+
+const char *
+dls_devnet_mac(dls_dl_handle_t ddh)
+{
+ return (ddh->dd_mac);
+}
+
+uint16_t
+dls_devnet_vid(dls_dl_handle_t ddh)
+{
+ return (ddh->dd_vid);
+}
+
+datalink_id_t
+dls_devnet_linkid(dls_dl_handle_t ddh)
+{
+ return (ddh->dd_linkid);
+}
+
+boolean_t
+dls_devnet_is_explicit(dls_dl_handle_t ddh)
+{
+ return (ddh->dd_explicit);
+}