--- old/usr/src/cmd/devfsadm/misc_link.c	Fri Jun 12 16:14:56 2020
+++ new/usr/src/cmd/devfsadm/misc_link.c	Fri Jun 12 16:14:56 2020
@@ -60,8 +60,9 @@
 static int glvc(di_minor_t minor, di_node_t node);
 static int ses_callback(di_minor_t minor, di_node_t node);
 static int kmdrv_create(di_minor_t minor, di_node_t node);
+static int feth_device(di_minor_t minor, di_node_t node);
 
-static devfsadm_create_t misc_cbt[] = {
+	static devfsadm_create_t misc_cbt[] = {
 	{ "pseudo", "ddi_pseudo", "(^sad$)",
 	    TYPE_EXACT | DRV_RE, ILEVEL_0, node_slash_minor
 	},
@@ -86,6 +87,9 @@
 	{ "pseudo", "ddi_pseudo", "(^winlock$)|(^pm$)",
 	    TYPE_EXACT | DRV_RE, ILEVEL_0, node_name
 	},
+	{ "pseudo", "ddi_pseudo", "feth",
+	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, feth_device
+	},
 	{ "pseudo", "ddi_pseudo", "conskbd",
 	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, conskbd
 	},
@@ -257,6 +261,9 @@
 	},
 	{ "pseudo", "^sctp|sctp6$",
 	    RM_PRE | RM_ALWAYS, ILEVEL_0, devfsadm_rm_link
+	},
+	{ "pseudo", "^feth/.*[0-9]+$",
+	    RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0, devfsadm_rm_all
 	}
 };
 
@@ -434,6 +441,19 @@
 	(void) devfsadm_mklink(buf, node, minor, 0);
 
 	return (DEVFSADM_CONTINUE);
+}
+
+static int
+feth_device(di_minor_t minor, di_node_t node)
+{  
+	char l_path[PATH_MAX + 1];
+        char *name = di_minor_name(minor);
+
+        (void) strcpy(l_path, "feth/");
+        (void) strcat(l_path, name);
+        (void) devfsadm_mklink(l_path, node, minor, 0);
+
+	return (DEVFSADM_CONTINUE);
 }
 
 static int
--- old/usr/src/cmd/dladm/dladm.c	Fri Jun 12 16:14:56 2020
+++ new/usr/src/cmd/dladm/dladm.c	Fri Jun 12 16:14:56 2020
@@ -62,6 +62,7 @@
 #include <libdlether.h>
 #include <libdliptun.h>
 #include <libdlsim.h>
+#include <libdlfeth.h>
 #include <libdlbridge.h>
 #include <libinetutil.h>
 #include <libvrrpadm.h>
@@ -217,6 +218,7 @@
 static cmdfunc_t do_create_etherstub, do_delete_etherstub, do_show_etherstub;
 static cmdfunc_t do_create_simnet, do_modify_simnet;
 static cmdfunc_t do_delete_simnet, do_show_simnet, do_up_simnet;
+static cmdfunc_t do_create_feth, do_delete_feth, do_show_feth, do_up_feth;
 static cmdfunc_t do_show_usage;
 static cmdfunc_t do_create_bridge, do_modify_bridge, do_delete_bridge;
 static cmdfunc_t do_add_bridge, do_remove_bridge, do_show_bridge;
@@ -377,6 +379,15 @@
 	{ "delete-simnet",	do_delete_simnet,	NULL		},
 	{ "show-simnet",	do_show_simnet,		NULL		},
 	{ "up-simnet",		do_up_simnet,		NULL		},
+	{ "create-feth",	do_create_feth,
+	    "    create-feth      [-t] [-s] [-R <root-dir>] [-m <mac address>] "
+		    "<feth-link>" },
+	{ "delete-feth",	do_delete_feth,		
+	    "    delete-feth      [-R <root-dir>] <feth-link>" },
+	{ "show-feth",		do_show_feth,
+	    "    show-feth        [-p] [-o <field>,...] [-P] [<feth-link>]" },
+	{ "up-feth",		do_up_feth,
+	    "    up-feth          <feth-link>\n" },
 	{ "create-bridge",	do_create_bridge,
 	    "    create-bridge    [-R <root-dir>] [-P <protect>] "
 	    "[-p <priority>]\n"
@@ -563,6 +574,12 @@
 	{ NULL, 0, NULL, 0 }
 };
 
+static const struct option feth_lopts[] = {
+	{"temporary",   no_argument,0, 't'  },
+	{"root-dir",    required_argument,      0, 'R'  },
+	{ NULL, 0, NULL, 0 }
+};
+
 static const struct option bridge_lopts[] = {
 	{ "protect",		required_argument,	0, 'P' },
 	{ "root-dir",		required_argument,	0, 'R'	},
@@ -1112,6 +1129,24 @@
 ;
 
 /*
+ * structures for 'dladm show-feth'
+ */
+
+typedef struct feth_fields_buf_s
+{
+	char feth_name[DLPI_LINKNAME_MAX];
+	char feth_macaddr[18];
+} feth_fields_buf_t;
+
+static const ofmt_field_t feth_fields[] = {
+	{ "LINK",12,
+		offsetof(feth_fields_buf_t, feth_name), print_default_cb},
+	{ "MACADDRESS",18,
+		offsetof(feth_fields_buf_t, feth_macaddr), print_default_cb},
+	{ NULL,0, 0, NULL}}
+;
+
+/*
  * structures for 'dladm show-usage'
  */
 
@@ -2786,6 +2821,11 @@
 				(void) strcpy(lbuf->link_over, "?");
 		}
 		break;
+	    }
+	
+	case DATALINK_CLASS_FETH: {
+		(void) strcpy(lbuf->link_over, "--");
+		break;
 	}
 	}
 }
@@ -5642,7 +5682,253 @@
 	ofmt_close(ofmt);
 }
 
+/* ARGSUSED */
 static void
+do_up_feth(int argc, char *argv[], const char *use)
+{
+	(void) dladm_feth_up(handle, DATALINK_ALL_LINKID, 0);
+}
+
+static void
+do_create_feth(int argc, char *argv[], const char *use)
+{
+	uint32_t flags;
+	char *altroot = NULL;
+	int option;
+	dladm_status_t status;
+	char name[MAXLINKNAMELEN];
+	boolean_t	mac_provided = B_FALSE;
+	boolean_t	mac_fixed;
+	uint8_t			mac_addr[ETHERADDRL];
+	
+	name[0] = '\0';
+	flags = DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST;
+
+	opterr = 0;
+	while ((option = getopt_long(argc, argv, ":tsR:m:",
+					feth_lopts, NULL)) != -1) {
+		switch (option) {
+			case 't':
+				flags &= ~DLADM_OPT_PERSIST;
+				break;
+			case 's':
+				flags |= FETH_FLAG_SINGLESHOT;
+				break;
+			case 'R':
+				altroot = optarg;
+				break;
+                case 'm':
+                        if (mac_provided == B_TRUE)
+                                die("cannot specify -m option twice");
+
+                        if (!dladm_aggr_str2macaddr(optarg, &mac_fixed, mac_addr)) {
+				die("invalid MAC address");
+                        }
+			mac_provided = B_TRUE;
+			printf("have mac address\n");
+                        break;
+			default:
+				die_opterr(optopt, option, use);
+		}
+	}
+
+	/* the simnet id is the required operand */
+	if (optind != (argc - 1))
+		usage();
+
+	if (strlcpy(name, argv[optind], MAXLINKNAMELEN) >= MAXLINKNAMELEN)
+		die("link name too long '%s'", argv[optind]);
+
+	if (!dladm_valid_linkname(name))
+		die("invalid link name '%s'", name);
+
+	if (altroot != NULL)
+		altroot_cmd(altroot, argc, argv);
+
+	if(mac_provided)
+		status = dladm_feth_create_mac(handle, name, flags, mac_addr);
+	else
+		status = dladm_feth_create(handle, name, flags);
+	if (status != DLADM_STATUS_OK)
+		die_dlerr(status, "feth creation failed");
+}
+
+static void
+do_delete_feth(int argc, char *argv[], const char *use)
+{
+	int option;
+	uint32_t flags = DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST;
+	datalink_id_t linkid;
+	char *altroot = NULL;
+	dladm_status_t status;
+	dladm_feth_attr_t flinfo;
+
+	opterr = 0;
+	while ((option = getopt_long(argc, argv, ":R:", feth_lopts,
+					NULL)) != -1) {
+		switch (option) {
+			case 'R':
+				altroot = optarg;
+				break;
+			default:
+				die_opterr(optopt, option, use);
+		}
+	}
+
+	/* get feth name (required last argument) */
+	if (optind != (argc - 1))
+		usage();
+
+	if (!dladm_valid_linkname(argv[optind]))
+		die("invalid link name '%s'", argv[optind]);
+
+	if (altroot != NULL)
+		altroot_cmd(altroot, argc, argv);
+
+	status = dladm_name2info(handle, argv[optind], &linkid, NULL, NULL,
+			NULL);
+	if (status != DLADM_STATUS_OK)
+		die("feth '%s' not found", argv[optind]);
+
+	if ((status = dladm_feth_info(handle, linkid, &flinfo,
+					flags)) != DLADM_STATUS_OK)
+		die_dlerr(status, "failed to retrieve feth information");
+	
+	status = dladm_feth_delete(handle, linkid, flags);
+	if (status != DLADM_STATUS_OK)
+		die_dlerr(status, "feth deletion failed");
+}
+
+static dladm_status_t
+print_feth(show_state_t *state, datalink_id_t linkid)
+{
+	dladm_feth_attr_t     flinfo;
+	uint32_t flags;
+	dladm_status_t status;
+	feth_fields_buf_t     flbuf;
+	char mstr[ETHERADDRL * 3];
+
+	bzero(&flbuf, sizeof (flbuf));
+	if ((status = dladm_datalink_id2info(handle, linkid, &flags, NULL, NULL,
+					flbuf.feth_name, sizeof (flbuf.feth_name)))
+			!= DLADM_STATUS_OK)
+		return (status);
+
+	if (!(state->ls_flags & flags))
+		return (DLADM_STATUS_NOTFOUND);
+
+	if ((status = dladm_feth_info(handle, linkid, &flinfo,
+					state->ls_flags)) != DLADM_STATUS_OK)
+		return (status);
+
+	if (flinfo.sna_mac_len > sizeof (flbuf.feth_macaddr))
+		return (DLADM_STATUS_BADVAL);
+
+	(void) strlcpy(flbuf.feth_macaddr,
+			dladm_aggr_macaddr2str(flinfo.sna_mac_addr, mstr),
+			sizeof (flbuf.feth_macaddr));
+
+	ofmt_print(state->ls_ofmt, &flbuf);
+	return (status);
+}
+
+/* ARGSUSED */
+static int
+show_feth(dladm_handle_t dh, datalink_id_t linkid, void *arg)
+{
+	show_state_t* state = arg;
+
+	state->ls_status = print_feth(state, linkid);
+	return (DLADM_WALK_CONTINUE);
+}
+
+static void
+do_show_feth(int argc, char *argv[], const char *use)
+{
+	int option;
+	uint32_t	flags = DLADM_OPT_ACTIVE;
+	boolean_t       p_arg = B_FALSE;
+	datalink_id_t   linkid = DATALINK_ALL_LINKID;
+	show_state_t    state;
+	dladm_status_t  status;
+	boolean_t       o_arg = B_FALSE;
+	ofmt_handle_t   ofmt;
+	ofmt_status_t   oferr;
+	char *all_fields = "link,macaddress";
+	char *fields_str = all_fields;
+	uint_t		ofmtflags = 0;
+
+	bzero(&state, sizeof (state));
+
+	opterr = 0;
+	while ((option = getopt_long(argc, argv, ":pPo:",
+					show_lopts, NULL)) != -1) {
+		switch (option) {
+			case 'p':
+				if (p_arg)
+					die_optdup(option);
+
+				p_arg = B_TRUE;
+				state.ls_parsable = p_arg;
+				break;
+			case 'P':
+				if (flags != DLADM_OPT_ACTIVE)
+					die_optdup(option);
+
+				flags = DLADM_OPT_PERSIST;
+				break;
+			case 'o':
+				o_arg = B_TRUE;
+				fields_str = optarg;
+				break;
+			default:
+				die_opterr(optopt, option, use);
+				break;
+		}
+	}
+
+	if (p_arg && !o_arg)
+		die("-p requires -o");
+
+	if (strcasecmp(fields_str, "all") == 0) {
+		if (p_arg)
+			die("\"-o all\" is invalid with -p");
+		fields_str = all_fields;
+	}
+
+	/* get link name (optional last argument) */
+	if (optind == (argc-1)) {
+		if ((status = dladm_name2info(handle, argv[optind], &linkid,
+		  NULL, NULL, NULL)) != DLADM_STATUS_OK) {
+			die_dlerr(status, "link %s is not valid", argv[optind]);
+		}
+	} else if (optind != argc) {
+		usage();
+	}
+
+	state.ls_flags = flags;
+	state.ls_donefirst = B_FALSE;
+	if (state.ls_parsable)
+		ofmtflags |= OFMT_PARSABLE;
+	oferr = ofmt_open(fields_str, feth_fields, ofmtflags, 0, &ofmt);
+	ofmt_check(oferr, state.ls_parsable, ofmt, die, warn);
+	state.ls_ofmt = ofmt;
+
+	if (linkid == DATALINK_ALL_LINKID) {
+		(void) dladm_walk_datalink_id(show_feth, handle, &state,
+				DATALINK_CLASS_FETH, DATALINK_ANY_MEDIATYPE, flags);
+	} else {
+		(void) show_feth(handle, linkid, &state);
+		if (state.ls_status != DLADM_STATUS_OK) {
+			ofmt_close(ofmt);
+			die_dlerr(state.ls_status, "failed to show feth %s",
+					argv[optind]);
+		}
+	}
+	ofmt_close(ofmt);
+}
+
+static void
 link_stats(datalink_id_t linkid, uint_t interval, char *fields_str,
     show_state_t *state)
 {
@@ -7921,7 +8207,8 @@
 		    &media) != DLADM_STATUS_OK)
 			die("invalid link name '%s'", links[n]);
 		if (class & ~(DATALINK_CLASS_PHYS | DATALINK_CLASS_AGGR |
-		    DATALINK_CLASS_ETHERSTUB | DATALINK_CLASS_SIMNET))
+		    DATALINK_CLASS_ETHERSTUB | DATALINK_CLASS_SIMNET | 
+		    DATALINK_CLASS_FETH))
 			die("%s %s cannot be bridged",
 			    dladm_class2str(class, pointless), links[n]);
 		if (media != DL_ETHER && media != DL_100VG &&
--- old/usr/src/cmd/truss/codes.c	Fri Jun 12 16:14:57 2020
+++ new/usr/src/cmd/truss/codes.c	Fri Jun 12 16:14:57 2020
@@ -88,6 +88,7 @@
 #include <sys/aggr.h>
 #include <sys/dld.h>
 #include <net/simnet.h>
+#include <net/feth.h>
 #include <sys/vnic.h>
 #include <sys/fs/zfs.h>
 #include <inet/kssl/kssl.h>
@@ -1135,6 +1136,16 @@
 	{ (uint_t)SIMNET_IOC_MODIFY,		"SIMNET_IOC_MODIFY",
 		"simnet_ioc_info"},
 
+	/* feth ioctls */
+	{ (uint_t)FETH_IOC_CREATE,		"FETH_IOC_CREATE",
+		"feth_ioc_create"},
+	{ (uint_t)FETH_IOC_DELETE,		"FETH_IOC_DELETE",
+		"feth_ioc_delete"},
+	{ (uint_t)FETH_IOC_INFO,		"FETH_IOC_INFO",
+		"feth_ioc_info"},
+	{ (uint_t)FETH_IOC_MODIFY,		"FETH_IOC_MODIFY",
+		"feth_ioc_modify"},
+
 	/* vnic ioctls */
 	{ (uint_t)VNIC_IOC_CREATE,		"VNIC_IOC_CREATE",
 		"vnic_ioc_create"},
--- old/usr/src/lib/libdladm/Makefile	Fri Jun 12 16:14:57 2020
+++ new/usr/src/lib/libdladm/Makefile	Fri Jun 12 16:14:57 2020
@@ -29,7 +29,7 @@
 		libdlwlan.h libdlwlan_impl.h libdlvnic.h libdlvlan.h	\
 		libdlmgmt.h libdlflow.h libdlflow_impl.h libdlstat.h	\
 		libdlether.h libdlsim.h libdlbridge.h libdliptun.h	\
-		libdlib.h
+		libdlfeth.h libdlib.h
 
 HDRDIR =	common
 
@@ -46,7 +46,7 @@
 		common/libdlstat.c common/flowattr.c	\
 		common/libdlether.c common/libdlsim.c	\
 		common/libdlbridge.c common/libdliptun.c\
-		common/libdlib.c
+		common/libdlfeth.c common/libdlib.c
 
 XGETFLAGS =     -a -x libdladm.xcl
 
--- old/usr/src/lib/libdladm/Makefile.com	Fri Jun 12 16:14:57 2020
+++ new/usr/src/lib/libdladm/Makefile.com	Fri Jun 12 16:14:57 2020
@@ -28,7 +28,8 @@
 OBJECTS = libdladm.o secobj.o linkprop.o libdllink.o libdlaggr.o \
 	libdlwlan.o libdlvnic.o libdlmgmt.o libdlvlan.o	libdlib.o\
 	flowattr.o flowprop.o propfuncs.o libdlflow.o libdlstat.o \
-	usage.o libdlether.o libdlsim.o libdlbridge.o libdliptun.o
+	usage.o libdlether.o libdlsim.o libdlbridge.o libdliptun.o \
+	libdlfeth.o
 
 include ../../Makefile.lib
 
--- old/usr/src/lib/libdladm/common/libdladm.c	Fri Jun 12 16:14:57 2020
+++ new/usr/src/lib/libdladm/common/libdladm.c	Fri Jun 12 16:14:57 2020
@@ -663,6 +663,9 @@
 	case DATALINK_CLASS_SIMNET:
 		s = "simnet";
 		break;
+	case DATALINK_CLASS_FETH:
+		s = "feth";
+		break;
 	case DATALINK_CLASS_BRIDGE:
 		s = "bridge";
 		break;
--- old/usr/src/lib/libdladm/common/libdlaggr.c	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/libdlaggr.c	Fri Jun 12 16:14:58 2020
@@ -400,7 +400,9 @@
 		if ((dladm_datalink_id2info(handle, ports[i].lp_linkid, NULL,
 		    &class, &media, NULL, 0) != DLADM_STATUS_OK) ||
 		    !((class == DATALINK_CLASS_PHYS) ||
-		    (class == DATALINK_CLASS_SIMNET)) || (media != DL_ETHER)) {
+		    (class == DATALINK_CLASS_SIMNET) ||
+		    (class == DATALINK_CLASS_FETH) 
+		    || (media != DL_ETHER))) {
 			return (DLADM_STATUS_BADARG);
 		}
 	}
--- old/usr/src/lib/libdladm/common/libdlbridge.c	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/libdlbridge.c	Fri Jun 12 16:14:58 2020
@@ -1199,7 +1199,7 @@
 		 */
 		(void) dladm_walk_datalink_id(i_dladm_bridge_is_held, handle,
 		    &arg, DATALINK_CLASS_PHYS | DATALINK_CLASS_AGGR |
-		    DATALINK_CLASS_ETHERSTUB | DATALINK_CLASS_SIMNET,
+		    DATALINK_CLASS_ETHERSTUB | DATALINK_CLASS_SIMNET | DATALINK_CLASS_FETH,
 		    DATALINK_ANY_MEDIATYPE, DLADM_OPT_PERSIST);
 		if (arg.bha_isheld)
 			return (DLADM_STATUS_LINKBUSY);
--- /dev/null	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/libdlfeth.c	Fri Jun 12 16:14:58 2020
@@ -0,0 +1,345 @@
+/*
+ * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/mac.h>
+#include <sys/dls_mgmt.h>
+#include <sys/dlpi.h>
+#include <net/feth.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <libdladm_impl.h>
+#include <libdllink.h>
+#include <libdlaggr.h>
+#include <libdlfeth.h>
+
+static dladm_status_t dladm_feth_persist_conf(dladm_handle_t, const char *,
+	dladm_feth_attr_t *);
+static dladm_status_t dladm_feth_create_sys(dladm_handle_t, const char *,
+	uint32_t, dladm_feth_attr_t *);
+
+/* New feth instance creation */
+static dladm_status_t
+i_dladm_create_feth(dladm_handle_t handle, dladm_feth_attr_t *attrp) {
+	int rc;
+	dladm_status_t status = DLADM_STATUS_OK;
+	feth_ioc_create_t ioc;
+
+	bzero(&ioc, sizeof (ioc));
+	ioc.sic_flags = attrp->sna_flags;
+	ioc.sic_link_id = attrp->sna_link_id;
+	if (attrp->sna_mac_len > 0 && attrp->sna_mac_len <= MAXMACADDRLEN) {
+		ioc.sic_mac_len = attrp->sna_mac_len;
+		bcopy(attrp->sna_mac_addr, ioc.sic_mac_addr, ioc.sic_mac_len);
+	}
+	rc = ioctl(dladm_dld_fd(handle), FETH_IOC_CREATE, &ioc);
+	if (rc < 0)
+		status = dladm_errno2status(errno);
+	if (status != DLADM_STATUS_OK)
+		return (status);
+	bcopy(ioc.sic_mac_addr, attrp->sna_mac_addr, MAXMACADDRLEN);
+	attrp->sna_mac_len = ioc.sic_mac_len;
+	return (status);
+}
+
+/* Delete feth instance */
+static dladm_status_t
+i_dladm_delete_feth(dladm_handle_t handle, dladm_feth_attr_t *attrp) {
+	int rc;
+	dladm_status_t status = DLADM_STATUS_OK;
+	feth_ioc_delete_t ioc;
+
+	bzero(&ioc, sizeof (ioc));
+	ioc.sid_link_id = attrp->sna_link_id;
+
+	rc = ioctl(dladm_dld_fd(handle), FETH_IOC_DELETE, &ioc);
+	if (rc < 0)
+		status = dladm_errno2status(errno);
+	return (status);
+}
+
+/* Retrieve feth instance information */
+static dladm_status_t
+i_dladm_get_feth_info(dladm_handle_t handle, dladm_feth_attr_t *attrp) {
+	int rc;
+	dladm_status_t status = DLADM_STATUS_OK;
+	feth_ioc_info_t ioc;
+
+	bzero(&ioc, sizeof (ioc));
+	ioc.sii_link_id = attrp->sna_link_id;
+
+	rc = ioctl(dladm_dld_fd(handle), FETH_IOC_INFO, &ioc);
+	if (rc < 0) {
+		status = dladm_errno2status(errno);
+		return (status);
+	}
+	bcopy(ioc.sii_mac_addr, attrp->sna_mac_addr, MAXMACADDRLEN);
+	attrp->sna_mac_len = ioc.sii_mac_len;
+	return (status);
+}
+
+/* Retrieve feth configuratin */
+static dladm_status_t
+i_dladm_get_feth_info_persist(dladm_handle_t handle, dladm_feth_attr_t *attrp) {
+	dladm_conf_t conf;
+	dladm_status_t status;
+	char macstr[ETHERADDRL * 3];
+	uint64_t u64;
+	boolean_t mac_fixed;
+
+	if ((status = dladm_getsnap_conf(handle, attrp->sna_link_id, &conf)) !=
+		DLADM_STATUS_OK)
+		return (status);
+	status = dladm_get_conf_field(handle, conf, FMADDRLEN, &u64,
+		sizeof (u64));
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	attrp->sna_mac_len = (uint_t) u64;
+
+	status = dladm_get_conf_field(handle, conf, FMACADDR, macstr,
+		sizeof (macstr));
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	(void) dladm_aggr_str2macaddr(macstr, &mac_fixed, attrp->sna_mac_addr);
+
+done:
+	dladm_destroy_conf(handle, conf);
+	return (status);
+}
+
+dladm_status_t
+dladm_feth_create(dladm_handle_t handle, const char *fethname, uint32_t flags) {
+	datalink_id_t feth_id;
+	dladm_status_t status;
+	dladm_feth_attr_t attr;
+	uint_t feth_flags;
+
+	if (!(flags & DLADM_OPT_ACTIVE))
+		return (DLADM_STATUS_NOTSUP);
+	feth_flags = flags & FETH_FLAG_SINGLESHOT;
+	flags &= (DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
+	if ((status = dladm_create_datalink_id(handle, fethname,
+		DATALINK_CLASS_FETH, DL_ETHER, flags, &feth_id)) !=
+		DLADM_STATUS_OK)
+		return (status);
+	bzero(&attr, sizeof (attr));
+	attr.sna_link_id = feth_id;
+	attr.sna_flags = feth_flags;
+	status = dladm_feth_create_sys(handle, fethname, flags, &attr);
+	return (status);
+}
+
+dladm_status_t
+dladm_feth_create_mac(dladm_handle_t handle, const char *fethname, uint32_t
+	flags, uint8_t *mac_addr) {
+	datalink_id_t feth_id;
+	dladm_status_t status;
+	dladm_feth_attr_t attr;
+
+	if (!(flags & DLADM_OPT_ACTIVE))
+		return (DLADM_STATUS_NOTSUP);
+	flags &= (DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
+	if ((status = dladm_create_datalink_id(handle, fethname,
+		DATALINK_CLASS_FETH, DL_ETHER, flags, &feth_id)) !=
+		DLADM_STATUS_OK)
+		return (status);
+	bzero(&attr, sizeof (attr));
+	attr.sna_link_id = feth_id;
+	bcopy(mac_addr, attr.sna_mac_addr, ETHERADDRL);
+	attr.sna_mac_len = ETHERADDRL;
+	status = dladm_feth_create_sys(handle, fethname, flags, &attr);
+	return (status);
+}
+
+static dladm_status_t
+dladm_feth_create_sys(dladm_handle_t handle, const char *fethname, uint32_t
+	flags, dladm_feth_attr_t *attr) {
+	dladm_status_t status;
+
+	attr->sna_flags |= (flags & FETH_FLAG_SINGLESHOT);
+	status = i_dladm_create_feth(handle, attr);
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	if (!(flags & DLADM_OPT_PERSIST))
+		goto done;
+	status = dladm_feth_persist_conf(handle, fethname, attr);
+	if (status != DLADM_STATUS_OK) {
+		(void) i_dladm_delete_feth(handle, attr);
+		goto done;
+	}
+	(void) dladm_set_linkprop(handle, attr->sna_link_id, NULL, NULL, 0,
+		flags);
+
+done:
+	if (status != DLADM_STATUS_OK)
+		(void) dladm_destroy_datalink_id(handle, attr->sna_link_id,
+			flags);
+	return (status);
+}
+
+dladm_status_t
+dladm_feth_delete(dladm_handle_t handle, datalink_id_t feth_id, uint32_t
+	flags) {
+	dladm_feth_attr_t attr;
+	dladm_feth_attr_t prevattr;
+	dladm_status_t status;
+	datalink_class_t class;
+
+	if ((dladm_datalink_id2info(handle, feth_id, NULL, &class, NULL, NULL,
+		0) != DLADM_STATUS_OK))
+		return (DLADM_STATUS_BADARG);
+	if (class != DATALINK_CLASS_FETH)
+		return (DLADM_STATUS_BADARG);
+	/* Check current feth attributes before deletion */
+	flags &= (DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
+	bzero(&prevattr, sizeof (prevattr));
+	if ((status = dladm_feth_info(handle, feth_id, &prevattr, flags)) !=
+		DLADM_STATUS_OK)
+		return (status);
+	bzero(&attr, sizeof (attr));
+	attr.sna_link_id = feth_id;
+	if (flags & DLADM_OPT_ACTIVE) {
+		status = i_dladm_delete_feth(handle, &attr);
+		if (status == DLADM_STATUS_OK) {
+			(void) dladm_set_linkprop(handle, feth_id, NULL, NULL,
+				0, DLADM_OPT_ACTIVE);
+			(void) dladm_destroy_datalink_id(handle, feth_id,
+				DLADM_OPT_ACTIVE);
+		} else if (status != DLADM_STATUS_NOTFOUND) {
+			return (status);
+		}
+	}
+	if (flags & DLADM_OPT_PERSIST) {
+		(void) dladm_remove_conf(handle, feth_id);
+		(void) dladm_destroy_datalink_id(handle, feth_id,
+			DLADM_OPT_PERSIST);
+	}
+	return (status);
+}
+
+/* Retrieve feth information either active or from configuration */
+dladm_status_t
+dladm_feth_info(dladm_handle_t handle, datalink_id_t feth_id,
+	dladm_feth_attr_t *attrp, uint32_t flags) {
+	datalink_class_t class;
+	dladm_status_t status;
+
+	if ((dladm_datalink_id2info(handle, feth_id, NULL, &class, NULL, NULL,
+		0) != DLADM_STATUS_OK))
+		return (DLADM_STATUS_BADARG);
+	if (class != DATALINK_CLASS_FETH)
+		return (DLADM_STATUS_BADARG);
+	bzero(attrp, sizeof (attrp));
+	attrp->sna_link_id = feth_id;
+
+	if (flags & DLADM_OPT_ACTIVE) {
+		status = i_dladm_get_feth_info(handle, attrp);
+		/*
+		 * If no active feth found then return any feth
+		 * from stored config if requested.
+		 */
+		if (status == DLADM_STATUS_NOTFOUND && (flags &
+			DLADM_OPT_PERSIST))
+			return (i_dladm_get_feth_info_persist(handle, attrp));
+		return (status);
+	} else if (flags & DLADM_OPT_PERSIST) {
+		return (i_dladm_get_feth_info_persist(handle, attrp));
+	} else {
+		return (DLADM_STATUS_BADARG);
+	}
+}
+
+/* Bring up feth from stored configuration */
+static int
+i_dladm_feth_up(dladm_handle_t handle, datalink_id_t feth_id, void *arg) {
+	dladm_status_t *statusp = arg;
+	dladm_status_t status;
+	dladm_feth_attr_t attr;
+
+	bzero(&attr, sizeof (attr));
+	attr.sna_link_id = feth_id;
+	status = dladm_feth_info(handle, feth_id, &attr, DLADM_OPT_PERSIST);
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	status = i_dladm_create_feth(handle, &attr);
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	if ((status = dladm_up_datalink_id(handle, feth_id)) !=
+		DLADM_STATUS_OK) {
+		(void) dladm_feth_delete(handle, feth_id, DLADM_OPT_PERSIST);
+		goto done;
+	}
+done:
+	*statusp = status;
+	return (DLADM_WALK_CONTINUE);
+}
+
+/*
+ * Bring up feth instance(s) from configuration
+ * ARGSUSED
+ */
+dladm_status_t
+dladm_feth_up(dladm_handle_t handle, datalink_id_t feth_id, uint32_t flags) {
+	dladm_status_t status;
+
+	if (feth_id == DATALINK_ALL_LINKID) {
+		(void) dladm_walk_datalink_id(i_dladm_feth_up, handle, &status,
+			DATALINK_CLASS_FETH, DATALINK_ANY_MEDIATYPE,
+			DLADM_OPT_PERSIST);
+		return (DLADM_STATUS_OK);
+	} else {
+		(void) i_dladm_feth_up(handle, feth_id, &status);
+		return (status);
+	}
+}
+
+/* Store feth configuration */
+static dladm_status_t
+dladm_feth_persist_conf(dladm_handle_t handle, const char *name,
+	dladm_feth_attr_t *attrp) {
+	dladm_conf_t conf;
+	dladm_status_t status;
+	char mstr[ETHERADDRL * 3];
+	uint64_t u64;
+
+	if ((status = dladm_create_conf(handle, name, attrp->sna_link_id,
+		DATALINK_CLASS_FETH, DL_ETHER, &conf)) != DLADM_STATUS_OK)
+		return (status);
+	status = dladm_set_conf_field(handle, conf, FMACADDR, DLADM_TYPE_STR,
+		dladm_aggr_macaddr2str(attrp->sna_mac_addr, mstr));
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	u64 = attrp->sna_mac_len;
+	status = dladm_set_conf_field(handle, conf, FMADDRLEN,
+		DLADM_TYPE_UINT64, &u64);
+	if (status != DLADM_STATUS_OK)
+		goto done;
+	status = dladm_write_conf(handle, conf);
+done:
+	dladm_destroy_conf(handle, conf);
+	return (status);
+}
--- /dev/null	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/libdlfeth.h	Fri Jun 12 16:14:58 2020
@@ -0,0 +1,56 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBDLFETH_H
+#define	_LIBDLFETH_H
+
+#include <sys/mac.h>
+#include <libdladm_impl.h>
+#include <net/feth.h>
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+typedef struct dladm_feth_attr {
+	datalink_id_t sna_link_id;
+	uchar_t sna_mac_addr[MAXMACADDRLEN];
+	uint_t sna_mac_len;
+	uint_t sna_flags;
+} dladm_feth_attr_t;
+
+dladm_status_t dladm_feth_create(dladm_handle_t, const char *, uint32_t);
+dladm_status_t dladm_feth_create_mac(dladm_handle_t, const char *, uint32_t,
+	uint8_t *);
+dladm_status_t dladm_feth_delete(dladm_handle_t, datalink_id_t, uint32_t);
+dladm_status_t dladm_feth_info(dladm_handle_t, datalink_id_t,
+	dladm_feth_attr_t *, uint32_t);
+dladm_status_t dladm_feth_up(dladm_handle_t, datalink_id_t, uint32_t);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* _LIBDLFETH_H */
--- old/usr/src/lib/libdladm/common/linkprop.c	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/linkprop.c	Fri Jun 12 16:14:58 2020
@@ -739,53 +739,62 @@
 	{ "default_tag", { "1", 1 }, NULL, 0,
 	    set_bridge_pvid, NULL, get_bridge_pvid, check_bridge_pvid,
 	    0, DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "learn_limit", { "1000", 1000 }, NULL, 0,
 	    set_public_prop, NULL, get_uint32,
 	    check_uint32, 0,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "learn_decay", { "200", 200 }, NULL, 0,
 	    set_public_prop, NULL, get_uint32,
 	    check_uint32, 0,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp", { "1", 1 },
 	    link_01_vals, VALCNT(link_01_vals),
 	    set_stp_prop, NULL, get_stp, NULL, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp_priority", { "128", 128 }, NULL, 0,
 	    set_stp_prop, NULL, get_stp, check_stp_prop, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp_cost", { "auto", 0 }, NULL, 0,
 	    set_stp_prop, NULL, get_stp, check_stp_prop, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp_edge", { "1", 1 },
 	    link_01_vals, VALCNT(link_01_vals),
 	    set_stp_prop, NULL, get_stp, NULL, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp_p2p", { "auto", P2P_AUTO },
 	    stp_p2p_vals, VALCNT(stp_p2p_vals),
 	    set_stp_prop, NULL, get_stp, NULL, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "stp_mcheck", { "0", 0 },
 	    link_01_vals, VALCNT(link_01_vals),
 	    set_stp_prop, NULL, get_stp, check_stp_prop, PD_AFTER_PERM,
 	    DATALINK_CLASS_PHYS|DATALINK_CLASS_AGGR|
-	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET, DL_ETHER },
+	    DATALINK_CLASS_ETHERSTUB|DATALINK_CLASS_SIMNET|DATALINK_CLASS_FETH,
+	    DL_ETHER },
 
 	{ "protection", { "--", RESET_VAL },
 	    link_protect_vals, VALCNT(link_protect_vals),
--- old/usr/src/lib/libdladm/common/mapfile-vers	Fri Jun 12 16:14:58 2020
+++ new/usr/src/lib/libdladm/common/mapfile-vers	Fri Jun 12 16:14:58 2020
@@ -223,6 +223,11 @@
 	dladm_simnet_delete;
 	dladm_simnet_info;
 	dladm_simnet_up;
+	dladm_feth_create;
+	dladm_feth_create_mac;
+	dladm_feth_delete;
+	dladm_feth_info;
+	dladm_feth_up;
 	dladm_bridge_str2prot;
 	dladm_bridge_prot2str;
 	dladm_bridge_get_properties;
--- old/usr/src/pkg/manifests/system-kernel.mf	Fri Jun 12 16:14:58 2020
+++ new/usr/src/pkg/manifests/system-kernel.mf	Fri Jun 12 16:14:58 2020
@@ -144,6 +144,7 @@
     perms="devinfo,ro 0444 root sys"
 driver name=dld perms="* 0666 root sys"
 driver name=dlpistub perms="* 0666 root sys"
+driver name=feth clone_perms="feth 0666 root sys" perms="* 0666 root sys"
 $(sparc_ONLY)driver name=i8042 alias=8042
 $(i386_ONLY)driver name=i8042
 driver name=icmp perms="icmp 0666 root sys" \
@@ -355,6 +356,7 @@
 file path=kernel/drv/$(ARCH64)/devinfo group=sys
 file path=kernel/drv/$(ARCH64)/dld group=sys
 file path=kernel/drv/$(ARCH64)/dlpistub group=sys
+file path=kernel/drv/$(ARCH64)/feth group=sys
 file path=kernel/drv/$(ARCH64)/i8042 group=sys
 file path=kernel/drv/$(ARCH64)/icmp group=sys
 file path=kernel/drv/$(ARCH64)/icmp6 group=sys
@@ -437,6 +439,7 @@
 file path=kernel/drv/devinfo.conf group=sys
 file path=kernel/drv/dld.conf group=sys
 file path=kernel/drv/dlpistub.conf group=sys
+file path=kernel/drv/feth.conf group=sys
 file path=kernel/drv/icmp.conf group=sys
 file path=kernel/drv/icmp6.conf group=sys
 $(i386_ONLY)file path=kernel/drv/intel_nb5000.conf group=sys
--- old/usr/src/uts/common/Makefile.files	Fri Jun 12 16:14:58 2020
+++ new/usr/src/uts/common/Makefile.files	Fri Jun 12 16:14:58 2020
@@ -709,6 +709,8 @@
 
 SIMNET_OBJS +=	simnet.o
 
+FETH_OBJS +=	feth.o
+
 IB_OBJS +=	ibnex.o ibnex_ioctl.o ibnex_hca.o
 
 IBCM_OBJS +=	ibcm_impl.o ibcm_sm.o ibcm_ti.o ibcm_utils.o ibcm_path.o \
--- old/usr/src/uts/common/Makefile.rules	Fri Jun 12 16:14:58 2020
+++ new/usr/src/uts/common/Makefile.rules	Fri Jun 12 16:14:58 2020
@@ -1130,6 +1130,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/feth/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/softmac/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
@@ -2416,6 +2420,9 @@
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/simnet/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
+$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/feth/%.c
+	@($(LHEAD) $(LINT.c) $< $(LTAIL))
+
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/softmac/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
--- old/usr/src/uts/common/io/dld/dld_drv.c	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/io/dld/dld_drv.c	Fri Jun 12 16:14:59 2020
@@ -1575,7 +1575,8 @@
 	{SIMNET_IOC,	"simnet", 0, NULL, 0},
 	{BRIDGE_IOC,	"bridge", 0, NULL, 0},
 	{IPTUN_IOC,	"iptun", 0, NULL, 0},
-	{IBPART_IOC,	"ibp", -1, NULL, 0}
+	{IBPART_IOC,	"ibp", -1, NULL, 0},
+	{FETH_IOC,	"feth", 0, NULL, 0}
 };
 #define	DLDIOC_CNT	\
 	(sizeof (dld_ioc_modtable) / sizeof (dld_ioc_modentry_t))
--- old/usr/src/uts/common/io/dld/dld_str.c	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/io/dld/dld_str.c	Fri Jun 12 16:14:59 2020
@@ -2279,8 +2279,8 @@
 static void
 ioc(dld_str_t *dsp, mblk_t *mp)
 {
-	queue_t	*q = dsp->ds_wq;
-
+	queue_t *q = dsp->ds_wq;
+	
 	if (dsp->ds_dlstate == DL_UNATTACHED) {
 		miocnak(q, mp, 0, EINVAL);
 		return;
--- /dev/null	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/io/feth/feth.c	Fri Jun 12 16:14:59 2020
@@ -0,0 +1,1514 @@
+/*
+ * 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 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2019 Joyent, Inc.
+ * Copyright 2020 H. William Welliver III
+ */
+
+/*
+ * feth is a simulated network device driver intended to facilitate
+ * userland based software networking. Each network device created has a
+ * corresponding character device that a userland process may open.
+ * Data written to the character device is received as network traffic
+ * by the feth interface. Data transmitted by the system on a feth
+ * interface is available to be read by a userland program as raw frame
+ * data.
+ *
+ * feth is currently implemented as a single driver where networking
+ * functionality is handled by the GLDv3 (mac) framework and character
+ * device functionality is implemented as a standard STREAMS driver.
+ * Because a mac driver is layered on top of STREAMS, and thus uses
+ * the same entry points needed by the character device portion of feth,
+ * the driver intercepts all STREAMS callbacks common to both and
+ * routes calls destined for the mac layer to the appropriate entries in
+ * the mac streamtab and handling those for the character device ourselves.
+ * This behavior is almost certainly not what we'd want to do in an
+ * ideal situation; hopefully this can be improved.
+ */
+
+#include <sys/policy.h>
+#include <sys/conf.h>
+#include <sys/modctl.h>
+#include <sys/priv_names.h>
+#include <sys/dlpi.h>
+#include <net/feth.h>
+#include <sys/ethernet.h>
+#include <sys/mac.h>
+#include <sys/dls.h>
+#include <sys/mac_ether.h>
+#include <sys/mac_provider.h>
+#include <sys/mac_client_priv.h>
+#include <sys/vlan.h>
+#include <sys/random.h>
+#include <sys/sysmacros.h>
+#include <sys/list.h>
+#include <sys/strsubr.h>
+#include <sys/strsun.h>
+#include <sys/atomic.h>
+#include <sys/mac_impl.h>
+#include <sys/pattr.h>
+#include <sys/thread.h>
+#include <sys/synch.h>
+#include <sys/sunddi.h>
+
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/debug.h>
+#include <sys/stropts.h>
+#include <sys/stream.h>
+#include <sys/strlog.h>
+#include <sys/kmem.h>
+#include <sys/stat.h>
+#include <sys/kstat.h>
+#include <sys/errno.h>
+#include <sys/strsun.h>
+#include <sys/byteorder.h>
+#include <sys/id_space.h>
+#include <sys/fs/dv_node.h>
+#include "feth_impl.h"
+
+#define	FETHINFO	"Faux Ethernet Driver"
+
+#if 0
+#define	FETH_DEBUG	1
+#endif
+
+#ifdef FETH_DEBUG
+#define	FDBG		cmn_err
+#else
+#define	FDBG(a ...)
+#endif
+
+#define	DBG		cmn_err
+
+static dev_info_t *feth_dip;
+// static ddi_taskq_t *feth_txq;
+
+static int feth_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int feth_attach(dev_info_t *, ddi_attach_cmd_t);
+static int feth_detach(dev_info_t *, ddi_detach_cmd_t);
+static int feth_ioc_create(void *, intptr_t, int, cred_t *, int *);
+static int feth_ioc_delete(void *, intptr_t, int, cred_t *, int *);
+static int feth_ioc_info(void *, intptr_t, int, cred_t *, int *);
+
+static int feth_wq_srv(queue_t *wq);
+static int feth_wq_put(queue_t *wq, mblk_t *mp);
+
+static int feth_rq_close(queue_t *, int, cred_t *);
+static int feth_rq_open(queue_t *rq, dev_t *dev, int flag, int sflag,
+    cred_t *credp);
+
+static uint8_t *mcastaddr_lookup(feth_dev_t *, const uint8_t *);
+
+static int i_feth_delete(feth_dev_t *fdev);
+static void i_feth_resume_tx(feth_dev_t *fdev);
+
+static dld_ioc_info_t feth_ioc_list[] = {
+	{ FETH_IOC_CREATE, DLDCOPYINOUT, (sizeof (feth_ioc_create_t)),
+	    feth_ioc_create, secpolicy_dl_config },
+	{ FETH_IOC_DELETE, DLDCOPYIN, (sizeof (feth_ioc_delete_t)),
+	    feth_ioc_delete, secpolicy_dl_config },
+	{ FETH_IOC_INFO, DLDCOPYINOUT, (sizeof (feth_ioc_info_t)),
+	    feth_ioc_info, NULL }
+};
+
+static struct module_info feth_stream_mod_info = {
+	278,			/* mi_idnum  - Module ID number	*/
+	"feth",	/* mi_idname - Module name 	*/
+	21,			/* mi_minpsz - Min packet size 	*/
+	10240,			/* mi_maxpsz - Max packet size 	*/
+	(100000000),		/* mi_hiwat  - Hi-water mark 	*/
+	(100000)		/* mi_lowat  _ Lo-water mark 	*/
+};
+
+qi_qopen_t read_queue_qopen;
+qi_qclose_t read_queue_qclose;
+
+qi_putp_t write_queue_putp;
+qi_srvp_t write_queue_srvp;
+
+static struct qinit feth_rq_init = {
+	NULL,			/* qi_putp */
+	NULL,			/* qi_srvp */
+	feth_rq_open,		/* qi_qopen */
+	feth_rq_close,		/* qi_qclose */
+	NULL,			/* qi_qadmin */
+	&feth_stream_mod_info,	/* qi_minfo */
+	NULL			/* qi_mstat */
+};
+
+static struct qinit feth_wq_init = {
+	feth_wq_put,		/* qi_putp */
+	feth_wq_srv,		/* qi_srvp */
+	NULL,			/* qi_qopen */
+	NULL,			/* qi_qclose */
+	NULL,			/* qi_qadmin */
+	&feth_stream_mod_info,	/* qi_minfo */
+	NULL			/* qi_mstat */
+};
+
+static struct streamtab feth_stream_info = {
+	&feth_rq_init,		/* st_rdinit */
+	&feth_wq_init,		/* st_wrinit */
+	NULL,			/* st_muxrinit */
+	NULL			/* st_muxwrinit */
+};
+
+DDI_DEFINE_STREAM_OPS(feth_dev_ops, nulldev, nulldev, feth_attach, feth_detach,
+    nodev, feth_getinfo, D_MP, &feth_stream_info, ddi_quiesce_not_supported);
+
+static struct modldrv feth_modldrv = {
+	&mod_driverops,		/* Type of module.  This one is a driver */
+	FETHINFO,		/* short description */
+	&feth_dev_ops		/* driver specific ops */
+};
+
+static struct modlinkage modlinkage = {
+	MODREV_1, &feth_modldrv, NULL
+};
+
+/* List of active driver Streams */
+static feth_stream_t *feth_stream;
+
+/* Minor number allocation space */
+id_space_t *minor_numbers;
+
+/* MAC callback function declarations */
+static int feth_m_start(void *);
+static void feth_m_stop(void *);
+static int feth_m_promisc(void *, boolean_t);
+static int feth_m_multicst(void *, boolean_t, const uint8_t *);
+static int feth_m_unicst(void *, const uint8_t *);
+static int feth_m_stat(void *, uint_t, uint64_t *);
+static mblk_t *feth_m_tx(void *, mblk_t *);
+static int feth_m_setprop(void *, const char *, mac_prop_id_t, const uint_t,
+    const void *);
+static int feth_m_getprop(void *, const char *, mac_prop_id_t, uint_t, void *);
+static void feth_m_propinfo(void *, const char *, mac_prop_id_t,
+    mac_prop_info_handle_t);
+static void feth_m_ioctl(void *, queue_t *, mblk_t *);
+static boolean_t feth_m_getcapab(void *, mac_capab_t, void *);
+static int feth_send(feth_dev_t *fdev, mblk_t *mp);
+
+static mac_callbacks_t feth_m_callbacks = {
+	(MC_IOCTL | MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO),
+	feth_m_stat, feth_m_start, feth_m_stop, feth_m_promisc,
+	feth_m_multicst, feth_m_unicst, feth_m_tx, NULL, feth_m_ioctl,
+	feth_m_getcapab, NULL, NULL, feth_m_setprop, feth_m_getprop,
+	feth_m_propinfo
+};
+
+/*
+ * feth_dev_lock protects the feth device list.
+ * fd_instlock in each feth_dev_t protects access to
+ * a single feth_dev_t.
+ */
+static krwlock_t feth_dev_lock;
+static list_t feth_dev_list;
+static int feth_count;	/* Num of feth instances */
+
+int
+_init(void) {
+	int status;
+
+	mac_init_ops(&feth_dev_ops, "feth");
+
+	// save the mac stream callbacks
+	write_queue_putp = feth_dev_ops.devo_cb_ops->cb_str->st_wrinit->qi_putp;
+	write_queue_srvp = feth_dev_ops.devo_cb_ops->cb_str->st_wrinit->qi_srvp;
+	read_queue_qopen =
+	    feth_dev_ops.devo_cb_ops->cb_str->st_rdinit->qi_qopen;
+	read_queue_qclose =
+	    feth_dev_ops.devo_cb_ops->cb_str->st_rdinit->qi_qclose;
+
+	// now, put our callbacks back in.
+	// these will check to see if the messages are for our minors
+	// and pass the messages on to mac if not
+	feth_dev_ops.devo_cb_ops->cb_str->st_wrinit->qi_putp = feth_wq_put;
+	feth_dev_ops.devo_cb_ops->cb_str->st_wrinit->qi_srvp = feth_wq_srv;
+	feth_dev_ops.devo_cb_ops->cb_str->st_rdinit->qi_qopen = feth_rq_open;
+	feth_dev_ops.devo_cb_ops->cb_str->st_rdinit->qi_qclose = feth_rq_close;
+
+	status = mod_install(&modlinkage);
+	if (status != DDI_SUCCESS) {
+		FDBG(CE_WARN, "feth _init failed");
+		mac_fini_ops(&feth_dev_ops);
+	}
+	return (status);
+}
+
+int
+_fini(void) {
+	int status;
+
+	status = mod_remove(&modlinkage);
+	if (status == DDI_SUCCESS)
+		mac_fini_ops(&feth_dev_ops);
+	return (status);
+}
+
+int
+_info(struct modinfo *modinfop) {
+	return (mod_info(&modlinkage, modinfop));
+}
+
+static boolean_t
+feth_init(void) {
+	minor_numbers = id_space_create("feth_minors", 0, 1000);
+	rw_init(&feth_dev_lock, NULL, RW_DEFAULT, NULL);
+	list_create(&feth_dev_list, sizeof (feth_dev_t), offsetof(feth_dev_t,
+		fd_listnode));
+	return (B_TRUE);
+}
+
+static void
+feth_fini(void) {
+	ASSERT(feth_count == 0);
+	id_space_destroy(minor_numbers);
+	rw_destroy(&feth_dev_lock);
+	list_destroy(&feth_dev_list);
+}
+
+/*ARGSUSED*/
+static int
+feth_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
+    void **result) {
+	int instance = getminor((dev_t) arg);
+	switch (infocmd) {
+	case DDI_INFO_DEVT2DEVINFO:
+		*result = feth_dip;
+		return (DDI_SUCCESS);
+	case DDI_INFO_DEVT2INSTANCE:
+		cmn_err(CE_WARN, "feth: trying to get instance number.");
+		*result = &instance;
+		return (DDI_SUCCESS);
+	}
+	return (DDI_FAILURE);
+}
+
+static int
+feth_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) {
+	switch (cmd) {
+	case DDI_ATTACH:
+		if (ddi_get_instance(dip) != 0) {
+			FDBG(CE_WARN, "feth not instance zero");
+			/* we only allow instance 0 to attach */
+			return (DDI_FAILURE);
+		}
+		if (dld_ioc_register(FETH_IOC, feth_ioc_list, DLDIOCCNT(
+			feth_ioc_list)) != 0) {
+			FDBG(CE_WARN, "feth ioctl register failed");
+			return (DDI_FAILURE);
+		}
+		feth_dip = dip;
+		if (!feth_init())
+			return (DDI_FAILURE);
+		return (DDI_SUCCESS);
+
+	case DDI_RESUME:
+		return (DDI_SUCCESS);
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+/*ARGSUSED*/
+static int
+feth_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) {
+	switch (cmd) {
+	case DDI_DETACH:
+		/*
+		 * Allow the feth instance to be detached only if there
+		 * are no feths configured.
+		 */
+		if (feth_count > 0)
+			return (DDI_FAILURE);
+		dld_ioc_unregister(FETH_IOC);
+		feth_fini();
+		feth_dip = NULL;
+		return (DDI_SUCCESS);
+
+	case DDI_SUSPEND:
+		return (DDI_SUCCESS);
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+/* Caller must hold feth_dev_lock */
+static feth_dev_t *
+feth_dev_lookup(datalink_id_t link_id) {
+	feth_dev_t *fdev;
+
+	ASSERT(RW_LOCK_HELD(&feth_dev_lock));
+	for (fdev = list_head(&feth_dev_list); fdev != NULL;
+	    fdev = list_next(&feth_dev_list, fdev)) {
+		if (!(fdev->fd_flags & FDF_SHUTDOWN) && (fdev->fd_link_id ==
+		    link_id)) {
+			atomic_inc_32(&fdev->fd_refcount);
+			return (fdev);
+		}
+	}
+	return (NULL);
+}
+
+static feth_dev_t *
+feth_dev_lookup_cd(dev_t fd_dev_cd) {
+	feth_dev_t *fdev;
+
+	ASSERT(RW_LOCK_HELD(&feth_dev_lock));
+	for (fdev = list_head(&feth_dev_list); fdev != NULL;
+	    fdev = list_next(&feth_dev_list, fdev)) {
+		if (!(fdev->fd_flags & FDF_SHUTDOWN) && (fdev->fd_dev_cd ==
+		    fd_dev_cd)) {
+			atomic_inc_32(&fdev->fd_refcount);
+			return (fdev);
+		}
+	}
+	return (NULL);
+}
+
+static void
+feth_dev_unref(feth_dev_t *fdev) {
+	cmn_err(CE_WARN, "feth ref count: %d\n", fdev->fd_refcount);
+	ASSERT(fdev->fd_refcount > 0);
+	if (atomic_dec_32_nv(&fdev->fd_refcount) != 0)
+		return;
+	if (fdev->fd_mh != NULL)
+		(void) mac_unregister(fdev->fd_mh);
+	mutex_destroy(&fdev->fd_instlock);
+	cv_destroy(&fdev->fd_threadwait);
+	kmem_free(fdev->fd_mcastaddrs, ETHERADDRL * fdev->fd_mcastaddr_count);
+	kmem_free(fdev, sizeof (*fdev));
+	feth_count--;
+}
+
+static int
+feth_init_ether(feth_dev_t *fdev, mac_register_t *mac) {
+	int err;
+
+	mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
+	mac->m_max_sdu = FETH_MAX_MTU;
+	mac->m_margin = VLAN_TAGSZ;
+	err = mac_register(mac, &fdev->fd_mh);
+	return (err);
+}
+
+static int
+feth_init_mac(feth_dev_t *fdev) {
+	mac_register_t *mac;
+	int err;
+
+	if ((mac = mac_alloc(MAC_VERSION)) == NULL)
+		return (ENOMEM);
+	mac->m_driver = fdev;
+	mac->m_dip = feth_dip;
+	mac->m_instance = (uint_t) -1;
+	mac->m_src_addr = fdev->fd_mac_addr;
+	mac->m_callbacks = &feth_m_callbacks;
+	mac->m_min_sdu = 0;
+
+	err = feth_init_ether(fdev, mac);
+
+	mac_free(mac);
+	return (err);
+}
+
+/* ARGSUSED */
+static int
+feth_ioc_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) {
+	feth_ioc_create_t *create_arg = karg;
+	feth_dev_t *fdev;
+	feth_dev_t *fdev_tmp;
+	dev_info_t *devinfo;
+	int err = 0;
+	int dev_instance = -1;
+	char linkname[MAXLINKNAMELEN];
+	datalink_class_t class;
+
+	DBG(CE_WARN, "feth feth_ioc_create, link_id=%d",
+	    create_arg->sic_link_id);
+	fdev = kmem_zalloc(sizeof (*fdev), KM_NOSLEEP);
+	if (fdev == NULL)
+		return (ENOMEM);
+	rw_enter(&feth_dev_lock, RW_WRITER);
+	if ((fdev_tmp = feth_dev_lookup(create_arg->sic_link_id)) != NULL) {
+		DBG(CE_WARN, "feth: dev already exists");
+		feth_dev_unref(fdev_tmp);
+		rw_exit(&feth_dev_lock);
+		kmem_free(fdev, sizeof (*fdev));
+		return (EEXIST);
+	}
+	fdev->fd_link_id = create_arg->sic_link_id;
+	fdev->fd_zoneid = crgetzoneid(cred);
+	fdev->fd_flags = create_arg->sic_flags;
+	fdev->fd_refcount++;
+	mutex_init(&fdev->fd_instlock, NULL, MUTEX_DRIVER, NULL);
+	cv_init(&fdev->fd_threadwait, NULL, CV_DRIVER, NULL);
+	feth_count++;
+	if (create_arg->sic_mac_len == 0) {
+		FDBG(CE_WARN, "feth creating mac address");
+		/* Generate random MAC address */
+		(void) random_get_pseudo_bytes(fdev->fd_mac_addr, ETHERADDRL);
+		/* Ensure MAC address is not multicast and is local */
+		fdev->fd_mac_addr[0] = (fdev->fd_mac_addr[0] & ~1) | 2;
+		fdev->fd_mac_len = ETHERADDRL;
+	} else {
+		FDBG(CE_WARN, "feth using provided mac address");
+		(void) memcpy(fdev->fd_mac_addr, create_arg->sic_mac_addr,
+		    create_arg->sic_mac_len);
+		fdev->fd_mac_len = create_arg->sic_mac_len;
+	}
+	if ((err = feth_init_mac(fdev)) != 0) {
+		feth_dev_unref(fdev);
+		goto exit;
+	}
+	if ((err = dls_devnet_create(fdev->fd_mh, fdev->fd_link_id, crgetzoneid(
+		cred))) != 0) {
+		feth_dev_unref(fdev);
+		goto exit;
+	}
+	if ((err = dls_mgmt_get_linkinfo(fdev->fd_link_id, linkname, &class,
+		NULL, NULL)) != 0) {
+		datalink_id_t tmpid;
+
+		if ((err = dls_devnet_destroy(fdev->fd_mh, &tmpid, B_TRUE)) !=
+		    0)
+			cmn_err(CE_WARN,
+			    "feth: unable to remove /dev/net entry");
+		feth_dev_unref(fdev);
+		goto exit;
+	}
+
+	/* Create the filesystem device node */
+	devinfo = ((mac_impl_t *) fdev->fd_mh)->mi_dip;
+	dev_instance = id_alloc(minor_numbers);
+	if (dev_instance == -1) {
+		datalink_id_t tmpid;
+
+		cmn_err(CE_WARN, "feth: no available instances");
+		if ((err = dls_devnet_destroy(fdev->fd_mh, &tmpid, B_TRUE)) !=
+		    0)
+			cmn_err(CE_WARN,
+			    "feth: unable to remove /dev/net entry");
+		err = DDI_FAILURE;	/* perhaps not the best error code */
+		feth_dev_unref(fdev);
+		goto exit;
+	}
+	if (ddi_create_minor_node(feth_dip, linkname, S_IFCHR, dev_instance,
+		DDI_PSEUDO, 0) == DDI_FAILURE) {
+		datalink_id_t tmpid;
+
+		FDBG(CE_CONT, "feth: ddi_create_minor_node failed");
+		if ((err = dls_devnet_destroy(fdev->fd_mh, &tmpid, B_TRUE)) !=
+		    0)
+			cmn_err(CE_WARN,
+			    "feth: unable to remove /dev/net entry");
+		id_free(minor_numbers, dev_instance);
+		feth_dev_unref(fdev);
+		err = DDI_FAILURE;
+		goto exit;
+	}
+	fdev->fd_dev_dl = ((mac_impl_t *) fdev->fd_mh)->mi_phy_dev;
+	fdev->fd_dev_cd = makedevice(getmajor(fdev->fd_dev_dl), dev_instance);
+	fdev->fd_dev_cd_minor = dev_instance;
+
+	ddi_report_dev(devinfo);
+
+	fdev->fd_tx_paused = B_FALSE;
+	mac_link_update(fdev->fd_mh, LINK_STATE_UP);
+	mac_tx_update(fdev->fd_mh);
+	list_insert_tail(&feth_dev_list, fdev);
+
+	/* Always return MAC address back to caller */
+	(void) memcpy(create_arg->sic_mac_addr, fdev->fd_mac_addr,
+	    fdev->fd_mac_len);
+	create_arg->sic_mac_len = fdev->fd_mac_len;
+
+	exit:
+		rw_exit(&feth_dev_lock);
+		return (err);
+}
+
+/* ARGSUSED */
+static int
+feth_ioc_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) {
+	feth_dev_t *fdev;
+	feth_ioc_delete_t *delete_arg = karg;
+	gid_t gid;
+
+	rw_enter(&feth_dev_lock, RW_WRITER);
+	if ((fdev = feth_dev_lookup(delete_arg->sid_link_id)) == NULL) {
+		rw_exit(&feth_dev_lock);
+		return (ENOENT);
+	}
+	gid = crgetzoneid(cred);
+	if (fdev->fd_zoneid != gid) {
+		rw_exit(&feth_dev_lock);
+		feth_dev_unref(fdev);
+		return (ENOENT);
+	}
+	return (i_feth_delete(fdev));
+}
+
+static int
+i_feth_delete(feth_dev_t *fdev) {
+	dev_info_t *di_clean_node;
+	datalink_id_t tmpid;
+	datalink_class_t class;
+	char linkname[MAXLINKNAMELEN];
+	int err;
+
+	if ((err = dls_mgmt_get_linkinfo(fdev->fd_link_id, linkname, &class,
+		NULL, NULL)) != 0)
+		goto fail;
+	ddi_remove_minor_node(feth_dip, linkname);
+	if ((err = dls_devnet_destroy(fdev->fd_mh, &tmpid, B_TRUE)) != 0) {
+		rw_exit(&feth_dev_lock);
+		feth_dev_unref(fdev);
+		return (err);
+	}
+
+	/* Prevent new threads from using the instance */
+	mutex_enter(&fdev->fd_instlock);
+	fdev->fd_flags |= FDF_SHUTDOWN;
+	FDBG(CE_WARN, "waiting for %d threads to finish\n",
+	    fdev->fd_threadcount);
+	/* Wait until all active threads using the instance exit */
+	while (fdev->fd_threadcount > 0) {
+		if (cv_wait_sig(&fdev->fd_threadwait, &fdev->fd_instlock) ==
+		    0) {
+			/* Signaled */
+			mutex_exit(&fdev->fd_instlock);
+			err = EINTR;
+			goto fail;
+		}
+	}
+	mutex_exit(&fdev->fd_instlock);
+	/* Try disabling the MAC */
+	if ((err = mac_disable(fdev->fd_mh)) != 0)
+		goto fail;
+	id_free(minor_numbers, fdev->fd_dev_cd_minor);
+	list_remove(&feth_dev_list, fdev);
+	rw_exit(&feth_dev_lock);
+	feth_dev_unref(fdev);	/* Release lookup ref */
+	/* Releasing the last ref performs fdev/mem free */
+	feth_dev_unref(fdev);
+
+	di_clean_node = ddi_get_parent(feth_dip);
+	if (!di_clean_node) {
+		cmn_err(CE_WARN, "feth: driver has no parent node; "
+		    "cleaning from root of dev tree.");
+		di_clean_node = ddi_root_node();
+	}
+	(void) devfs_clean(di_clean_node, NULL, 0);
+
+	return (err);
+
+	fail:
+		FDBG(CE_WARN, "feth delete feth ioc failure");
+		/* Re-create feth instance */
+		(void) dls_devnet_create(fdev->fd_mh, fdev->fd_link_id,
+		    fdev->fd_zoneid);
+		fdev->fd_flags &= ~FDF_SHUTDOWN;
+		if (ddi_create_minor_node(feth_dip, linkname, S_IFCHR,
+			fdev->fd_dev_cd_minor, DDI_PSEUDO, 0) == DDI_FAILURE)
+			cmn_err(CE_CONT, "feth: ddi_create_minor_node failed");
+		/*
+		 * No previous peer or previous peer no longer
+		 * available so release lookup reference.
+		 */
+		feth_dev_unref(fdev);
+
+		rw_exit(&feth_dev_lock);
+		return (err);
+}
+
+/* ARGSUSED */
+static int
+feth_ioc_info(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) {
+	feth_ioc_info_t *info_arg = karg;
+	feth_dev_t *fdev;
+
+	/* Make sure that the feth link is visible from the caller's zone. */
+	if (!dls_devnet_islinkvisible(info_arg->sii_link_id, crgetzoneid(cred)))
+		return (ENOENT);
+	rw_enter(&feth_dev_lock, RW_READER);
+	if ((fdev = feth_dev_lookup(info_arg->sii_link_id)) == NULL) {
+		rw_exit(&feth_dev_lock);
+		return (ENOENT);
+	}
+	(void) memcpy(info_arg->sii_mac_addr, fdev->fd_mac_addr,
+	    fdev->fd_mac_len);
+	info_arg->sii_mac_len = fdev->fd_mac_len;
+	rw_exit(&feth_dev_lock);
+	feth_dev_unref(fdev);
+	return (0);
+}
+
+static boolean_t
+feth_thread_ref(feth_dev_t *fdev) {
+	mutex_enter(&fdev->fd_instlock);
+	if (fdev->fd_flags & FDF_SHUTDOWN || !(fdev->fd_flags & FDF_STARTED)) {
+		mutex_exit(&fdev->fd_instlock);
+		return (B_FALSE);
+	}
+	fdev->fd_threadcount++;
+	mutex_exit(&fdev->fd_instlock);
+	return (B_TRUE);
+}
+
+static void
+feth_thread_unref(feth_dev_t *fdev) {
+	mutex_enter(&fdev->fd_instlock);
+	if (--fdev->fd_threadcount == 0)
+		cv_broadcast(&fdev->fd_threadwait);
+	mutex_exit(&fdev->fd_instlock);
+}
+
+/* send data outbound to any listening character device */
+static void
+i_feth_tx(void *arg) {
+	feth_dev_t *fdev;
+	mblk_t *mp;
+	feth_stream_t *str;
+	queue_t *q;
+
+	mp = (mblk_t *) arg;
+	fdev = (feth_dev_t *) mp->b_next;
+	if (fdev == NULL) {
+		FDBG(CE_WARN, "feth: feth_tx stream not set");
+		return;
+	}
+	str = (feth_stream_t *) fdev->fd_feth_stream;
+	mp->b_next = NULL;
+	if (fdev->fd_feth_stream == NULL) {
+		FDBG(CE_WARN, "feth: feth_tx error stream is null");
+		freemsg(mp);
+		return;
+	}
+
+	q = str->rq;
+	if (!(q->q_flag & QREADR))	/* if not read queue */
+		q = RD(q);		/* get read queue */
+	putnext(q, mp);			/* send message upstream */
+}
+
+static void
+i_feth_resume_tx(feth_dev_t *fdev) {
+	queue_t *q;
+
+	/*
+	 * if the device has been shutdown, we should not attempt to continue.
+	 * eventually all registered retries will hit this point and any refs
+	 * they held will be released and the feth_dev_t will be de-allocated.
+	 */
+	if (fdev->fd_flags & FDF_SHUTDOWN) {
+		feth_dev_unref(fdev);
+		return;
+	}
+	q = ((feth_stream_t *) (fdev->fd_feth_stream))->rq;
+
+	if (!(q->q_flag & QREADR))	/* if not read queue */
+		q = RD(q);
+	if (1 || canputnext(q)) {
+		fdev->fd_tx_paused = B_FALSE;
+		fdev->fd_tx_paused_size--;
+		feth_dev_unref(fdev);
+		mac_tx_update(fdev->fd_mh);
+		return;
+	} else {
+		/* retry as soon as possible */
+		(void)timeout((void (*)(void *))i_feth_resume_tx, fdev,
+			(drv_usectohz(1)));
+		return;
+	}
+}
+
+/*
+ * TODO: Add properties to set Rx checksum flag behavior.
+ *
+ * o HCK_PARTIALCKSUM.
+ * o HCK_FULLCKSUM_OK.
+ */
+static void
+i_feth_rx(feth_dev_t *fdev, mblk_t *mp) {
+	mac_header_info_t hdr_info;
+
+	mp->b_next = NULL;
+
+	if (feth_thread_ref(fdev) == B_FALSE) {
+		mac_drop_pkt(mp, "device not ready");
+		return;
+	}
+	/* Check for valid packet header */
+	if (mac_header_info(fdev->fd_mh, mp, &hdr_info) != 0) {
+		cmn_err(CE_WARN, "invalid l2 header");
+		mac_drop_pkt(mp, "invalid L2 header");
+		fdev->fd_stats.recv_errors++;
+		goto rx_done;
+	}
+	/*
+	 * When we are NOT in promiscuous mode we only receive
+	 * unicast packets addressed to us and multicast packets that
+	 * MAC clients have requested.
+	 */
+	if (!fdev->fd_promisc && hdr_info.mhi_dsttype !=
+	    MAC_ADDRTYPE_BROADCAST) {
+		if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_UNICAST && bcmp(
+			hdr_info.mhi_daddr, fdev->fd_mac_addr, ETHERADDRL) !=
+		    0) {
+			cmn_err(CE_WARN, "dropping unicast");
+			freemsg(mp);
+			goto rx_done;
+		} else if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_MULTICAST) {
+			mutex_enter(&fdev->fd_instlock);
+			if (mcastaddr_lookup(fdev, hdr_info.mhi_daddr) ==
+			    NULL) {
+				cmn_err(CE_WARN, "dropping multicast");
+				mutex_exit(&fdev->fd_instlock);
+				freemsg(mp);
+				goto rx_done;
+			}
+			mutex_exit(&fdev->fd_instlock);
+		}
+	}
+	/*
+	 * We don't actually calculate and verify the IP header
+	 * checksum because the nature of feth makes it redundant to
+	 * do so. The point is to test the presence of the flags. The
+	 * Tx side will have already populated the checksum field.
+	 */
+	if ((fdev->fd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0)
+		mac_hcksum_set(mp, 0, 0, 0, 0, HCK_IPV4_HDRCKSUM_OK);
+	fdev->fd_stats.recv_count++;
+	fdev->fd_stats.rbytes += msgdsize(mp);
+
+	mac_rx(fdev->fd_mh, NULL, mp);
+
+	rx_done:
+		feth_thread_unref(fdev);
+}
+
+#define	FETH_ULP_CKSUM (HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL)
+
+static mblk_t *
+feth_m_tx(void *arg, mblk_t *mp_chain) {
+	feth_dev_t *fdev = arg;
+	mblk_t *mpnext = mp_chain;
+	mblk_t *nmp, *mp;
+	queue_t *q;
+	mac_emul_t emul = 0;
+
+	if (fdev->fd_cd_state != FDS_ATTACHED) {
+		mac_drop_chain(mp_chain, "feth character device not connected");
+		FDBG(CE_WARN, "feth: feth_m_tx character device not connected");
+		return (NULL);
+	}
+	if (!feth_thread_ref(fdev)) {
+		mac_drop_chain(mp_chain, "feth dev not ready");
+		FDBG(CE_WARN, "feth: feth_tx dev not ready");
+		return (NULL);
+	}
+	
+	/*
+	 * If we cannot queue the packet without going over
+	 * the STREAMS high watermark, return the current
+	 * packet back to MAC for retry later.
+	 */
+	q = ((feth_stream_t *) (fdev->fd_feth_stream))->rq;
+	if (!(q->q_flag & QREADR))	/* if not read queue */
+		q = RD(q);
+	mutex_enter(&fdev->fd_instlock);
+	if (fdev->fd_tx_paused == B_TRUE) {
+		/* already paused. no need to do it again. */
+		mutex_exit(&fdev->fd_instlock);
+		feth_thread_unref(fdev);
+		return (mp_chain);
+	} else if (!canputnext(q)) {
+		fdev->fd_tx_paused = B_TRUE;
+		fdev->fd_tx_paused_size++;
+
+		/*
+		 * while we are waiting to resume, we incrment the refcount,
+		 * so that the device is not removed between "go ahead" checks.
+		 */
+		atomic_inc_32(&fdev->fd_refcount);
+		(void)timeout((void (*)(void *))i_feth_resume_tx, fdev,
+			(drv_usectohz(1)));
+		mutex_exit(&fdev->fd_instlock);
+		feth_thread_unref(fdev);
+		return (mp_chain);
+	}
+	mutex_exit(&fdev->fd_instlock);
+	while ((mp = mpnext) != NULL) {
+		size_t len;
+		size_t size;
+		mblk_t *mp_new;
+		mblk_t *mp_tmp;
+
+		mpnext = mp->b_next;
+		mp->b_next = NULL;
+
+		len = msgdsize(mp);
+
+		/* Pad packet to minimum Ethernet frame size */
+		if (len < ETHERMIN) {
+			size = ETHERMIN - len;
+			mp_new = allocb(size, BPRI_HI);
+			if (mp_new == NULL) {
+				fdev->fd_stats.xmit_errors++;
+				mac_drop_pkt(mp, "allocb failed");
+				return (NULL);
+			}
+			bzero(mp_new->b_wptr, size);
+			mp_new->b_wptr += size;
+
+			mp_tmp = mp;
+			while (mp_tmp->b_cont != NULL)
+				mp_tmp = mp_tmp->b_cont;
+			mp_tmp->b_cont = mp_new;
+			len += size;
+		}
+		/* Pullup packet into a single mblk */
+		if ((nmp = msgpullup(mp, -1)) == NULL) {
+			fdev->fd_stats.xmit_errors++;
+			mac_drop_pkt(mp, "msgpullup failed");
+			return (NULL);
+		} else {
+			mac_hcksum_clone(mp, nmp);
+			freemsg(mp);
+			mp = nmp;
+		}
+		if ((fdev->fd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0)
+			emul |= MAC_IPCKSUM_EMUL;
+		if ((fdev->fd_tx_cksum & FETH_ULP_CKSUM) != 0)
+			emul |= MAC_HWCKSUM_EMUL;
+		if (fdev->fd_lso)
+			emul |= MAC_LSO_EMUL;
+		if (emul != 0)
+			mac_hw_emul(&mp, NULL, NULL, emul);
+		if (mp == NULL) {
+			fdev->fd_stats.xmit_errors++;
+			return (NULL);
+		}
+		/*
+		 * Remember, we are emulating a real NIC here; the
+		 * checksum flags can't make the trip across the link.
+		 */
+		DB_CKSUMFLAGS(mp) = 0;
+
+		mp->b_next = (mblk_t *) fdev;
+		i_feth_tx(mp);
+
+		fdev->fd_stats.xmit_count++;
+		fdev->fd_stats.obytes += len;
+	}
+	feth_thread_unref(fdev);
+	return (NULL);
+}
+
+static void
+feth_m_ioctl(void *arg, queue_t *q, mblk_t *mp) {
+	miocnak(q, mp, 0, ENOTSUP);
+}
+
+static boolean_t
+feth_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) {
+	feth_dev_t *fdev = arg;
+	const uint_t tcp_cksums = HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL;
+
+	switch (cap) {
+	case MAC_CAPAB_HCKSUM: {
+		uint32_t *tx_cksum_flags = cap_data;
+
+		*tx_cksum_flags = fdev->fd_tx_cksum;
+		break;
+	}
+	case MAC_CAPAB_LSO: {
+		mac_capab_lso_t *cap_lso = cap_data;
+
+		if (fdev->fd_lso && (fdev->fd_tx_cksum & HCKSUM_IPHDRCKSUM) !=
+		    0 && (fdev->fd_tx_cksum & tcp_cksums) != 0) {
+			/*
+			 * The LSO configuration is hardwired for now,
+			 * but there's no reason we couldn't also make
+			 * this configurable in the future.
+			 */
+			cap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4;
+			cap_lso->lso_basic_tcp_ipv4.lso_max = FD_LSO_MAXLEN;
+			break;
+		} else {
+			return (B_FALSE);
+		}
+	}
+	default:
+		return (B_FALSE);
+	}
+	return (B_TRUE);
+}
+
+static int
+feth_m_stat(void *arg, uint_t stat, uint64_t *val) {
+	int rval = 0;
+	feth_dev_t *fdev = arg;
+
+	ASSERT(fdev->fd_mh != NULL);
+	switch (stat) {
+	case MAC_STAT_IFSPEED:
+		*val = 100 * 1000000ull;	/* 100 Mbps */
+		break;
+	case MAC_STAT_LINK_STATE:
+		*val = LINK_DUPLEX_FULL;
+		break;
+	case MAC_STAT_LINK_UP:
+		if (fdev->fd_flags & FDF_STARTED)
+			*val = LINK_STATE_UP;
+		else
+			*val = LINK_STATE_DOWN;
+		break;
+	case MAC_STAT_PROMISC:
+	case MAC_STAT_MULTIRCV:
+	case MAC_STAT_MULTIXMT:
+	case MAC_STAT_BRDCSTRCV:
+	case MAC_STAT_BRDCSTXMT:
+		rval = ENOTSUP;
+		break;
+	case MAC_STAT_OPACKETS:
+		*val = fdev->fd_stats.xmit_count;
+		break;
+	case MAC_STAT_OBYTES:
+		*val = fdev->fd_stats.obytes;
+		break;
+	case MAC_STAT_IERRORS:
+		*val = fdev->fd_stats.recv_errors;
+		break;
+	case MAC_STAT_OERRORS:
+		*val = fdev->fd_stats.xmit_errors;
+		break;
+	case MAC_STAT_RBYTES:
+		*val = fdev->fd_stats.rbytes;
+		break;
+	case MAC_STAT_IPACKETS:
+		*val = fdev->fd_stats.recv_count;
+		break;
+	default:
+		rval = ENOTSUP;
+		break;
+	}
+	return (rval);
+}
+
+static int
+feth_m_start(void *arg) {
+	feth_dev_t *fdev = arg;
+
+	fdev->fd_flags |= FDF_STARTED;
+	return (0);
+}
+
+static void
+feth_m_stop(void *arg) {
+	feth_dev_t *fdev = arg;
+
+	fdev->fd_flags &= ~FDF_STARTED;
+}
+
+static int
+feth_m_promisc(void *arg, boolean_t on) {
+	feth_dev_t *fdev = arg;
+
+	fdev->fd_promisc = on;
+	return (0);
+}
+
+/*
+ * Returns matching multicast address enabled on the feth instance.
+ * Assumes feth instance mutex lock is held.
+ */
+static uint8_t *
+mcastaddr_lookup(feth_dev_t *fdev, const uint8_t *addrp) {
+	int idx;
+	uint8_t *maddrptr;
+
+	ASSERT(MUTEX_HELD(&fdev->fd_instlock));
+	maddrptr = fdev->fd_mcastaddrs;
+	for (idx = 0; idx < fdev->fd_mcastaddr_count; idx++) {
+		if (bcmp(maddrptr, addrp, ETHERADDRL) == 0)
+			return (maddrptr);
+		maddrptr += ETHERADDRL;
+	}
+	return (NULL);
+}
+
+/* Add or remove Multicast addresses on feth instance */
+static int
+feth_m_multicst(void *arg, boolean_t add, const uint8_t *addrp) {
+	feth_dev_t *fdev = arg;
+	uint8_t *maddrptr;
+	uint8_t *newbuf;
+	size_t prevsize;
+	size_t newsize;
+	ptrdiff_t len;
+	ptrdiff_t len2;
+
+	alloc_retry:
+	prevsize = fdev->fd_mcastaddr_count * ETHERADDRL;
+	newsize = prevsize + (add ? ETHERADDRL : -ETHERADDRL);
+	newbuf = kmem_alloc(newsize, KM_SLEEP);
+
+	mutex_enter(&fdev->fd_instlock);
+	if (prevsize != (fdev->fd_mcastaddr_count * ETHERADDRL)) {
+		mutex_exit(&fdev->fd_instlock);
+		kmem_free(newbuf, newsize);
+		goto alloc_retry;
+	}
+	maddrptr = mcastaddr_lookup(fdev, addrp);
+	if (!add && maddrptr != NULL) {
+		/* Removing a Multicast address */
+		if (newbuf != NULL) {
+			/* LINTED: E_PTRDIFF_OVERFLOW */
+			len = maddrptr - fdev->fd_mcastaddrs;
+			(void) memcpy(newbuf, fdev->fd_mcastaddrs, len);
+			len2 = prevsize - len - ETHERADDRL;
+			(void) memcpy(newbuf + len, maddrptr + ETHERADDRL,
+			    len2);
+		}
+		fdev->fd_mcastaddr_count--;
+	} else if (add && maddrptr == NULL) {
+		/* Adding a new Multicast address */
+		(void) memcpy(newbuf, fdev->fd_mcastaddrs, prevsize);
+		(void) memcpy(newbuf + prevsize, addrp, ETHERADDRL);
+		fdev->fd_mcastaddr_count++;
+	} else {
+		/* Error: removing a non-existing Multicast address */
+		mutex_exit(&fdev->fd_instlock);
+		kmem_free(newbuf, newsize);
+		FDBG(CE_WARN, "feth: MAC call to remove a "
+		    "Multicast address failed");
+		return (EINVAL);
+	}
+	kmem_free(fdev->fd_mcastaddrs, prevsize);
+	fdev->fd_mcastaddrs = newbuf;
+	mutex_exit(&fdev->fd_instlock);
+	return (0);
+}
+
+static int
+feth_m_unicst(void *arg, const uint8_t *macaddr) {
+	feth_dev_t *fdev = arg;
+
+	(void) memcpy(fdev->fd_mac_addr, macaddr, ETHERADDRL);
+	return (0);
+}
+
+/* ARGSUSED */
+static int
+feth_set_priv_prop_ether(feth_dev_t *fdev, const char *name, const uint_t len,
+    const void *val) {
+	if (strcmp(name, FD_PROP_RX_IP_CKSUM) == 0) {
+		if (val == NULL)
+			return (EINVAL);
+		if (strcmp(val, "off") == 0)
+			fdev->fd_rx_cksum &= ~HCKSUM_IPHDRCKSUM;
+		else if (strcmp(val, "on") == 0)
+			fdev->fd_rx_cksum |= HCKSUM_IPHDRCKSUM;
+		else
+			return (EINVAL);
+		return (0);
+	} else if (strcmp(name, FD_PROP_TX_ULP_CKSUM) == 0) {
+		if (val == NULL)
+			return (EINVAL);
+		/*
+		 * Remember, full and partial checksum are mutually
+		 * exclusive.
+		 */
+		if (strcmp(val, "none") == 0) {
+			fdev->fd_tx_cksum &= ~HCKSUM_INET_FULL_V4;
+		} else if (strcmp(val, "fullv4") == 0) {
+			fdev->fd_tx_cksum &= ~HCKSUM_INET_PARTIAL;
+			fdev->fd_tx_cksum |= HCKSUM_INET_FULL_V4;
+		} else if (strcmp(val, "partial") == 0) {
+			fdev->fd_tx_cksum &= HCKSUM_INET_FULL_V4;
+			fdev->fd_tx_cksum |= HCKSUM_INET_PARTIAL;
+		} else {
+			return (EINVAL);
+		}
+		return (0);
+	} else if (strcmp(name, FD_PROP_TX_IP_CKSUM) == 0) {
+		if (val == NULL)
+			return (EINVAL);
+		if (strcmp(val, "off") == 0)
+			fdev->fd_tx_cksum &= ~HCKSUM_IPHDRCKSUM;
+		else if (strcmp(val, "on") == 0)
+			fdev->fd_tx_cksum |= HCKSUM_IPHDRCKSUM;
+		else
+			return (EINVAL);
+		return (0);
+	} else if (strcmp(name, FD_PROP_LSO) == 0) {
+		if (val == NULL)
+			return (EINVAL);
+		if (strcmp(val, "off") == 0)
+			fdev->fd_lso = B_FALSE;
+		else if (strcmp(val, "on") == 0)
+			fdev->fd_lso = B_TRUE;
+		else
+			return (EINVAL);
+		return (0);
+	}
+	return (ENOTSUP);
+}
+
+static int
+feth_setprop_ether(feth_dev_t *fdev, const char *name, const mac_prop_id_t num,
+    const uint_t len, const void *val) {
+	int err = 0;
+
+	switch (num) {
+	case MAC_PROP_PRIVATE:
+		err = feth_set_priv_prop_ether(fdev, name, len, val);
+		break;
+	default:
+		err = EINVAL;
+		break;
+	}
+	return (err);
+}
+
+static int
+feth_m_setprop(void *arg, const char *name, mac_prop_id_t num, const uint_t len,
+    const void *val) {
+	feth_dev_t *fdev = arg;
+	int err = 0;
+	uint32_t mtu;
+
+	switch (num) {
+	case MAC_PROP_MTU:
+		(void) memcpy(&mtu, val, sizeof (mtu));
+		if (mtu > ETHERMIN && mtu < FETH_MAX_MTU)
+			return (mac_maxsdu_update(fdev->fd_mh, mtu));
+		else
+			return (EINVAL);
+	default:
+		break;
+	}
+	err = feth_setprop_ether(fdev, name, num, len, val);
+
+	/*
+	 * We may have modified the configuration of hardware
+	 * offloads. Make sure to renegotiate capabilities with the
+	 * upstream clients.
+	 */
+	mac_capab_update(fdev->fd_mh);
+	return (err);
+}
+
+static int
+feth_get_priv_prop_ether(const feth_dev_t *fdev, const char *name, const uint_t
+    len, void *val) {
+	int ret;
+	char *value;
+
+	if (strcmp(name, FD_PROP_RX_IP_CKSUM) == 0) {
+		if ((fdev->fd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0)
+			value = "on";
+		else
+			value = "off";
+	} else if (strcmp(name, FD_PROP_TX_ULP_CKSUM) == 0) {
+		if ((fdev->fd_tx_cksum & HCKSUM_INET_FULL_V4) != 0)
+			value = "fullv4";
+		else if ((fdev->fd_tx_cksum & HCKSUM_INET_PARTIAL) != 0)
+			value = "partial";
+		else
+			value = "none";
+	} else if (strcmp(name, FD_PROP_TX_IP_CKSUM) == 0) {
+		if ((fdev->fd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0)
+			value = "on";
+		else
+			value = "off";
+	} else if (strcmp(name, FD_PROP_LSO) == 0) {
+		value = fdev->fd_lso ? "on" : "off";
+	} else {
+		return (ENOTSUP);
+	}
+	ret = snprintf(val, len, "%s", value);
+	if (ret < 0 || ret >= len)
+		return (EOVERFLOW);
+	return (0);
+}
+
+static int
+feth_getprop_ether(const feth_dev_t *fdev, const char *name, const mac_prop_id_t
+    num, const uint_t len, void *val) {
+	int err = 0;
+
+	switch (num) {
+	case MAC_PROP_PRIVATE:
+		err = feth_get_priv_prop_ether(fdev, name, len, val);
+		break;
+	default:
+		err = ENOTSUP;
+		break;
+	}
+	return (err);
+}
+
+static int
+feth_m_getprop(void *arg, const char *name, const mac_prop_id_t num, const
+    uint_t len, void *val) {
+	const feth_dev_t *fdev = arg;
+	int err = 0;
+
+	err = feth_getprop_ether(fdev, name, num, len, val);
+
+	return (err);
+}
+
+static void
+feth_priv_propinfo_ether(const char *name, mac_prop_info_handle_t prh) {
+	if (strcmp(name, FD_PROP_RX_IP_CKSUM) == 0 || strcmp(name,
+		FD_PROP_TX_ULP_CKSUM) == 0 || strcmp(name,
+	    FD_PROP_TX_IP_CKSUM) ==
+	    0 || strcmp(name, FD_PROP_LSO) == 0)
+		mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
+	if (strcmp(name, FD_PROP_TX_ULP_CKSUM) == 0)
+		mac_prop_info_set_default_str(prh, "none");
+	if (strcmp(name, FD_PROP_RX_IP_CKSUM) == 0 || strcmp(name,
+		FD_PROP_TX_IP_CKSUM) == 0 || strcmp(name, FD_PROP_LSO) == 0)
+		mac_prop_info_set_default_str(prh, "off");
+}
+
+static void
+feth_propinfo_ether(const char *name, const mac_prop_id_t num,
+    mac_prop_info_handle_t prh) {
+	switch (num) {
+	case MAC_PROP_PRIVATE:
+		feth_priv_propinfo_ether(name, prh);
+		break;
+	}
+}
+
+static void
+feth_m_propinfo(void *arg, const char *name, const mac_prop_id_t num, const
+    mac_prop_info_handle_t prh) {
+	feth_propinfo_ether(name, num, prh);
+}
+
+static void
+feth_ioc_ack(queue_t *wq, mblk_t *mp, int ack, int ret, int err) {
+	struct iocblk *ioc = (struct iocblk *) mp->b_rptr;
+
+	mp->b_datap->db_type = ack;
+	ioc->ioc_count = 0;
+	ioc->ioc_error = err;
+	ioc->ioc_rval = ret;
+
+	qreply(wq, mp);
+}
+
+static void
+feth_ioctl(queue_t *wq, mblk_t *mp) {
+/* Handle IOCTLs */
+	struct iocblk *ioc = (struct iocblk *) mp->b_rptr;
+	feth_stream_t *str = (feth_stream_t *) wq->q_ptr;
+	int val;
+	boolean_t on_val;
+
+	switch (ioc->ioc_cmd) {
+	case FETH_STR_IOC_PROMISC:
+		val = *(int *) mp->b_cont->b_rptr;
+		if (val == 0)
+			on_val = B_FALSE;
+		else
+			on_val = B_TRUE;
+		str->feth_dev->fd_promisc = on_val;
+		feth_ioc_ack(wq, mp, M_IOCACK, 0, 0);
+		break;
+	default:
+		feth_ioc_ack(wq, mp, M_IOCNAK, 0, EINVAL);
+	}
+}
+
+static int
+feth_rq_close(queue_t *rq, int flag, cred_t *credp) {
+	/*
+	 * NOTE:
+	 * also need to close the stream if the device is downed.
+	 */
+	feth_stream_t **prev, *tmp, *str;
+
+	/* If the first sizeof(int) bytes of the queue private data
+	 * is our magic value, we know it is the character device. 
+	 *
+	 * Otherwise, it is the mac device and we shuffle the request off 
+	 */
+	if ((((feth_stream_t *) rq->q_ptr)->magic) != FD_MAGIC_COOKIE)
+		return (read_queue_qclose(rq, flag, credp));
+	 
+	str = (feth_stream_t *) rq->q_ptr;
+	str->feth_dev->fd_cd_state = FDS_UNATTACHED;
+	str->feth_dev->fd_feth_stream = NULL;
+	qprocsoff(rq);
+
+	feth_dev_unref(str->feth_dev);	/* release the stream's ref to dev */
+	
+	/*
+	 * NOTE:
+	 * this functionality does not work properly with respect to dladm
+	 * and should ultimately be reworked or removed. it is left here
+	 * in order to foster a discussion on the functionality.
+	 */
+	if (str->feth_dev->fd_flags & FDF_SINGLESHOT) {
+		rw_enter(&feth_dev_lock, RW_WRITER);
+		if (i_feth_delete(str->feth_dev))
+			cmn_err(CE_WARN,
+			    "feth: failed to delete single-shot device");
+		rw_exit(&feth_dev_lock);
+	}
+
+	/* Unlink Stream from streams list and free it */
+	for (prev = &feth_stream; (tmp = *prev); prev = &tmp->s_next)
+		if (tmp == str)
+			break;
+	*prev = tmp->s_next;
+	kmem_free((char *) str, sizeof (feth_stream_t));
+
+	rq->q_ptr = WR(rq)->q_ptr = NULL;
+
+	return (0);
+}
+
+static int
+feth_rq_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *credp) {
+	register feth_stream_t *str, **prev;
+	int minordev;
+	feth_dev_t *fdev_tmp;
+	int _major, _minor;
+	int err;
+	char linkname[MAXLINKNAMELEN];
+	datalink_class_t class;
+
+	rw_enter(&feth_dev_lock, RW_WRITER);
+	if ((fdev_tmp = feth_dev_lookup_cd(*dev)) == NULL) {
+		rw_exit(&feth_dev_lock);
+		return (read_queue_qopen(rq, dev, flag, sflag, credp));
+	}
+	FDBG(CE_WARN, "feth: open on known character device.");
+	rw_exit(&feth_dev_lock);
+
+	str = NULL;
+	/* Determine minor device number */
+	prev = &feth_stream;
+
+	minordev = getminor(*dev);
+	DBG(CE_WARN, "feth: open minor %d", minordev);
+	if ((err = dls_mgmt_get_linkinfo(fdev_tmp->fd_link_id, linkname, &class,
+		NULL, NULL)) != 0)
+		cmn_err(CE_WARN, "feth: failed to get linkinfo");
+	FDBG(CE_WARN, "open got matching link name %s", linkname);
+	if (!rq->q_ptr) {
+		str = (feth_stream_t *) kmem_zalloc(sizeof (feth_stream_t),
+			KM_SLEEP);
+		str->magic = FD_MAGIC_COOKIE;
+		str->rq = rq;
+		str->minor = minordev;
+		str->state = DL_UNATTACHED;
+		str->feth_dev = fdev_tmp;
+		fdev_tmp->fd_feth_stream = (char *) str;
+		str->s_next = *prev;
+		*prev = str;
+		rq->q_ptr = WR(rq)->q_ptr = (char *) str;
+	}
+	fdev_tmp->fd_cd_state = FDS_ATTACHED;
+	FDBG(CE_WARN, "feth: nopen str %p minor %d", str, minordev);
+	qprocson(rq);
+	return (0);
+}
+
+static int
+feth_wq_put(queue_t *wq, mblk_t *mp) {
+	feth_stream_t *str;
+
+	if ((((feth_stream_t *) wq->q_ptr)->magic) != FD_MAGIC_COOKIE)
+		return (write_queue_putp(wq, mp));
+	
+	str = (feth_stream_t *) wq->q_ptr;
+	
+	switch (DB_TYPE(mp)) {
+	case M_DATA:
+		i_feth_rx(str->feth_dev, mp);
+		break;
+
+	case M_PROTO:
+	case M_PCPROTO:
+		FDBG(CE_WARN, "feth: got PCPROTO/PROTO message");
+		break;
+	case M_IOCTL:
+		feth_ioctl(wq, mp);
+		break;
+
+	case M_FLUSH:
+		FDBG(CE_WARN, "feth: got FLUSH message");
+		/* Flush queues */
+		if (*mp->b_rptr & FLUSHW) {
+			flushq(wq, FLUSHALL);
+			*mp->b_rptr &= ~FLUSHW;
+		}
+		if (*mp->b_rptr & FLUSHR)
+			qreply(wq, mp);
+		else
+			freemsg(mp);
+		break;
+
+	default:
+		freemsg(mp);
+		break;
+	}
+	return (0);
+}
+
+static int
+feth_wq_srv(queue_t *wq) {
+	mblk_t *mp;
+
+	if ((((feth_stream_t *) wq->q_ptr)->magic) != FD_MAGIC_COOKIE)
+		return (write_queue_srvp(wq));
+	
+	feth_stream_t *str = (feth_stream_t *) wq->q_ptr;
+
+	while ((mp = getq(wq)))
+		switch (DB_TYPE(mp)) {
+		case M_DATA:
+			i_feth_rx(str->feth_dev, mp);
+			break;
+		default:
+			FDBG(CE_WARN, "feth: wq_srv str unknown mssage type %p",
+			    str);
+			freemsg(mp);
+			break;
+		}
+	return (0);
+}
--- /dev/null	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/io/feth/feth.conf	Fri Jun 12 16:14:59 2020
@@ -0,0 +1,25 @@
+#
+# 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 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+name="feth" parent="pseudo" instance=0;
--- /dev/null	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/io/feth/feth_impl.h	Fri Jun 12 16:14:59 2020
@@ -0,0 +1,122 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ * Copyright 2019 Joyent, Inc.
+ */
+
+#ifndef	_SYS_FETH_IMPL_H
+#define	_SYS_FETH_IMPL_H
+
+#include <sys/types.h>
+#include <sys/list.h>
+#include <sys/mutex.h>
+#include <sys/mac.h>
+#include <sys/net80211.h>
+#include <net/feth.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+struct feth_dev;
+
+typedef struct feth_stats {
+	uint64_t	rbytes;
+	uint64_t	obytes;
+	uint64_t	xmit_errors;
+	uint64_t	xmit_count;
+	uint64_t	recv_count;
+	uint64_t	recv_errors;
+} feth_stats_t;
+
+typedef struct feth_dev {
+	list_node_t	fd_listnode;
+	datalink_id_t	fd_link_id;
+	zoneid_t	fd_zoneid;	/* zone where created */
+	uint_t		fd_flags;	/* Device flags SDF_* */
+	uint_t		fd_refcount;
+	dev_t		fd_dev_dl; /* Device ID of datalink end of feth */
+	dev_t		fd_dev_cd; /* Device ID of character end of feth */
+	uint_t		fd_dev_cd_minor; /* Minor number of character dev */
+	u_long		fd_cd_state;
+	char		*fd_feth_stream;	/* feth_stream_t * */
+	/* Num of active threads using the device */
+	boolean_t	fd_tx_paused;
+	uint_t		fd_tx_paused_size;
+	uint_t		fd_threadcount;
+	kcondvar_t	fd_threadwait;
+	mac_handle_t	fd_mh;
+	boolean_t	fd_promisc;
+	kmutex_t	fd_instlock;
+	/* Num of multicast addresses stored in fd_mcastaddrs */
+	uint_t		fd_mcastaddr_count;
+	/* Multicast address list stored in single buffer */
+	uint8_t		*fd_mcastaddrs;
+	uint_t		fd_mac_len;
+	uchar_t		fd_mac_addr[MAXMACADDRLEN];
+	feth_stats_t	fd_stats;
+
+	/* Capabilities */
+	uint_t		fd_rx_cksum;
+	uint_t		fd_tx_cksum;
+	boolean_t	fd_lso;
+} feth_dev_t;
+
+#define	FD_MAGIC_COOKIE 0xfe1f1d07
+
+/* Stream structure, one per Stream */
+typedef struct feth_stream {
+	uint32_t magic;
+	struct feth_stream	*s_next;	/* next in streams list */
+	struct feth_stream	*p_next;	/* next in ppa list */
+	queue_t *rq;			/* pointer to rq */
+
+	feth_dev_t *feth_dev;		/* assigned feth device */
+	u_long flags;			/* flags */
+	u_long state;			/* DL state */
+	u_long minor;			/* minor device number */
+} feth_stream_t;
+
+/* Feth dladm private properties. */
+#define	FD_PROP_RX_IP_CKSUM	"_rx_ipv4_cksum"
+#define	FD_PROP_TX_ULP_CKSUM	"_tx_ulp_cksum"
+#define	FD_PROP_TX_IP_CKSUM	"_tx_ipv4_cksum"
+#define	FD_PROP_LSO		"_lso"
+
+/* Feth MAC device flags */
+#define	FDF_SHUTDOWN	0x00000001	/* Device started, allow ops */
+#define	FDF_STARTED	0x00000002	/* Device started, allow ops */
+#define	FDF_SINGLESHOT	FETH_FLAG_SINGLESHOT /* Device should be deleted upon */
+						/* character device close */
+/* feth STREAMS device states */
+#define	FDS_UNATTACHED	0x00000001	/* Character device not opened. */
+#define	FDS_ATTACHED	0x00000002	/* Character device is opened. */
+
+#define	FETH_MAX_MTU	9000		/* Max MTU supported by feth driver */
+#define	FD_LSO_MAXLEN	65535		/* Max LSO supported by feth driver */
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _SYS_FETH_IMPL_H */
--- old/usr/src/uts/common/net/Makefile	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/net/Makefile	Fri Jun 12 16:14:59 2020
@@ -28,7 +28,7 @@
 
 HDRS=	af.h if.h if_arp.h if_dl.h if_types.h route.h pfkeyv2.h pfpolicy.h \
 	ppp-comp.h ppp_defs.h pppio.h vjcompress.h sppptun.h pppoe.h radix.h \
-	wpa.h simnet.h bridge.h bridge_impl.h trill.h
+	wpa.h simnet.h feth.h bridge.h bridge_impl.h trill.h
 
 ROOTDIRS= $(ROOT)/usr/include/net
 
--- /dev/null	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/net/feth.h	Fri Jun 12 16:14:59 2020
@@ -0,0 +1,76 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef	_SYS_FETH_H
+#define	_SYS_FETH_H
+
+#include <sys/types.h>
+#include <sys/mac.h>
+#include <sys/dld_ioc.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/* Simnet IOCTL commands handled via DLD driver */
+#define	FETH_IOC_CREATE	FETHIOC(1)
+#define	FETH_IOC_DELETE	FETHIOC(2)
+#define	FETH_IOC_INFO		FETHIOC(3)
+#define	FETH_IOC_MODIFY	FETHIOC(4)
+
+#define FETH_STR_IOC(X)		((uint8_t)(X))
+#define FETH_STR_IOC_PROMISC FETH_STR_IOC(1)
+
+#define FETH_FLAG_SINGLESHOT 0x04
+
+typedef struct feth_ioc_create {
+	datalink_id_t	sic_link_id;
+	uint_t		sic_mac_len;
+	uint_t		sic_flags;
+	uchar_t		sic_mac_addr[MAXMACADDRLEN];
+} feth_ioc_create_t;
+
+typedef struct feth_ioc_delete {
+	datalink_id_t	sid_link_id;
+	uint_t		sid_flags;
+} feth_ioc_delete_t;
+
+typedef struct feth_ioc_info {
+	datalink_id_t	sii_link_id;
+	uint_t		sii_mac_len;
+	uint_t		sii_flags;
+	uchar_t		sii_mac_addr[MAXMACADDRLEN];
+} feth_ioc_info_t;
+
+typedef struct feth_ioc_modify {
+	datalink_id_t	sim_link_id;
+	uint_t		sim_flags;
+} feth_ioc_modify_t;
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _SYS_FETH_H */
--- old/usr/src/uts/common/sys/dld_ioc.h	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/sys/dld_ioc.h	Fri Jun 12 16:14:59 2020
@@ -56,6 +56,7 @@
 #define	AGGR_IOC	0x0A66
 #define	VNIC_IOC	0x0171
 #define	SIMNET_IOC	0x5132
+#define	FETH_IOC	0x7A71
 #define	IPTUN_IOC	0x454A
 #define	BRIDGE_IOC	0xB81D
 #define	IBPART_IOC	0x6171
@@ -65,6 +66,7 @@
 #define	AGGRIOC(cmdid)		DLD_IOC_CMD(AGGR_IOC, (cmdid))
 #define	VNICIOC(cmdid)		DLD_IOC_CMD(VNIC_IOC, (cmdid))
 #define	SIMNETIOC(cmdid)	DLD_IOC_CMD(SIMNET_IOC, (cmdid))
+#define	FETHIOC(cmdid)		DLD_IOC_CMD(FETH_IOC, (cmdid))
 #define	IPTUNIOC(cmdid)		DLD_IOC_CMD(IPTUN_IOC, (cmdid))
 #define	BRIDGEIOC(cmdid)	DLD_IOC_CMD(BRIDGE_IOC, (cmdid))
 #define	IBPARTIOC(cmdid)	DLD_IOC_CMD(IBPART_IOC, (cmdid))
--- old/usr/src/uts/common/sys/dls_mgmt.h	Fri Jun 12 16:14:59 2020
+++ new/usr/src/uts/common/sys/dls_mgmt.h	Fri Jun 12 16:14:59 2020
@@ -46,13 +46,15 @@
 	DATALINK_CLASS_SIMNET		= 0x20,
 	DATALINK_CLASS_BRIDGE		= 0x40,
 	DATALINK_CLASS_IPTUN		= 0x80,
-	DATALINK_CLASS_PART		= 0x100
+	DATALINK_CLASS_PART		= 0x100,
+	DATALINK_CLASS_FETH		= 0x200
 } datalink_class_t;
 
 #define	DATALINK_CLASS_ALL	(DATALINK_CLASS_PHYS |	\
 	DATALINK_CLASS_VLAN | DATALINK_CLASS_AGGR | DATALINK_CLASS_VNIC | \
 	DATALINK_CLASS_ETHERSTUB | DATALINK_CLASS_SIMNET | \
-	DATALINK_CLASS_BRIDGE | DATALINK_CLASS_IPTUN | DATALINK_CLASS_PART)
+	DATALINK_CLASS_BRIDGE | DATALINK_CLASS_IPTUN | DATALINK_CLASS_PART | \
+	DATALINK_CLASS_FETH)
 
 /*
  * A combination of flags and media.
--- old/usr/src/uts/intel/Makefile.intel	Fri Jun 12 16:15:00 2020
+++ new/usr/src/uts/intel/Makefile.intel	Fri Jun 12 16:15:00 2020
@@ -412,6 +412,7 @@
 DRV_KMODS	+= iptun
 DRV_KMODS	+= vmxnet3s
 DRV_KMODS	+= pvscsi
+DRV_KMODS	+= feth
 
 #
 # HYPER-V driver
--- /dev/null	Fri Jun 12 16:15:00 2020
+++ new/usr/src/uts/intel/feth/Makefile	Fri Jun 12 16:15:00 2020
@@ -0,0 +1,81 @@
+#
+# 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 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE	= ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE		= feth
+OBJECTS		= $(FETH_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+#
+# Define targets
+#
+ALL_TARGET	= $(BINARY) $(SRC_CONFILE)
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+# Overrides
+#
+CFLAGS		+= $(CCVERBOSE)
+LDFLAGS		+= -dy -Ndrv/dld -Nmisc/mac -Nmisc/dls -Ndrv/random
+
+CERRWARN	+= -_gcc=-Wno-switch
+
+# needs work
+$(OBJS_DIR)/feth.o := SMOFF += index_overflow
+
+#
+#	Default build targets.
+#
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+#
+#	Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ
--- /dev/null	Fri Jun 12 16:15:00 2020
+++ new/usr/src/uts/sparc/feth/Makefile	Fri Jun 12 16:15:00 2020
@@ -0,0 +1,83 @@
+#
+# 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 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE	= ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE		= simnet
+OBJECTS		= $(FETH_OBJS:%=$(OBJS_DIR)/%)
+LINTS		= $(FETH_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/sparc/Makefile.sparc
+
+#
+# Define targets
+#
+ALL_TARGET	= $(BINARY) $(SRC_CONFILE)
+LINT_TARGET	= $(MODULE).lint
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+# Overrides
+#
+CFLAGS		+= $(CCVERBOSE)
+LDFLAGS		+= -dy -Ndrv/dld -Nmisc/mac -Nmisc/dls -Ndrv/random
+
+CERRWARN	+= -_gcc=-Wno-switch
+
+#
+#	Default build targets.
+#
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+lint:		$(LINT_DEPS)
+
+modlintlib:	$(MODLINTLIB_DEPS)
+
+clean.lint:	$(CLEAN_LINT_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+#
+#	Include common targets.
+#
+include $(UTSBASE)/sparc/Makefile.targ
