Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 20 Jan 2012 23:35:55 +0100
From:      Stefan Bethke <stb@lassitu.de>
To:        FreeBSD Net <freebsd-net@freebsd.org>
Subject:   Ethernet Switch Framework, the other one
Message-ID:  <76C5AEDF-39BA-4824-B8F8-BBD451200EF2@lassitu.de>

next in thread | raw e-mail | index | archive | help

[-- Attachment #1 --]
If you have followed a number of threads on -embedded, -mips, and -arch, you'll be aware that I've been busy attempting to come up with a switch control interface of my own.  The goal is to combine what Aleksandr has done over the past couple of months with some of what I have come up with.

The reason I did start from scratch were some architectural concerns that I had with Aleksandr's code.  Instead of idle talk, I decided to do a proof of concept implementation, and lo and behold, I did run into some interesting problems.  I'll reply to Aleksandr's post momentarily, but first I wanted to get my code out for people to review, and use the chance try and explain the problem space and why I decided to approach it the way I did.

The attached patches contain:
- etherswitchcfg.patch the userland utility
- miiproxy.patch a device that can act as a proxy between an ethernet driver and miibus
- etherswitch-complete: the above plus two switch drivers, a new kernel config and related files.

All that can also be obtained from http://gitorious.org/~stb/freebsd/stb-adrianchadd-freebsd-work in the branch work/ath.

The target systems for these switch device drivers are typical WLAN routers, which usually contain a one-chip switch controller.  All of them support port-based as well as .1q VLANs, as well as many other interesting features.  OpenWrt has a framework for configuring aspects of these switch controllers, but the original firmware of these devices usually does not expose the settings for the user.

For my proof of concept, I've put together two drivers that know how to configure an RTL8366RB and AR8x16 switches.  The RTL8366RB is configured through I2C.  The system's ethernet interface is connected directly to a switch port (back-to-back MAC connection via GMII).  The Atheros family of switch controllers is configured through an MDIO bus attachment; the switch controller uses the frame format, but reassigns phy address bits as register address bits.

Both switch drivers implement the etherswitch_if.m interface, and act as busses and automatically attach matching drivers as children.  I've written a simple cdev driver that allows the command line utility to open a control device and submit ioctls that are translated into etherswitch_if methods.

newbus attachments look like this (arrow indicates parent attachment with interface implemented)

              iicbus_if    etherswitch_if
... <--- iicbus <--- rtl833rb <--- etherswitch

        mdio_if   mdio_if    etherswitch_if
argemdio <--- mdio <--- arswitch <--- etherswitch

All switch controller chips contain PHYs (older models have external PHYs).  These are controlled in the usual manner via MDIO.  The MDIO master is in the switch controller.  Both switch drivers export the miibus_if.m methods readreg and writereg, and attach an miibus for each one of the switch ports that have a PHY.  This allows the command line utility to submit ifmedia ioctls to query and configure the individual ports.

I've gone through a number of iterations to make sure that the interdependencies of the drivers are as limited as possible, and that attachments happen automatically, without code nor hints, whereever possible.

MII Proxy

In some of the systems, one system ethernet interface is connected to a PHY inside the switch controller; the PHY acts completely independent from the switch.  However, the PHYs MDIO slave is connected not to the ethernet controllers MDIO master, but to the switch MDIO master.  This means solving two problems: allowing a driver control both an ethernet driver and use another MDIO bus driver to communicate with the PHY at the same time.

The miibus code unfortunately is extremely tailored to the one use case that was present so far; I'll go into more detail about what was stopping me from just using miibus directly in a separate post.  To leave miibus unmodified while maintaining functionality and not reimplementing miibus or the phy drivers, I've decided to create a proxy driver.  This proxy driver solves two issues: be an appropriate parent for miibus, and provide the capability to have a reference on a different device (and call methods there) than the parent.  A new bus driver, mdio, provides the MDIO readreg and writereg methods separate from the miibus driver, thus allowing automatic attachment of parent and children.

For a TL-MR3420 router, devinfo shows this device graph (filtered for interface and switch components):
nexus0
  arge0
    miiproxy0
      miibus4
        ukphy4
  arge1
  argemdio0
    mdio0
      arswitch0
        miibus0
          ukphy0
        miibus1
          ukphy1
        miibus2
          ukphy2
        miibus3
          ukphy3
        etherswitch0
        mdio1
          mdioproxy1
      mdioproxy0

arge(4) has been extended with a separate mdio host driver that has a new mdio bus driver attached.  The Atheros switch driver arswitch0 is attached to that bus; the four switch ports with PHYs on them show up as miibus0 to miibus3 and phy0 to phy3, respectively.  arge0, which has the MII connection (but not the MDIO connection) to PHY for in the switch controller, has miiproxy0 attached to it, which in turn hosts miibus4 and phy4.  miiproxy0 has a hidden connection to mdioproxy1 that was established through a hint on arge0; miiproxy0 uses mdioproxy1's parent to issue readreg and writereg calls on behalf of miibus.  Note mdioproxy0 above which was automatically attached (but is not used).

The only hint required to plug this together is
hint.arge.0.mdio=mdioproxy1

The simple and straightforward attachment follows the newbus design, and allows additional drivers to be automatically attached by newbus.  The etherswitch driver provides one possible consumer of the etherswitch_if API, but other are possible; for example, most switches allow .1d control frames to be punted to the CPU port, so our STP code could be used to construct a driver that implements spanning tree for a switch.


Stefan

-- 
Stefan Bethke <stb@lassitu.de>   Fon +49 151 14070811



[-- Attachment #2 --]
diff --git a/sbin/Makefile b/sbin/Makefile
index f9ba4ca..0cb421f 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -20,6 +20,7 @@ SUBDIR=adjkerntz \
 	dump \
 	dumpfs \
 	dumpon \
+	etherswitchcfg \
 	ffsinfo \
 	fsck \
 	fsck_ffs \
diff --git a/sbin/etherswitchcfg/Makefile b/sbin/etherswitchcfg/Makefile
new file mode 100644
index 0000000..d21c88b
--- /dev/null
+++ b/sbin/etherswitchcfg/Makefile
@@ -0,0 +1,9 @@
+#	@(#)Makefile	5.4 (Berkeley) 6/5/91
+# $FreeBSD: head/sbin/comcontrol/Makefile 198236 2009-10-19 16:00:24Z ru $
+
+PROG=	etherswitchcfg
+MAN=	etherswitchcfg.8
+SRCS=	etherswitchcfg.c ifmedia.c
+CFLAGS+= -I${.CURDIR}/../../sys
+
+.include <bsd.prog.mk>
diff --git a/sbin/etherswitchcfg/etherswitchcfg.8 b/sbin/etherswitchcfg/etherswitchcfg.8
new file mode 100644
index 0000000..538950c
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.8
@@ -0,0 +1,114 @@
+.\" $FreeBSD$
+.Dd December 15, 2011
+.Dt ETHERSWITCHCFG 8
+.Os
+.Sh NAME
+.Nm etherswitchcfg
+.Nd configure a built-in Ethernet switch
+.Sh SYNOPSIS
+.Nm
+.Op Fl "f control file"
+.Ar info
+.Nm
+.Op Fl "f control file"
+.Ar phy
+.Ar phy.register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar port%d
+.Ar command parameter
+.Nm
+.Op Fl "f control file"
+.Ar reg
+.Ar register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar vlangroup%d
+.Ar command parameter
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure an Ethernet switch built into the system.
+.Nm
+accepts a number of options:
+.Bl -tag -width ".Fl f" -compact
+.It Fl "f control file"
+Specifies the
+.Xr etherswitch 4
+control file that represents the switch to be configured.
+It defaults to
+.Li /dev/etherswitch0 .
+.It Fl m
+When reporting port information, also list available media options for
+that port.
+.It Fl v
+Produce more verbose output.
+Without this flag, lines that represent inactive or empty configuration
+options are omitted.
+.El
+.Ss phy
+The phy command provides access to the registers of the PHYs attached
+to or integrated into the switch controller.
+PHY registers are specified as phy.register,
+where
+.Ar phy
+is usually the port number, and
+.Ar register
+is the register number.
+Both can be provided as decimal, octal or hexadecimal numbers in any of the formats
+understood by
+.Xr strtol 4 .
+To set the register value, use the form instance.register=value.
+.Ss port
+The port command selects one of the ports of the switch.
+It supports the following commands:
+.Bl -tag -width ".Ar vlangroup number" -compact
+.It Ar vlangroup number
+Sets the VLAN group number that is used to process incoming frames that are not tagged.
+.It Ar media mediaspec
+Specifies the physical media configuration to be configured for a port.
+.It Ar mediaopt mediaoption
+Specifies a list of media options for a port. See
+.Xr ifconfig 8
+for details on
+.Ar media and 
+.Ar mediaopt .
+.El
+.Ss reg
+The reg command provides access to the registers of the switch controller.
+.Ss vlangroup
+The vlangroup command selects one of the VLAN groups for configuration.
+It supports the following commands:
+.Bl -tag -width ".Ar vlangroup" -compact
+.It Ar vlan VID
+Sets the VLAN ID (802.1q VID) for this VLAN group.
+Frames transmitted on tagged member ports of this group will be tagged
+with this VID.
+Incoming frames carrying this tag will be forwarded according to the
+configuration of this VLAN group.
+.It Ar members port,...
+Configures which ports are to be a member of this VLAN group.
+The port numbers are given as a comma-separated list.
+Each port can optionally be followed by
+.Dq t
+to indicate that frames on this port are tagged.
+.El
+.Sh FILES
+.Bl -tag -width /dev/etherswitch? -compact
+.It Pa /dev/etherswitch?
+Control file for the ethernet switch driver.
+.El
+.Sh EXAMPLES
+Configure VLAN group 1 with a VID of 2 and makes ports 0 and 5 members,
+while excluding all other ports.
+Port 5 will send and receive tagged frames, while port 0 will be untagged.
+Incoming untagged frames on port 0 are assigned to vlangroup1.
+.Dl # etherswitchcfg vlangroup1 vlan 2 members 0,5t port0 vlangroup 1
+.Sh SEE ALSO
+.Xr etherswitch 4
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Stefan Bethke
diff --git a/sbin/etherswitchcfg/etherswitchcfg.c b/sbin/etherswitchcfg/etherswitchcfg.c
new file mode 100644
index 0000000..e6129f3
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.c
@@ -0,0 +1,511 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <dev/etherswitch/etherswitch.h>
+
+int	get_media_subtype(int, const char *);
+int	get_media_mode(int, const char *);
+int	get_media_options(int, const char *);
+int	lookup_media_word(struct ifmedia_description *, const char *);
+void    print_media_word(int, int);
+void    print_media_word_ifconfig(int);
+
+/* some constants */
+#define IEEE802DOT1Q_VID_MAX	4094
+#define IFMEDIAREQ_NULISTENTRIES	256
+
+enum cmdmode {
+	MODE_NONE = 0,
+	MODE_PORT,
+	MODE_VLANGROUP,
+	MODE_REGISTER,
+	MODE_PHYREG
+};
+
+struct cfg {
+	int					fd;
+	int					verbose;
+	int					mediatypes;
+	const char			*controlfile;
+	etherswitch_info_t	info;
+	enum cmdmode		mode;
+	int					unit;
+};
+
+struct cmds {
+	enum cmdmode	mode;
+	const char		*name;
+	int				args;
+	void 			(*f)(struct cfg *, char *argv[]);
+};
+struct cmds cmds[];
+
+
+static void usage(void);
+
+static int
+read_register(struct cfg *cfg, int r)
+{
+	struct etherswitch_reg er;
+	
+	er.reg = r;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
+	return (er.val);
+}
+
+static void
+write_register(struct cfg *cfg, int r, int v)
+{
+	struct etherswitch_reg er;
+	
+	er.reg = r;
+	er.val = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
+}
+
+static int
+read_phyregister(struct cfg *cfg, int phy, int reg)
+{
+	struct etherswitch_phyreg er;
+	
+	er.phy = phy;
+	er.reg = reg;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
+	return (er.val);
+}
+
+static void
+write_phyregister(struct cfg *cfg, int phy, int reg, int val)
+{
+	struct etherswitch_phyreg er;
+	
+	er.phy = phy;
+	er.reg = reg;
+	er.val = val;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
+}
+
+static void
+set_port_vlangroup(struct cfg *cfg, char *argv[])
+{
+	int v;
+	etherswitch_port_t p;
+	
+	v = strtol(argv[1], NULL, 0);
+	if (v < 0 || v >= cfg->info.es_nvlangroups)
+		errx(EX_USAGE, "vlangroup must be between 0 and %d", cfg->info.es_nvlangroups-1);
+	p.es_port = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	p.es_vlangroup = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_media(struct cfg *cfg, char *argv[])
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int subtype;
+	
+	bzero(&p, sizeof(p));
+	p.es_port = cfg->unit;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
+	p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
+	        IFM_TYPE(ifm_ulist[0]) | subtype;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_mediaopt(struct cfg *cfg, char *argv[])
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int options;
+	
+	bzero(&p, sizeof(p));
+	p.es_port = cfg->unit;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
+	if (options == -1)
+		errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
+	if (options & IFM_HDX) {
+		p.es_ifr.ifr_media &= ~IFM_FDX;
+		options &= ~IFM_HDX;
+	}
+	p.es_ifr.ifr_media |= options;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_vlangroup_vid(struct cfg *cfg, char *argv[])
+{
+	int v;
+	etherswitch_vlangroup_t vg;
+	
+	v = strtol(argv[1], NULL, 0);
+	if (v < 0 || v >= IEEE802DOT1Q_VID_MAX)
+		errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
+	vg.es_vlangroup = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	vg.es_vid = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static void
+set_vlangroup_members(struct cfg *cfg, char *argv[])
+{
+	etherswitch_vlangroup_t vg;
+	int member, untagged;
+	char *c, *d;
+	int v;
+	
+	member = untagged = 0;
+	if (strcmp(argv[1], "none") != 0) {
+		for (c=argv[1]; *c; c=d) {
+			v = strtol(c, &d, 0);
+			if (d == c)
+				break;
+			if (v < 0 || v >= cfg->info.es_nports)
+				errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
+			if (d[0] == ',' || d[0] == '\0' ||
+				((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
+				if (d[0] == 't' || d[0] == 'T') {
+					untagged &= ~ETHERSWITCH_PORTMASK(v);
+					d++;
+				} else
+					untagged |= ETHERSWITCH_PORTMASK(v);
+				member |= ETHERSWITCH_PORTMASK(v);
+				d++;
+			} else
+				errx(EX_USAGE, "Invalid members specification \"%s\"", d);
+		}
+	}
+	vg.es_vlangroup = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	vg.es_member_ports = member;
+	vg.es_untagged_ports = untagged;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static int
+set_register(struct cfg *cfg, char *arg)
+{
+	int a, v;
+	char *c;
+	
+	a = strtol(arg, &c, 0);
+	if (c==arg)
+		return (1);
+	if (*c == '=') {
+		v = strtol(c+1, NULL, 0);
+		write_register(cfg, a, v);
+	}
+	printf("\treg 0x%04x=0x%04x\n", a, read_register(cfg, a));
+	return (0);
+}
+
+static int
+set_phyregister(struct cfg *cfg, char *arg)
+{
+	int phy, reg, val;
+	char *c, *d;
+	
+	phy = strtol(arg, &c, 0);
+	if (c==arg)
+		return (1);
+	if (*c != '.')
+		return (1);
+	d = c+1;
+	reg = strtol(d, &c, 0);
+	if (d == c)
+		return (1);
+	if (*c == '=') {
+		val = strtol(c+1, NULL, 0);
+		write_phyregister(cfg, phy, reg, val);
+	}
+	printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
+	return (0);
+}
+
+static void
+print_port(struct cfg *cfg, int port)
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int i;
+
+	bzero(&p, sizeof(p));
+	p.es_port = port;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	printf("port%d:\n", port);
+	printf("\tvlangroup: %d\n", p.es_vlangroup);
+	printf("\tmedia: ");
+	print_media_word(p.es_ifmr.ifm_current, 1);
+	if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
+		putchar(' ');
+		putchar('(');
+		print_media_word(p.es_ifmr.ifm_active, 0);
+		putchar(')');
+	}
+	putchar('\n');
+	printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
+	if (cfg->mediatypes) {
+		printf("\tsupported media:\n");
+		if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
+			p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+		for (i=0; i<p.es_ifmr.ifm_count; i++) {
+			printf("\t\tmedia ");
+			print_media_word(ifm_ulist[i], 0);
+			putchar('\n');
+		}
+	}
+}
+
+static void
+print_vlangroup(struct cfg *cfg, int vlangroup)
+{
+	etherswitch_vlangroup_t vg;
+	int i, comma;
+	
+	vg.es_vlangroup = vlangroup;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	if (cfg->verbose == 0 && vg.es_member_ports == 0)
+		return;
+	printf("vlangroup%d:\n", vlangroup);
+	printf("\tvlan: %d\n", vg.es_vid);
+	printf("\tmembers ");
+	comma = 0;
+	if (vg.es_member_ports != 0)
+		for (i=0; i<cfg->info.es_nports; i++) {
+			if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
+				if (comma)
+					printf(",");
+				printf("%d", i);
+				if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
+					printf("t");
+				comma = 1;
+			}
+		}
+	else
+		printf("none");
+	printf("\n");
+}
+
+static void
+print_info(struct cfg *cfg)
+{
+	const char *c;
+	int i;
+	
+	c = strrchr(cfg->controlfile, '/');
+	if (c != NULL)
+		c = c + 1;
+	else
+		c = cfg->controlfile;
+	if (cfg->verbose)
+		printf("%s: %s with %d ports and %d VLAN groups\n",
+			c, cfg->info.es_name, cfg->info.es_nports, cfg->info.es_nvlangroups);
+	for (i=0; i<cfg->info.es_nports; i++) {
+		print_port(cfg, i);
+	}
+	for (i=0; i<cfg->info.es_nvlangroups; i++) {
+		print_vlangroup(cfg, i);
+	}
+}
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: etherswitchctl\n");
+	exit(EX_USAGE);
+}
+
+static void
+newmode(struct cfg *cfg, enum cmdmode mode)
+{
+	if (mode == cfg->mode)
+		return;
+	switch (cfg->mode) {
+	case MODE_NONE:
+		break;
+	case MODE_PORT:
+		print_port(cfg, cfg->unit);
+		break;
+	case MODE_VLANGROUP:
+		print_vlangroup(cfg, cfg->unit);
+		break;
+	case MODE_REGISTER:
+	case MODE_PHYREG:
+		break;
+	}
+	cfg->mode = mode;
+}
+
+int
+main(int argc, char *argv[])
+{
+	int ch;
+	struct cfg cfg;
+	int i;
+	
+	bzero(&cfg, sizeof(cfg));
+	cfg.controlfile = "/dev/etherswitch0";
+	while ((ch = getopt(argc, argv, "f:mv?")) != -1)
+		switch(ch) {
+		case 'f':
+			cfg.controlfile = optarg;
+			break;
+		case 'm':
+			cfg.mediatypes++;
+			break;
+		case 'v':
+			cfg.verbose++;
+			break;
+		case '?':
+			/* FALLTHROUGH */
+		default:
+			usage();
+		}
+	argc -= optind;
+	argv += optind;
+	cfg.fd = open(cfg.controlfile, O_RDONLY);
+	if (cfg.fd < 0)
+		err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
+	if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
+	if (argc == 0) {
+		print_info(&cfg);
+		return (0);
+	}
+	cfg.mode = MODE_NONE;
+	while (argc > 0) {
+		switch(cfg.mode) {
+		case MODE_NONE:
+			if (strcmp(argv[0], "info") == 0) {
+				print_info(&cfg);
+			} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
+				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
+					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports);
+				newmode(&cfg, MODE_PORT);
+			} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
+				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
+					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nvlangroups);
+				newmode(&cfg, MODE_VLANGROUP);
+			} else if (strcmp(argv[0], "phy") == 0) {
+				newmode(&cfg, MODE_PHYREG);
+			} else if (strcmp(argv[0], "reg") == 0) {
+				newmode(&cfg, MODE_REGISTER);
+			} else {
+				errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
+			}
+			break;
+		case MODE_PORT:
+		case MODE_VLANGROUP:
+			for(i=0; cmds[i].name != NULL; i++) {
+				if (cfg.mode == cmds[i].mode && strcmp(argv[0], cmds[i].name) == 0
+					&& argc >= cmds[i].args) {
+					(cmds[i].f)(&cfg, argv);
+					argc -= cmds[i].args;
+					argv += cmds[i].args;
+					break;
+				}
+			}
+			if (cmds[i].name == NULL) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		case MODE_REGISTER:
+			if (set_register(&cfg, argv[0]) != 0) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		case MODE_PHYREG:
+			if (set_phyregister(&cfg, argv[0]) != 0) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		}
+		argc--;
+		argv++;
+	}
+	/* switch back to command mode to print configuration for last command */
+	newmode(&cfg, MODE_NONE);
+	close(cfg.fd);
+	return (0);
+}
+
+struct cmds cmds[] = {
+	{ MODE_PORT, "vlangroup", 1, set_port_vlangroup },
+	{ MODE_PORT, "media", 1, set_port_media },
+	{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
+	{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
+	{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
+	{ 0, NULL, 0, NULL }
+};
diff --git a/sbin/etherswitchcfg/ifmedia.c b/sbin/etherswitchcfg/ifmedia.c
new file mode 100644
index 0000000..49881fe
--- /dev/null
+++ b/sbin/etherswitchcfg/ifmedia.c
@@ -0,0 +1,812 @@
+/*	$NetBSD: ifconfig.c,v 1.34 1997/04/21 01:17:58 lukem Exp $	*/
+/* $FreeBSD: head/sbin/ifconfig/ifmedia.c 221954 2011-05-15 12:51:00Z marius $ */
+
+/*
+ * Copyright (c) 1997 Jason R. Thorpe.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project
+ *	by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * based on sbin/ifconfig/ifmedia.c r221954
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void	domediaopt(const char *, int, int);
+int	get_media_subtype(int, const char *);
+int	get_media_mode(int, const char *);
+int	get_media_options(int, const char *);
+int	lookup_media_word(struct ifmedia_description *, const char *);
+void	print_media_word(int, int);
+void	print_media_word_ifconfig(int);
+
+#if 0
+static struct ifmedia_description *get_toptype_desc(int);
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int);
+static struct ifmedia_description *get_subtype_desc(int,
+    struct ifmedia_type_to_subtype *ttos);
+
+#define	IFM_OPMODE(x) \
+	((x) & (IFM_IEEE80211_ADHOC | IFM_IEEE80211_HOSTAP | \
+	 IFM_IEEE80211_IBSS | IFM_IEEE80211_WDS | IFM_IEEE80211_MONITOR | \
+	 IFM_IEEE80211_MBSS))
+#define	IFM_IEEE80211_STA	0
+
+static void
+media_status(int s)
+{
+	struct ifmediareq ifmr;
+	int *media_list, i;
+
+	(void) memset(&ifmr, 0, sizeof(ifmr));
+	(void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name));
+
+	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+		/*
+		 * Interface doesn't support SIOC{G,S}IFMEDIA.
+		 */
+		return;
+	}
+
+	if (ifmr.ifm_count == 0) {
+		warnx("%s: no media types?", name);
+		return;
+	}
+
+	media_list = (int *)malloc(ifmr.ifm_count * sizeof(int));
+	if (media_list == NULL)
+		err(1, "malloc");
+	ifmr.ifm_ulist = media_list;
+
+	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
+		err(1, "SIOCGIFMEDIA");
+
+	printf("\tmedia: ");
+	print_media_word(ifmr.ifm_current, 1);
+	if (ifmr.ifm_active != ifmr.ifm_current) {
+		putchar(' ');
+		putchar('(');
+		print_media_word(ifmr.ifm_active, 0);
+		putchar(')');
+	}
+
+	putchar('\n');
+
+	if (ifmr.ifm_status & IFM_AVALID) {
+		printf("\tstatus: ");
+		switch (IFM_TYPE(ifmr.ifm_active)) {
+		case IFM_ETHER:
+		case IFM_ATM:
+			if (ifmr.ifm_status & IFM_ACTIVE)
+				printf("active");
+			else
+				printf("no carrier");
+			break;
+
+		case IFM_FDDI:
+		case IFM_TOKEN:
+			if (ifmr.ifm_status & IFM_ACTIVE)
+				printf("inserted");
+			else
+				printf("no ring");
+			break;
+
+		case IFM_IEEE80211:
+			if (ifmr.ifm_status & IFM_ACTIVE) {
+				/* NB: only sta mode associates */
+				if (IFM_OPMODE(ifmr.ifm_active) == IFM_IEEE80211_STA)
+					printf("associated");
+				else
+					printf("running");
+			} else
+				printf("no carrier");
+			break;
+		}
+		putchar('\n');
+	}
+
+	if (ifmr.ifm_count > 0 && supmedia) {
+		printf("\tsupported media:\n");
+		for (i = 0; i < ifmr.ifm_count; i++) {
+			printf("\t\t");
+			print_media_word_ifconfig(media_list[i]);
+			putchar('\n');
+		}
+	}
+
+	free(media_list);
+}
+
+struct ifmediareq *
+ifmedia_getstate(int s)
+{
+	static struct ifmediareq *ifmr = NULL;
+	int *mwords;
+
+	if (ifmr == NULL) {
+		ifmr = (struct ifmediareq *)malloc(sizeof(struct ifmediareq));
+		if (ifmr == NULL)
+			err(1, "malloc");
+
+		(void) memset(ifmr, 0, sizeof(struct ifmediareq));
+		(void) strncpy(ifmr->ifm_name, name,
+		    sizeof(ifmr->ifm_name));
+
+		ifmr->ifm_count = 0;
+		ifmr->ifm_ulist = NULL;
+
+		/*
+		 * We must go through the motions of reading all
+		 * supported media because we need to know both
+		 * the current media type and the top-level type.
+		 */
+
+		if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0) {
+			err(1, "SIOCGIFMEDIA");
+		}
+
+		if (ifmr->ifm_count == 0)
+			errx(1, "%s: no media types?", name);
+
+		mwords = (int *)malloc(ifmr->ifm_count * sizeof(int));
+		if (mwords == NULL)
+			err(1, "malloc");
+  
+		ifmr->ifm_ulist = mwords;
+		if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0)
+			err(1, "SIOCGIFMEDIA");
+	}
+
+	return ifmr;
+}
+
+static void
+setifmediacallback(int s, void *arg)
+{
+	struct ifmediareq *ifmr = (struct ifmediareq *)arg;
+	static int did_it = 0;
+
+	if (!did_it) {
+		ifr.ifr_media = ifmr->ifm_current;
+		if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0)
+			err(1, "SIOCSIFMEDIA (media)");
+		free(ifmr->ifm_ulist);
+		free(ifmr);
+		did_it = 1;
+	}
+}
+
+static void
+setmedia(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int subtype;
+
+	ifmr = ifmedia_getstate(s);
+
+	/*
+	 * We are primarily concerned with the top-level type.
+	 * However, "current" may be only IFM_NONE, so we just look
+	 * for the top-level type in the first "supported type"
+	 * entry.
+	 *
+	 * (I'm assuming that all supported media types for a given
+	 * interface will be the same top-level type..)
+	 */
+	subtype = get_media_subtype(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & IFM_IMASK) |
+	    IFM_TYPE(ifmr->ifm_ulist[0]) | subtype;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+	domediaopt(val, 0, s);
+}
+
+static void
+unsetmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+	domediaopt(val, 1, s);
+}
+
+static void
+domediaopt(const char *val, int clear, int s)
+{
+	struct ifmediareq *ifmr;
+	int options;
+
+	ifmr = ifmedia_getstate(s);
+
+	options = get_media_options(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = ifmr->ifm_current;
+	if (clear)
+		ifr.ifr_media &= ~options;
+	else {
+		if (options & IFM_HDX) {
+			ifr.ifr_media &= ~IFM_FDX;
+			options &= ~IFM_HDX;
+		}
+		ifr.ifr_media |= options;
+	}
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediainst(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int inst;
+
+	ifmr = ifmedia_getstate(s);
+
+	inst = atoi(val);
+	if (inst < 0 || inst > (int)IFM_INST_MAX)
+		errx(1, "invalid media instance: %s", val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & ~IFM_IMASK) | inst << IFM_ISHIFT;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediamode(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int mode;
+
+	ifmr = ifmedia_getstate(s);
+
+	mode = get_media_mode(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & ~IFM_MMASK) | mode;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+#endif
+
+/**********************************************************************
+ * A good chunk of this is duplicated from sys/net/ifmedia.c
+ **********************************************************************/
+
+static struct ifmedia_description ifm_type_descriptions[] =
+    IFM_TYPE_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_descriptions[] =
+    IFM_SUBTYPE_ETHERNET_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_aliases[] =
+    IFM_SUBTYPE_ETHERNET_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] =
+    IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_descriptions[] =
+    IFM_SUBTYPE_TOKENRING_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_aliases[] =
+    IFM_SUBTYPE_TOKENRING_ALIASES;
+
+static struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] =
+    IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_descriptions[] =
+    IFM_SUBTYPE_FDDI_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_aliases[] =
+    IFM_SUBTYPE_FDDI_ALIASES;
+
+static struct ifmedia_description ifm_subtype_fddi_option_descriptions[] =
+    IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_aliases[] =
+    IFM_SUBTYPE_IEEE80211_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_aliases[] =
+    IFM_SUBTYPE_IEEE80211_MODE_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_descriptions[] =
+    IFM_SUBTYPE_ATM_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_atm_aliases[] =
+    IFM_SUBTYPE_ATM_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
+    IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_descriptions[] =
+    IFM_SUBTYPE_SHARED_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_aliases[] =
+    IFM_SUBTYPE_SHARED_ALIASES;
+
+static struct ifmedia_description ifm_shared_option_descriptions[] =
+    IFM_SHARED_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_shared_option_aliases[] =
+    IFM_SHARED_OPTION_ALIASES;
+
+struct ifmedia_type_to_subtype {
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} subtypes[5];
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} options[4];
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} modes[3];
+};
+
+/* must be in the same order as IFM_TYPE_DESCRIPTIONS */
+static struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_ethernet_descriptions[0], 0 },
+			{ &ifm_subtype_ethernet_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_ethernet_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_tokenring_descriptions[0], 0 },
+			{ &ifm_subtype_tokenring_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_tokenring_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_fddi_descriptions[0], 0 },
+			{ &ifm_subtype_fddi_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_fddi_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_ieee80211_descriptions[0], 0 },
+			{ &ifm_subtype_ieee80211_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_ieee80211_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_subtype_ieee80211_mode_descriptions[0], 0 },
+			{ &ifm_subtype_ieee80211_mode_aliases[0], 0 },
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_atm_descriptions[0], 0 },
+			{ &ifm_subtype_atm_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_atm_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+};
+
+int
+get_media_subtype(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int rval, i;
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media type 0x%x", type);
+
+	for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+		rval = lookup_media_word(ttos->subtypes[i].desc, val);
+		if (rval != -1)
+			return (rval);
+	}
+	errx(1, "unknown media subtype: %s", val);
+	/*NOTREACHED*/
+}
+
+int
+get_media_mode(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int rval, i;
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media mode 0x%x", type);
+
+	for (i = 0; ttos->modes[i].desc != NULL; i++) {
+		rval = lookup_media_word(ttos->modes[i].desc, val);
+		if (rval != -1)
+			return (rval);
+	}
+	return -1;
+}
+
+int
+get_media_options(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	char *optlist, *optptr;
+	int option = 0, i, rval = 0;
+
+	/* We muck with the string, so copy it. */
+	optlist = strdup(val);
+	if (optlist == NULL)
+		err(1, "strdup");
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media type 0x%x", type);
+
+	/*
+	 * Look up the options in the user-provided comma-separated
+	 * list.
+	 */
+	optptr = optlist;
+	for (; (optptr = strtok(optptr, ",")) != NULL; optptr = NULL) {
+		for (i = 0; ttos->options[i].desc != NULL; i++) {
+			option = lookup_media_word(ttos->options[i].desc, optptr);
+			if (option != -1)
+				break;
+		}
+		if (option == 0)
+			errx(1, "unknown option: %s", optptr);
+		rval |= option;
+	}
+
+	free(optlist);
+	return (rval);
+}
+
+int
+lookup_media_word(struct ifmedia_description *desc, const char *val)
+{
+
+	for (; desc->ifmt_string != NULL; desc++)
+		if (strcasecmp(desc->ifmt_string, val) == 0)
+			return (desc->ifmt_word);
+
+	return (-1);
+}
+
+static struct ifmedia_description *get_toptype_desc(int ifmw)
+{
+	struct ifmedia_description *desc;
+
+	for (desc = ifm_type_descriptions; desc->ifmt_string != NULL; desc++)
+		if (IFM_TYPE(ifmw) == desc->ifmt_word)
+			break;
+
+	return desc;
+}
+
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int ifmw)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (IFM_TYPE(ifmw) == desc->ifmt_word)
+			break;
+
+	return ttos;
+}
+
+static struct ifmedia_description *get_subtype_desc(int ifmw, 
+    struct ifmedia_type_to_subtype *ttos)
+{
+	int i;
+	struct ifmedia_description *desc;
+
+	for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+		if (ttos->subtypes[i].alias)
+			continue;
+		for (desc = ttos->subtypes[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
+				return desc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct ifmedia_description *get_mode_desc(int ifmw, 
+    struct ifmedia_type_to_subtype *ttos)
+{
+	int i;
+	struct ifmedia_description *desc;
+
+	for (i = 0; ttos->modes[i].desc != NULL; i++) {
+		if (ttos->modes[i].alias)
+			continue;
+		for (desc = ttos->modes[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (IFM_MODE(ifmw) == desc->ifmt_word)
+				return desc;
+		}
+	}
+
+	return NULL;
+}
+
+void
+print_media_word(int ifmw, int print_toptype)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int seen_option = 0, i;
+
+	/* Find the top-level interface type. */
+	desc = get_toptype_desc(ifmw);
+	ttos = get_toptype_ttos(ifmw);
+	if (desc->ifmt_string == NULL) {
+		printf("<unknown type>");
+		return;
+	} else if (print_toptype) {
+		printf("%s", desc->ifmt_string);
+	}
+
+	/*
+	 * Don't print the top-level type; it's not like we can
+	 * change it, or anything.
+	 */
+
+	/* Find subtype. */
+	desc = get_subtype_desc(ifmw, ttos);
+	if (desc == NULL) {
+		printf("<unknown subtype>");
+		return;
+	}
+
+	if (print_toptype)
+		putchar(' ');
+
+	printf("%s", desc->ifmt_string);
+
+	if (print_toptype) {
+		desc = get_mode_desc(ifmw, ttos);
+		if (desc != NULL && strcasecmp("autoselect", desc->ifmt_string))
+			printf(" mode %s", desc->ifmt_string);
+	}
+
+	/* Find options. */
+	for (i = 0; ttos->options[i].desc != NULL; i++) {
+		if (ttos->options[i].alias)
+			continue;
+		for (desc = ttos->options[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (ifmw & desc->ifmt_word) {
+				if (seen_option == 0)
+					printf(" <");
+				printf("%s%s", seen_option++ ? "," : "",
+				    desc->ifmt_string);
+			}
+		}
+	}
+	printf("%s", seen_option ? ">" : "");
+
+	if (print_toptype && IFM_INST(ifmw) != 0)
+		printf(" instance %d", IFM_INST(ifmw));
+}
+
+void
+print_media_word_ifconfig(int ifmw)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int seen_option = 0, i;
+
+	/* Find the top-level interface type. */
+	desc = get_toptype_desc(ifmw);
+	ttos = get_toptype_ttos(ifmw);
+	if (desc->ifmt_string == NULL) {
+		printf("<unknown type>");
+		return;
+	}
+
+	/*
+	 * Don't print the top-level type; it's not like we can
+	 * change it, or anything.
+	 */
+
+	/* Find subtype. */
+	desc = get_subtype_desc(ifmw, ttos);
+	if (desc == NULL) {
+		printf("<unknown subtype>");
+		return;
+	}
+
+	printf("media %s", desc->ifmt_string);
+
+	desc = get_mode_desc(ifmw, ttos);
+	if (desc != NULL)
+		printf(" mode %s", desc->ifmt_string);
+
+	/* Find options. */
+	for (i = 0; ttos->options[i].desc != NULL; i++) {
+		if (ttos->options[i].alias)
+			continue;
+		for (desc = ttos->options[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (ifmw & desc->ifmt_word) {
+				if (seen_option == 0)
+					printf(" mediaopt ");
+				printf("%s%s", seen_option++ ? "," : "",
+				    desc->ifmt_string);
+			}
+		}
+	}
+
+	if (IFM_INST(ifmw) != 0)
+		printf(" instance %d", IFM_INST(ifmw));
+}
+
+/**********************************************************************
+ * ...until here.
+ **********************************************************************/

[-- Attachment #3 --]
diff --git a/sys/dev/etherswitch/mdio.c b/sys/dev/etherswitch/mdio.c
new file mode 100644
index 0000000..9302075
--- /dev/null
+++ b/sys/dev/etherswitch/mdio.c
@@ -0,0 +1,156 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/systm.h>
+
+#include <dev/etherswitch/mdio.h>
+
+#include "mdio_if.h"
+
+static void
+mdio_identify(driver_t *driver, device_t parent)
+{
+	if (device_find_child(parent, mdio_driver.name, -1) == NULL)
+		BUS_ADD_CHILD(parent, 0, mdio_driver.name, -1);
+}
+
+static int
+mdio_probe(device_t dev)
+{
+	device_set_desc(dev, "MDIO");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+mdio_attach(device_t dev)
+{
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	return (bus_generic_attach(dev));
+}
+
+static int
+mdio_detach(device_t dev)
+{
+	bus_generic_detach(dev);
+	return (0);
+}
+
+static int
+mdio_readreg(device_t dev, int phy, int reg)
+{
+	return MDIO_READREG(device_get_parent(dev), phy, reg);
+}
+
+static int
+mdio_writereg(device_t dev, int phy, int reg, int val)
+{
+	return MDIO_WRITEREG(device_get_parent(dev), phy, reg, val);
+}
+
+static int
+mdio_print_child(device_t dev, device_t child)
+{
+	int retval;
+
+	retval = bus_print_child_header(dev, child);
+	retval += bus_print_child_footer(dev, child);
+
+	return (retval);
+}
+
+static int
+mdio_read_ivar(device_t dev, device_t child __unused, int which,
+    uintptr_t *result)
+{
+	struct miibus_ivars *ivars;
+
+	ivars = device_get_ivars(dev);
+	switch (which) {
+	default:
+		return (ENOENT);
+	}
+	return (0);
+}
+
+static int
+mdio_child_pnpinfo_str(device_t dev __unused, device_t child, char *buf,
+    size_t buflen)
+{
+	buf[0] = '\0';
+	return (0);
+}
+
+static int
+mdio_child_location_str(device_t dev __unused, device_t child, char *buf,
+    size_t buflen)
+{
+	buf[0] = '\0';
+	return (0);
+}
+
+static void
+mdio_hinted_child(device_t dev, const char *name, int unit)
+{
+	device_add_child(dev, name, unit);
+}
+
+static device_method_t mdio_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_identify,	mdio_identify),
+	DEVMETHOD(device_probe,		mdio_probe),
+	DEVMETHOD(device_attach,	mdio_attach),
+	DEVMETHOD(device_detach,	mdio_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	/* bus interface */
+	DEVMETHOD(bus_print_child,	mdio_print_child),
+	DEVMETHOD(bus_read_ivar,	mdio_read_ivar),
+	DEVMETHOD(bus_child_pnpinfo_str, mdio_child_pnpinfo_str),
+	DEVMETHOD(bus_child_location_str, mdio_child_location_str),
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	DEVMETHOD(bus_hinted_child,	mdio_hinted_child),
+
+	/* MDIO access */
+	DEVMETHOD(mdio_readreg,		mdio_readreg),
+	DEVMETHOD(mdio_writereg,	mdio_writereg),
+
+	DEVMETHOD_END
+};
+
+driver_t mdio_driver = {
+	"mdio",
+	mdio_methods,
+	0
+};
+
+devclass_t mdio_devclass;
+
diff --git a/sys/dev/etherswitch/mdio.h b/sys/dev/etherswitch/mdio.h
new file mode 100644
index 0000000..52eddbd
--- /dev/null
+++ b/sys/dev/etherswitch/mdio.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DEV_MII_MDIO_H_
+#define	_DEV_MII_MDIO_H_
+
+extern driver_t mdio_driver;
+extern devclass_t mdio_devclass;
+
+#endif
diff --git a/sys/dev/etherswitch/mdio_if.m b/sys/dev/etherswitch/mdio_if.m
new file mode 100644
index 0000000..9aedd92
--- /dev/null
+++ b/sys/dev/etherswitch/mdio_if.m
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+#include <sys/bus.h>
+
+INTERFACE mdio;
+
+#
+# Read register from device on MDIO bus
+#
+METHOD int readreg {
+	device_t		dev;
+	int			phy;
+	int			reg;
+};
+
+#
+# Write register to device on MDIO bus
+#
+METHOD int writereg {
+	device_t		dev;
+	int			phy;
+	int			reg;
+	int			val;
+};
diff --git a/sys/dev/etherswitch/miiproxy.c b/sys/dev/etherswitch/miiproxy.c
new file mode 100644
index 0000000..2791082
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.c
@@ -0,0 +1,437 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <dev/etherswitch/miiproxy.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+
+
+MALLOC_DECLARE(M_MIIPROXY);
+MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
+
+driver_t miiproxy_driver;
+driver_t mdioproxy_driver;
+
+struct miiproxy_softc {
+	device_t	parent;
+	device_t	proxy;
+	device_t	mdio;
+	miiproxy_attach_callback_t	attach_callback;
+	void		*attach_arg;
+};
+
+struct mdioproxy_softc {
+};
+
+/*
+ * The rendevous data structures and functions allow two device endpoints to
+ * match up, so that the proxy endpoint can be associated with a target
+ * endpoint.  The proxy has to know the device name of the target that it
+ * wants to associate with, for example through a hint.  The rendevous code
+ * makes no assumptions about the devices that want to meet.
+ */
+struct rendevous_entry;
+
+enum rendevous_op {
+	RENDEVOUS_ATTACH,
+	RENDEVOUS_DETACH
+};
+
+typedef int (*rendevous_callback_t)(enum rendevous_op,
+    struct rendevous_entry *);
+
+static SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead =
+    SLIST_HEAD_INITIALIZER(rendevoushead);
+
+struct rendevous_endpoint {
+	device_t		device;
+	const char		*name;
+	rendevous_callback_t	callback;
+};
+
+struct rendevous_entry {
+	SLIST_ENTRY(rendevous_entry)	entries;
+	struct rendevous_endpoint	proxy;
+	struct rendevous_endpoint	target;
+};
+
+/*
+ * Call the callback routines for both the proxy and the target.  If either
+ * returns an error, undo the attachment.
+ */
+static int
+rendevous_attach(struct rendevous_entry *e, struct rendevous_endpoint *ep)
+{
+	int error;
+	
+	error = e->proxy.callback(RENDEVOUS_ATTACH, e);
+	if (error == 0)
+		error = e->target.callback(RENDEVOUS_ATTACH, e);
+	if (error != 0) {
+		e->proxy.callback(RENDEVOUS_DETACH, e);
+		ep->device = NULL;
+		ep->callback = NULL;
+	}
+	return (error);
+}
+
+/*
+ * Create an entry for the proxy in the rendevous list.  The name parameter
+ * indicates the name of the device that is the target endpoint for this
+ * rendevous.  The callback will be invoked as soon as the target is
+ * registered: either immediately if the target registered itself earlier,
+ * or once the target registers.
+ */
+static int
+rendevous_register_proxy(device_t dev, const char *name,
+    rendevous_callback_t callback)
+{
+	struct rendevous_entry *e;
+
+	KASSERT(callback != NULL, ("callback must not be NULL"));
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (strcmp(name, e->target.name) == 0) {
+			/* the target is already attached */
+		    	e->proxy.name = device_get_nameunit(dev);
+		    	e->proxy.device = dev;
+		    	e->proxy.callback = callback;
+			return (rendevous_attach(e, &e->proxy));
+		}
+	}
+	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+    	e->proxy.name = device_get_nameunit(dev);
+    	e->proxy.device = dev;
+    	e->proxy.callback = callback;
+	e->target.name = name;
+	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
+	return (0);
+}
+
+/*
+ * Create an entry in the rendevous list for the target.  The callback will
+ * be called once the proxy has registered.
+ */
+static int
+rendevous_register_target(device_t dev, rendevous_callback_t callback)
+{
+	struct rendevous_entry *e;
+	const char *name;
+	
+	KASSERT(callback != NULL, ("callback must not be NULL"));
+	name = device_get_nameunit(dev);
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (strcmp(name, e->target.name) == 0) {
+			e->target.device = dev;
+			e->target.callback = callback;
+			return (rendevous_attach(e, &e->target));
+		}
+	}
+	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+	e->target.name = name;
+    	e->target.device = dev;
+	e->target.callback = callback;
+	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
+	return (0);
+}
+
+/*
+ * Remove the registration for the proxy.
+ */
+static int
+rendevous_unregister_proxy(device_t dev)
+{
+	struct rendevous_entry *e;
+	int error = 0;
+	
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (e->proxy.device == dev) {
+			if (e->target.device == NULL) {
+				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
+				free(e, M_MIIPROXY);
+				return (0);
+			} else {
+				e->proxy.callback(RENDEVOUS_DETACH, e);
+				e->target.callback(RENDEVOUS_DETACH, e);
+			}
+			e->proxy.device = NULL;
+			e->proxy.callback = NULL;
+			return (error);
+		}
+	}
+	return (ENOENT);
+}
+
+/*
+ * Remove the registration for the target.
+ */
+static int
+rendevous_unregister_target(device_t dev)
+{
+	struct rendevous_entry *e;
+	int error = 0;
+	
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (e->target.device == dev) {
+			if (e->proxy.device == NULL) {
+				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
+				free(e, M_MIIPROXY);
+				return (0);
+			} else {
+				e->proxy.callback(RENDEVOUS_DETACH, e);
+				e->target.callback(RENDEVOUS_DETACH, e);
+			}
+			e->target.device = NULL;
+			e->target.callback = NULL;
+			return (error);
+		}
+	}
+	return (ENOENT);
+}
+
+/*
+ * Functions of the proxy that is interposed between the ethernet interface
+ * driver and the miibus device.
+ */
+
+static int
+miiproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
+{
+	struct miiproxy_softc *sc = device_get_softc(rendevous->proxy.device);
+
+	switch (op) {
+	case RENDEVOUS_ATTACH:
+		sc->mdio = device_get_parent(rendevous->target.device);
+		(sc->attach_callback)(sc->attach_arg);
+		break;
+	case RENDEVOUS_DETACH:
+		sc->mdio = NULL;
+		/* detach miibus */
+	}
+	return (0);
+}
+
+static int
+miiproxy_probe(device_t dev)
+{
+	device_set_desc(dev, "MII/MDIO proxy, MII side");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+miiproxy_attach(device_t dev)
+{
+	/*
+	 * The ethernet interface needs to call mii_attach_proxy() to pass
+	 * the relevant parameters for rendevous with the MDIO target.
+	 */
+	return (bus_generic_attach(dev));
+}
+
+static int
+miiproxy_detach(device_t dev)
+{
+	rendevous_unregister_proxy(dev);
+	bus_generic_detach(dev);
+	return (0);
+}
+
+static int
+miiproxy_readreg(device_t dev, int phy, int reg)
+{
+	struct miiproxy_softc *sc = device_get_softc(dev);
+
+	if (sc->mdio != NULL)
+		return (MDIO_READREG(sc->mdio, phy, reg));
+	return (-1);
+}
+
+static int
+miiproxy_writereg(device_t dev, int phy, int reg, int val)
+{
+	struct miiproxy_softc *sc = device_get_softc(dev);
+
+	if (sc->mdio != NULL)
+		return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
+	return (-1);
+}
+
+static void
+miiproxy_statchg(device_t dev)
+{
+	MIIBUS_STATCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_linkchg(device_t dev)
+{
+	MIIBUS_LINKCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_mediainit(device_t dev)
+{
+	MIIBUS_MEDIAINIT(device_get_parent(dev));
+}
+
+/*
+ * Functions for the MDIO target device driver.
+ */
+static int
+mdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
+{
+	return (0);
+}
+
+static void
+mdioproxy_identify(driver_t *driver, device_t parent)
+{
+	device_t child;
+
+	if (device_find_child(parent, driver->name, -1) == NULL) {
+		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
+	}
+}
+
+static int
+mdioproxy_probe(device_t dev)
+{
+	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+mdioproxy_attach(device_t dev)
+{
+	rendevous_register_target(dev, mdioproxy_rendevous_callback);
+	return (bus_generic_attach(dev));
+}
+
+static int
+mdioproxy_detach(device_t dev)
+{
+	rendevous_unregister_target(dev);
+	bus_generic_detach(dev);
+	return (0);
+}
+
+/*
+ * Attach this proxy in place of miibus.  The callback is called once all
+ * parts are in place, so that it can attach the miibus to the proxy device,
+ * and finish interface initialization.
+ */
+device_t
+mii_attach_proxy(device_t dev, miiproxy_attach_callback_t cb, void *aa)
+{
+	struct miiproxy_softc *sc;
+	int		error;
+	const char	*name;
+	device_t	miiproxy;
+	
+	if (resource_string_value(device_get_name(dev),
+	    device_get_unit(dev), "mdio", &name) != 0) {
+	    	if (bootverbose)
+			printf("mii_attach_proxy: not attaching, no mdio"
+			    " device hint for %s\n", device_get_nameunit(dev));
+		return (NULL);
+	}
+
+	miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
+	error = bus_generic_attach(dev);
+	if (error != 0) {
+		device_printf(dev, "can't attach miiproxy\n");
+		return (NULL);
+	}
+	sc = device_get_softc(miiproxy);
+	sc->parent = dev;
+	sc->proxy = miiproxy;
+	sc->attach_callback = cb;
+	sc->attach_arg = aa;
+	rendevous_register_proxy(miiproxy, name, miiproxy_rendevous_callback);
+	return (miiproxy);
+}
+
+static device_method_t miiproxy_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_probe,		miiproxy_probe),
+	DEVMETHOD(device_attach,	miiproxy_attach),
+	DEVMETHOD(device_detach,	miiproxy_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	/* MII interface */
+	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
+	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
+	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
+	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
+	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
+
+	DEVMETHOD_END
+};
+
+static device_method_t mdioproxy_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_identify,	mdioproxy_identify),
+	DEVMETHOD(device_probe,		mdioproxy_probe),
+	DEVMETHOD(device_attach,	mdioproxy_attach),
+	DEVMETHOD(device_detach,	mdioproxy_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
+    sizeof(struct miiproxy_softc));
+DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
+    sizeof(struct mdioproxy_softc));
+
+devclass_t miiproxy_devclass;
+static devclass_t mdioproxy_devclass;
+
+DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
+DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
+MODULE_VERSION(miiproxy, 1);
+MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
diff --git a/sys/dev/etherswitch/miiproxy.h b/sys/dev/etherswitch/miiproxy.h
new file mode 100644
index 0000000..5b8ee7c
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DEV_ETHERSWITCH_MIIPROXY_H_
+#define	_DEV_ETHERSWITCH_MIIPROXY_H_
+
+typedef void (*miiproxy_attach_callback_t)(void *);
+
+extern devclass_t miiproxy_devclass;
+extern driver_t miiproxy_driver;
+
+device_t mii_attach_proxy(device_t dev, miiproxy_attach_callback_t cb, void *aa);
+
+#endif
diff --git a/sys/mips/atheros/if_arge.c b/sys/mips/atheros/if_arge.c
index 0154ae2..3dd2e15 100644
--- a/sys/mips/atheros/if_arge.c
+++ b/sys/mips/atheros/if_arge.c
@@ -72,8 +72,18 @@ __FBSDID("$FreeBSD$");
 #include <dev/pci/pcireg.h>
 #include <dev/pci/pcivar.h>
 
+#include <opt_arge.h>
+
+#if defined(ARGE_MDIO)
+#include <dev/etherswitch/mdio.h>
+#include <dev/etherswitch/miiproxy.h>
+#include "mdio_if.h"
+#endif
+
+
 MODULE_DEPEND(arge, ether, 1, 1, 1);
 MODULE_DEPEND(arge, miibus, 1, 1, 1);
+MODULE_VERSION(arge, 1);
 
 #include "miibus_if.h"
 
@@ -102,6 +112,7 @@ typedef enum {
 #endif
 
 static int arge_attach(device_t);
+static int arge_attach_finish(struct arge_softc *sc);
 static int arge_detach(device_t);
 static void arge_flush_ddr(struct arge_softc *);
 static int arge_ifmedia_upd(struct ifnet *);
@@ -134,6 +145,8 @@ static void arge_intr(void *);
 static int arge_intr_filter(void *);
 static void arge_tick(void *);
 
+static void arge_hinted_child(device_t bus, const char *dname, int dunit);
+
 /*
  * ifmedia callbacks for multiPHY MAC
  */
@@ -160,6 +173,10 @@ static device_method_t arge_methods[] = {
 	DEVMETHOD(miibus_writereg,	arge_miibus_writereg),
 	DEVMETHOD(miibus_statchg,	arge_miibus_statchg),
 
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	DEVMETHOD(bus_hinted_child,	arge_hinted_child),
+
 	DEVMETHOD_END
 };
 
@@ -174,6 +191,37 @@ static devclass_t arge_devclass;
 DRIVER_MODULE(arge, nexus, arge_driver, arge_devclass, 0, 0);
 DRIVER_MODULE(miibus, arge, miibus_driver, miibus_devclass, 0, 0);
 
+#if defined(ARGE_MDIO)
+static int argemdio_probe(device_t);
+static int argemdio_attach(device_t);
+static int argemdio_detach(device_t);
+
+/*
+ * Declare an additional, separate driver for accessing the MDIO bus.
+ */
+static device_method_t argemdio_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		argemdio_probe),
+	DEVMETHOD(device_attach,	argemdio_attach),
+	DEVMETHOD(device_detach,	argemdio_detach),
+
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	
+	/* MDIO access */
+	DEVMETHOD(mdio_readreg,		arge_miibus_readreg),
+	DEVMETHOD(mdio_writereg,	arge_miibus_writereg),
+};
+
+DEFINE_CLASS_0(argemdio, argemdio_driver, argemdio_methods,
+    sizeof(struct arge_softc));
+static devclass_t argemdio_devclass;
+
+DRIVER_MODULE(miiproxy, arge, miiproxy_driver, miiproxy_devclass, 0, 0);
+DRIVER_MODULE(argemdio, nexus, argemdio_driver, argemdio_devclass, 0, 0);
+DRIVER_MODULE(mdio, argemdio, mdio_driver, mdio_devclass, 0, 0);
+#endif
+
 /*
  * RedBoot passes MAC address to entry point as environment 
  * variable. platfrom_start parses it and stores in this variable
@@ -234,15 +282,22 @@ arge_attach_sysctl(device_t dev)
 #endif
 }
 
+#if defined(ARGE_MDIO)
+static void
+arge_attach_proxy(void *aa)
+{
+	arge_attach_finish(aa);
+}
+#endif
+
 static int
 arge_attach(device_t dev)
 {
-	uint8_t			eaddr[ETHER_ADDR_LEN];
 	struct ifnet		*ifp;
 	struct arge_softc	*sc;
-	int			error = 0, rid, phymask;
+	int			error = 0, rid;
 	uint32_t		reg, rnd;
-	int			is_base_mac_empty, i, phys_total;
+	int			is_base_mac_empty, i;
 	uint32_t		hint;
 	long			eeprom_mac_addr = 0;
 
@@ -277,18 +332,18 @@ arge_attach(device_t dev)
 	 *  Get which PHY of 5 available we should use for this unit
 	 */
 	if (resource_int_value(device_get_name(dev), device_get_unit(dev), 
-	    "phymask", &phymask) != 0) {
+	    "phymask", &sc->arge_phymask) != 0) {
 		/*
 		 * Use port 4 (WAN) for GE0. For any other port use 
 		 * its PHY the same as its unit number 
 		 */
 		if (sc->arge_mac_unit == 0)
-			phymask = (1 << 4);
+			sc->arge_phymask = (1 << 4);
 		else
 			/* Use all phys up to 4 */
-			phymask = (1 << 4) - 1;
+			sc->arge_phymask = (1 << 4) - 1;
 
-		device_printf(dev, "No PHY specified, using mask %d\n", phymask);
+		device_printf(dev, "No PHY specified, using mask %d\n", sc->arge_phymask);
 	}
 
 	/*
@@ -313,8 +368,6 @@ arge_attach(device_t dev)
 	else
 		sc->arge_duplex_mode = 0;
 
-	sc->arge_phymask = phymask;
-
 	mtx_init(&sc->arge_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
 	    MTX_DEF);
 	callout_init_mtx(&sc->arge_stat_callout, &sc->arge_mtx, 0);
@@ -323,7 +376,7 @@ arge_attach(device_t dev)
 	/* Map control/status registers. */
 	sc->arge_rid = 0;
 	sc->arge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 
-	    &sc->arge_rid, RF_ACTIVE);
+	    &sc->arge_rid, RF_ACTIVE | RF_SHAREABLE);
 
 	if (sc->arge_res == NULL) {
 		device_printf(dev, "couldn't map memory\n");
@@ -371,8 +424,8 @@ arge_attach(device_t dev)
 
 	is_base_mac_empty = 1;
 	for (i = 0; i < ETHER_ADDR_LEN; i++) {
-		eaddr[i] = ar711_base_mac[i] & 0xff;
-		if (eaddr[i] != 0)
+		sc->arge_eaddr[i] = ar711_base_mac[i] & 0xff;
+		if (sc->arge_eaddr[i] != 0)
 			is_base_mac_empty = 0;
 	}
 
@@ -385,16 +438,15 @@ arge_attach(device_t dev)
 			    "Generating random ethernet address.\n");
 
 		rnd = arc4random();
-		eaddr[0] = 'b';
-		eaddr[1] = 's';
-		eaddr[2] = 'd';
-		eaddr[3] = (rnd >> 24) & 0xff;
-		eaddr[4] = (rnd >> 16) & 0xff;
-		eaddr[5] = (rnd >> 8) & 0xff;
+		sc->arge_eaddr[0] = 'b';
+		sc->arge_eaddr[1] = 's';
+		sc->arge_eaddr[2] = 'd';
+		sc->arge_eaddr[3] = (rnd >> 24) & 0xff;
+		sc->arge_eaddr[4] = (rnd >> 16) & 0xff;
+		sc->arge_eaddr[5] = (rnd >> 8) & 0xff;
 	}
-
 	if (sc->arge_mac_unit != 0)
-		eaddr[5] +=  sc->arge_mac_unit;
+		sc->arge_eaddr[5] +=  sc->arge_mac_unit;
 
 	if (arge_dma_alloc(sc) != 0) {
 		error = ENXIO;
@@ -423,19 +475,23 @@ arge_attach(device_t dev)
 
 	ARGE_WRITE(sc, AR71XX_MAC_MAX_FRAME_LEN, 1536);
 
+#if !defined(ARGE_MDIO)
 	/* Reset MII bus */
 	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_RESET);
 	DELAY(100);
 	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_CLOCK_DIV_28);
 	DELAY(100);
+#endif
 
 	/* 
 	 * Set all Ethernet address registers to the same initial values
 	 * set all four addresses to 66-88-aa-cc-dd-ee 
 	 */
-	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, 
-	    (eaddr[2] << 24) | (eaddr[3] << 16) | (eaddr[4] << 8)  | eaddr[5]);
-	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (eaddr[0] << 8) | eaddr[1]);
+	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, (sc->arge_eaddr[2] << 24)
+	    | (sc->arge_eaddr[3] << 16) | (sc->arge_eaddr[4] << 8)
+	    | sc->arge_eaddr[5]);
+	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (sc->arge_eaddr[0] << 8)
+	    | sc->arge_eaddr[1]);
 
 	ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG0, 
 	    FIFO_CFG0_ALL << FIFO_CFG0_ENABLE_SHIFT);
@@ -458,30 +514,44 @@ arge_attach(device_t dev)
 	ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMASK, 
 	    FIFO_RX_FILTMASK_DEFAULT);
 
-	/* 
-	 * Check if we have single-PHY MAC or multi-PHY
-	 */
-	phys_total = 0;
-	for (i = 0; i < ARGE_NPHY; i++)
-		if (phymask & (1 << i))
-			phys_total ++;
+#if defined(ARGE_MDIO)
+	sc->arge_miiproxy = mii_attach_proxy(sc->arge_dev, arge_attach_proxy, sc);
+	if (sc->arge_miiproxy == NULL)
+		return (arge_attach_finish(sc));
+#else
+	return (arge_attach_finish(sc));
+#endif
+fail:
+	if (error) 
+		arge_detach(dev);
 
-	if (phys_total == 0) {
-		error = EINVAL;
-		goto fail;
-	}
+	return (error);
+}
 
-	if (phys_total == 1) {
-		/* Do MII setup. */
-		error = mii_attach(dev, &sc->arge_miibus, ifp,
-		    arge_ifmedia_upd, arge_ifmedia_sts, BMSR_DEFCAPMASK,
-		    MII_PHY_ANY, MII_OFFSET_ANY, 0);
-		if (error != 0) {
-			device_printf(dev, "attaching PHYs failed\n");
-			goto fail;
+static int
+arge_attach_finish(struct arge_softc *sc)
+{
+	int	error, phy;
+
+	device_printf(sc->arge_dev, "finishing attachment, phymask %04x"
+	    ", proxy %s \n", sc->arge_phymask, sc->arge_miiproxy == NULL ?
+	    "null" : "set");
+	for (phy = 0; phy < ARGE_NPHY; phy++) {
+		if (((1 << phy) & sc->arge_phymask) != 0) {
+			error = mii_attach(sc->arge_miiproxy != NULL ?
+			    sc->arge_miiproxy : sc->arge_dev,
+			    &sc->arge_miibus, sc->arge_ifp,
+			    arge_ifmedia_upd, arge_ifmedia_sts,
+			    BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+			if (error != 0) {
+				device_printf(sc->arge_dev, "unable to attach"
+				    " PHY %d: %d\n", phy, error);
+				goto fail;
+			}
 		}
 	}
-	else {
+	if (sc->arge_miibus == NULL) {
+		/* no PHY, so use hard-coded values */
 		ifmedia_init(&sc->arge_ifmedia, 0, 
 		    arge_multiphy_mediachange,
 		    arge_multiphy_mediastatus);
@@ -494,24 +564,23 @@ arge_attach(device_t dev)
 	}
 
 	/* Call MI attach routine. */
-	ether_ifattach(ifp, eaddr);
+	ether_ifattach(sc->arge_ifp, sc->arge_eaddr);
 
 	/* Hook interrupt last to avoid having to lock softc */
-	error = bus_setup_intr(dev, sc->arge_irq, INTR_TYPE_NET | INTR_MPSAFE,
+	error = bus_setup_intr(sc->arge_dev, sc->arge_irq, INTR_TYPE_NET | INTR_MPSAFE,
 	    arge_intr_filter, arge_intr, sc, &sc->arge_intrhand);
 
 	if (error) {
-		device_printf(dev, "couldn't set up irq\n");
-		ether_ifdetach(ifp);
+		device_printf(sc->arge_dev, "couldn't set up irq\n");
+		ether_ifdetach(sc->arge_ifp);
 		goto fail;
 	}
 
 	/* setup sysctl variables */
-	arge_attach_sysctl(dev);
-
+	arge_attach_sysctl(sc->arge_dev);
 fail:
 	if (error) 
-		arge_detach(dev);
+		arge_detach(sc->arge_dev);
 
 	return (error);
 }
@@ -542,6 +611,9 @@ arge_detach(device_t dev)
 	if (sc->arge_miibus)
 		device_delete_child(dev, sc->arge_miibus);
 
+	if (sc->arge_miiproxy)
+		device_delete_child(dev, sc->arge_miiproxy);
+
 	bus_generic_detach(dev);
 
 	if (sc->arge_intrhand)
@@ -592,6 +664,13 @@ arge_shutdown(device_t dev)
 	return (0);
 }
 
+static void
+arge_hinted_child(device_t bus, const char *dname, int dunit)
+{
+	BUS_ADD_CHILD(bus, 0, dname, dunit);
+	device_printf(bus, "hinted child %s%d\n", dname, dunit);
+}
+
 static int
 arge_miibus_readreg(device_t dev, int phy, int reg)
 {
@@ -600,16 +679,13 @@ arge_miibus_readreg(device_t dev, int phy, int reg)
 	uint32_t addr = (phy << MAC_MII_PHY_ADDR_SHIFT) 
 	    | (reg & MAC_MII_REG_MASK);
 
-	if ((sc->arge_phymask  & (1 << phy)) == 0)
-		return (0);
-
 	mtx_lock(&miibus_mtx);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_READ);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_ADDR, addr);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_READ);
 
 	i = ARGE_MII_TIMEOUT;
-	while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & 
+	while ((ARGE_MDIO_READ(sc, AR71XX_MAC_MII_INDICATOR) & 
 	    MAC_MII_INDICATOR_BUSY) && (i--))
 		DELAY(5);
 
@@ -620,8 +696,8 @@ arge_miibus_readreg(device_t dev, int phy, int reg)
 		return (-1);
 	}
 
-	result = ARGE_MII_READ(AR71XX_MAC_MII_STATUS) & MAC_MII_STATUS_MASK;
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
+	result = ARGE_MDIO_READ(sc, AR71XX_MAC_MII_STATUS) & MAC_MII_STATUS_MASK;
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
 	mtx_unlock(&miibus_mtx);
 
 	ARGEDEBUG(sc, ARGE_DBG_MII, "%s: phy=%d, reg=%02x, value[%08x]=%04x\n", __func__, 
@@ -638,19 +714,15 @@ arge_miibus_writereg(device_t dev, int phy, int reg, int data)
 	uint32_t addr = 
 	    (phy << MAC_MII_PHY_ADDR_SHIFT) | (reg & MAC_MII_REG_MASK);
 
-
-	if ((sc->arge_phymask  & (1 << phy)) == 0)
-		return (-1);
-
 	ARGEDEBUG(sc, ARGE_DBG_MII, "%s: phy=%d, reg=%02x, value=%04x\n", __func__, 
 	    phy, reg, data);
 
 	mtx_lock(&miibus_mtx);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CONTROL, data);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_ADDR, addr);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CONTROL, data);
 
 	i = ARGE_MII_TIMEOUT;
-	while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & 
+	while ((ARGE_MDIO_READ(sc, AR71XX_MAC_MII_INDICATOR) & 
 	    MAC_MII_INDICATOR_BUSY) && (i--))
 		DELAY(5);
 
@@ -715,6 +787,8 @@ arge_set_pll(struct arge_softc *sc, int media, int duplex)
 	uint32_t		fifo_tx;
 	int if_speed;
 
+	ARGEDEBUG(sc, ARGE_DBG_MII, "set_pll(%04x, %s)\n", media,
+	    duplex == IFM_FDX ? "full" : "half");
 	cfg = ARGE_READ(sc, AR71XX_MAC_CFG2);
 	cfg &= ~(MAC_CFG2_IFACE_MODE_1000 
 	    | MAC_CFG2_IFACE_MODE_10_100 
@@ -1923,3 +1997,47 @@ arge_multiphy_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
 	    sc->arge_duplex_mode;
 }
 
+#if defined(ARGE_MDIO)
+static int
+argemdio_probe(device_t dev)
+{
+	device_set_desc(dev, "Atheros AR71xx built-in ethernet interface, MDIO controller");
+	return (0);
+}
+
+static int
+argemdio_attach(device_t dev)
+{
+	struct arge_softc	*sc;
+	int			error = 0;
+
+	sc = device_get_softc(dev);
+	sc->arge_dev = dev;
+	sc->arge_mac_unit = device_get_unit(dev);
+	sc->arge_rid = 0;
+	sc->arge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 
+	    &sc->arge_rid, RF_ACTIVE | RF_SHAREABLE);
+	if (sc->arge_res == NULL) {
+		device_printf(dev, "couldn't map memory\n");
+		error = ENXIO;
+		goto fail;
+	}
+	/* Reset MII bus */
+	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_RESET);
+	DELAY(100);
+	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_CLOCK_DIV_28);
+	DELAY(100);
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	error = bus_generic_attach(dev);
+fail:
+	return (error);
+}
+
+static int
+argemdio_detach(device_t dev)
+{
+	return (0);
+}
+
+#endif
diff --git a/sys/mips/atheros/if_argevar.h b/sys/mips/atheros/if_argevar.h
index c1df678..dc3d931 100644
--- a/sys/mips/atheros/if_argevar.h
+++ b/sys/mips/atheros/if_argevar.h
@@ -67,15 +67,10 @@
 #define ARGE_CLEAR_BITS(sc, reg, bits)	\
 	ARGE_WRITE(sc, reg, ARGE_READ(sc, (reg)) & ~(bits))
 
-/*
- * MII registers access macros
- */
-#define ARGE_MII_READ(reg) \
-        *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg)))
-
-#define ARGE_MII_WRITE(reg, val) \
-        *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg))) = (val)
-
+#define ARGE_MDIO_WRITE(_sc, _reg, _val)	\
+	ARGE_WRITE((_sc), (_reg), (_val))
+#define ARGE_MDIO_READ(_sc, _reg)	\
+	ARGE_READ((_sc), (_reg))
 
 #define ARGE_DESC_EMPTY		(1 << 31)
 #define ARGE_DESC_MORE		(1 << 24)
@@ -132,11 +127,14 @@ struct arge_softc {
 	 */
 	uint32_t		arge_media_type;
 	uint32_t		arge_duplex_mode;
+	uint32_t		arge_phymask;
+	uint8_t			arge_eaddr[ETHER_ADDR_LEN];
 	struct resource		*arge_res;
 	int			arge_rid;
 	struct resource		*arge_irq;
 	void			*arge_intrhand;
 	device_t		arge_miibus;
+	device_t		arge_miiproxy;
 	bus_dma_tag_t		arge_parent_tag;
 	bus_dma_tag_t		arge_tag;
 	struct mtx		arge_mtx;
@@ -148,7 +146,6 @@ struct arge_softc {
 	int			arge_detach;
 	uint32_t		arge_intr_status;
 	int			arge_mac_unit;
-	int			arge_phymask;
 	int			arge_if_flags;
 	uint32_t		arge_debug;
 	struct {

[-- Attachment #4 --]
diff --git a/sbin/Makefile b/sbin/Makefile
index f9ba4ca..0cb421f 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -20,6 +20,7 @@ SUBDIR=adjkerntz \
 	dump \
 	dumpfs \
 	dumpon \
+	etherswitchcfg \
 	ffsinfo \
 	fsck \
 	fsck_ffs \
diff --git a/sbin/etherswitchcfg/Makefile b/sbin/etherswitchcfg/Makefile
new file mode 100644
index 0000000..d21c88b
--- /dev/null
+++ b/sbin/etherswitchcfg/Makefile
@@ -0,0 +1,9 @@
+#	@(#)Makefile	5.4 (Berkeley) 6/5/91
+# $FreeBSD: head/sbin/comcontrol/Makefile 198236 2009-10-19 16:00:24Z ru $
+
+PROG=	etherswitchcfg
+MAN=	etherswitchcfg.8
+SRCS=	etherswitchcfg.c ifmedia.c
+CFLAGS+= -I${.CURDIR}/../../sys
+
+.include <bsd.prog.mk>
diff --git a/sbin/etherswitchcfg/etherswitchcfg.8 b/sbin/etherswitchcfg/etherswitchcfg.8
new file mode 100644
index 0000000..538950c
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.8
@@ -0,0 +1,114 @@
+.\" $FreeBSD$
+.Dd December 15, 2011
+.Dt ETHERSWITCHCFG 8
+.Os
+.Sh NAME
+.Nm etherswitchcfg
+.Nd configure a built-in Ethernet switch
+.Sh SYNOPSIS
+.Nm
+.Op Fl "f control file"
+.Ar info
+.Nm
+.Op Fl "f control file"
+.Ar phy
+.Ar phy.register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar port%d
+.Ar command parameter
+.Nm
+.Op Fl "f control file"
+.Ar reg
+.Ar register[=value]
+.Nm
+.Op Fl "f control file"
+.Ar vlangroup%d
+.Ar command parameter
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure an Ethernet switch built into the system.
+.Nm
+accepts a number of options:
+.Bl -tag -width ".Fl f" -compact
+.It Fl "f control file"
+Specifies the
+.Xr etherswitch 4
+control file that represents the switch to be configured.
+It defaults to
+.Li /dev/etherswitch0 .
+.It Fl m
+When reporting port information, also list available media options for
+that port.
+.It Fl v
+Produce more verbose output.
+Without this flag, lines that represent inactive or empty configuration
+options are omitted.
+.El
+.Ss phy
+The phy command provides access to the registers of the PHYs attached
+to or integrated into the switch controller.
+PHY registers are specified as phy.register,
+where
+.Ar phy
+is usually the port number, and
+.Ar register
+is the register number.
+Both can be provided as decimal, octal or hexadecimal numbers in any of the formats
+understood by
+.Xr strtol 4 .
+To set the register value, use the form instance.register=value.
+.Ss port
+The port command selects one of the ports of the switch.
+It supports the following commands:
+.Bl -tag -width ".Ar vlangroup number" -compact
+.It Ar vlangroup number
+Sets the VLAN group number that is used to process incoming frames that are not tagged.
+.It Ar media mediaspec
+Specifies the physical media configuration to be configured for a port.
+.It Ar mediaopt mediaoption
+Specifies a list of media options for a port. See
+.Xr ifconfig 8
+for details on
+.Ar media and 
+.Ar mediaopt .
+.El
+.Ss reg
+The reg command provides access to the registers of the switch controller.
+.Ss vlangroup
+The vlangroup command selects one of the VLAN groups for configuration.
+It supports the following commands:
+.Bl -tag -width ".Ar vlangroup" -compact
+.It Ar vlan VID
+Sets the VLAN ID (802.1q VID) for this VLAN group.
+Frames transmitted on tagged member ports of this group will be tagged
+with this VID.
+Incoming frames carrying this tag will be forwarded according to the
+configuration of this VLAN group.
+.It Ar members port,...
+Configures which ports are to be a member of this VLAN group.
+The port numbers are given as a comma-separated list.
+Each port can optionally be followed by
+.Dq t
+to indicate that frames on this port are tagged.
+.El
+.Sh FILES
+.Bl -tag -width /dev/etherswitch? -compact
+.It Pa /dev/etherswitch?
+Control file for the ethernet switch driver.
+.El
+.Sh EXAMPLES
+Configure VLAN group 1 with a VID of 2 and makes ports 0 and 5 members,
+while excluding all other ports.
+Port 5 will send and receive tagged frames, while port 0 will be untagged.
+Incoming untagged frames on port 0 are assigned to vlangroup1.
+.Dl # etherswitchcfg vlangroup1 vlan 2 members 0,5t port0 vlangroup 1
+.Sh SEE ALSO
+.Xr etherswitch 4
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Stefan Bethke
diff --git a/sbin/etherswitchcfg/etherswitchcfg.c b/sbin/etherswitchcfg/etherswitchcfg.c
new file mode 100644
index 0000000..e6129f3
--- /dev/null
+++ b/sbin/etherswitchcfg/etherswitchcfg.c
@@ -0,0 +1,511 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <dev/etherswitch/etherswitch.h>
+
+int	get_media_subtype(int, const char *);
+int	get_media_mode(int, const char *);
+int	get_media_options(int, const char *);
+int	lookup_media_word(struct ifmedia_description *, const char *);
+void    print_media_word(int, int);
+void    print_media_word_ifconfig(int);
+
+/* some constants */
+#define IEEE802DOT1Q_VID_MAX	4094
+#define IFMEDIAREQ_NULISTENTRIES	256
+
+enum cmdmode {
+	MODE_NONE = 0,
+	MODE_PORT,
+	MODE_VLANGROUP,
+	MODE_REGISTER,
+	MODE_PHYREG
+};
+
+struct cfg {
+	int					fd;
+	int					verbose;
+	int					mediatypes;
+	const char			*controlfile;
+	etherswitch_info_t	info;
+	enum cmdmode		mode;
+	int					unit;
+};
+
+struct cmds {
+	enum cmdmode	mode;
+	const char		*name;
+	int				args;
+	void 			(*f)(struct cfg *, char *argv[]);
+};
+struct cmds cmds[];
+
+
+static void usage(void);
+
+static int
+read_register(struct cfg *cfg, int r)
+{
+	struct etherswitch_reg er;
+	
+	er.reg = r;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
+	return (er.val);
+}
+
+static void
+write_register(struct cfg *cfg, int r, int v)
+{
+	struct etherswitch_reg er;
+	
+	er.reg = r;
+	er.val = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
+}
+
+static int
+read_phyregister(struct cfg *cfg, int phy, int reg)
+{
+	struct etherswitch_phyreg er;
+	
+	er.phy = phy;
+	er.reg = reg;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
+	return (er.val);
+}
+
+static void
+write_phyregister(struct cfg *cfg, int phy, int reg, int val)
+{
+	struct etherswitch_phyreg er;
+	
+	er.phy = phy;
+	er.reg = reg;
+	er.val = val;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
+}
+
+static void
+set_port_vlangroup(struct cfg *cfg, char *argv[])
+{
+	int v;
+	etherswitch_port_t p;
+	
+	v = strtol(argv[1], NULL, 0);
+	if (v < 0 || v >= cfg->info.es_nvlangroups)
+		errx(EX_USAGE, "vlangroup must be between 0 and %d", cfg->info.es_nvlangroups-1);
+	p.es_port = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	p.es_vlangroup = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_media(struct cfg *cfg, char *argv[])
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int subtype;
+	
+	bzero(&p, sizeof(p));
+	p.es_port = cfg->unit;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
+	p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
+	        IFM_TYPE(ifm_ulist[0]) | subtype;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_port_mediaopt(struct cfg *cfg, char *argv[])
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int options;
+	
+	bzero(&p, sizeof(p));
+	p.es_port = cfg->unit;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
+	if (options == -1)
+		errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
+	if (options & IFM_HDX) {
+		p.es_ifr.ifr_media &= ~IFM_FDX;
+		options &= ~IFM_HDX;
+	}
+	p.es_ifr.ifr_media |= options;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
+}
+
+static void
+set_vlangroup_vid(struct cfg *cfg, char *argv[])
+{
+	int v;
+	etherswitch_vlangroup_t vg;
+	
+	v = strtol(argv[1], NULL, 0);
+	if (v < 0 || v >= IEEE802DOT1Q_VID_MAX)
+		errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
+	vg.es_vlangroup = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	vg.es_vid = v;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static void
+set_vlangroup_members(struct cfg *cfg, char *argv[])
+{
+	etherswitch_vlangroup_t vg;
+	int member, untagged;
+	char *c, *d;
+	int v;
+	
+	member = untagged = 0;
+	if (strcmp(argv[1], "none") != 0) {
+		for (c=argv[1]; *c; c=d) {
+			v = strtol(c, &d, 0);
+			if (d == c)
+				break;
+			if (v < 0 || v >= cfg->info.es_nports)
+				errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
+			if (d[0] == ',' || d[0] == '\0' ||
+				((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
+				if (d[0] == 't' || d[0] == 'T') {
+					untagged &= ~ETHERSWITCH_PORTMASK(v);
+					d++;
+				} else
+					untagged |= ETHERSWITCH_PORTMASK(v);
+				member |= ETHERSWITCH_PORTMASK(v);
+				d++;
+			} else
+				errx(EX_USAGE, "Invalid members specification \"%s\"", d);
+		}
+	}
+	vg.es_vlangroup = cfg->unit;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	vg.es_member_ports = member;
+	vg.es_untagged_ports = untagged;
+	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
+}
+
+static int
+set_register(struct cfg *cfg, char *arg)
+{
+	int a, v;
+	char *c;
+	
+	a = strtol(arg, &c, 0);
+	if (c==arg)
+		return (1);
+	if (*c == '=') {
+		v = strtol(c+1, NULL, 0);
+		write_register(cfg, a, v);
+	}
+	printf("\treg 0x%04x=0x%04x\n", a, read_register(cfg, a));
+	return (0);
+}
+
+static int
+set_phyregister(struct cfg *cfg, char *arg)
+{
+	int phy, reg, val;
+	char *c, *d;
+	
+	phy = strtol(arg, &c, 0);
+	if (c==arg)
+		return (1);
+	if (*c != '.')
+		return (1);
+	d = c+1;
+	reg = strtol(d, &c, 0);
+	if (d == c)
+		return (1);
+	if (*c == '=') {
+		val = strtol(c+1, NULL, 0);
+		write_phyregister(cfg, phy, reg, val);
+	}
+	printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
+	return (0);
+}
+
+static void
+print_port(struct cfg *cfg, int port)
+{
+	etherswitch_port_t p;
+	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
+	int i;
+
+	bzero(&p, sizeof(p));
+	p.es_port = port;
+	p.es_ifmr.ifm_ulist = ifm_ulist;
+	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
+	printf("port%d:\n", port);
+	printf("\tvlangroup: %d\n", p.es_vlangroup);
+	printf("\tmedia: ");
+	print_media_word(p.es_ifmr.ifm_current, 1);
+	if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
+		putchar(' ');
+		putchar('(');
+		print_media_word(p.es_ifmr.ifm_active, 0);
+		putchar(')');
+	}
+	putchar('\n');
+	printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
+	if (cfg->mediatypes) {
+		printf("\tsupported media:\n");
+		if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
+			p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
+		for (i=0; i<p.es_ifmr.ifm_count; i++) {
+			printf("\t\tmedia ");
+			print_media_word(ifm_ulist[i], 0);
+			putchar('\n');
+		}
+	}
+}
+
+static void
+print_vlangroup(struct cfg *cfg, int vlangroup)
+{
+	etherswitch_vlangroup_t vg;
+	int i, comma;
+	
+	vg.es_vlangroup = vlangroup;
+	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
+	if (cfg->verbose == 0 && vg.es_member_ports == 0)
+		return;
+	printf("vlangroup%d:\n", vlangroup);
+	printf("\tvlan: %d\n", vg.es_vid);
+	printf("\tmembers ");
+	comma = 0;
+	if (vg.es_member_ports != 0)
+		for (i=0; i<cfg->info.es_nports; i++) {
+			if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
+				if (comma)
+					printf(",");
+				printf("%d", i);
+				if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
+					printf("t");
+				comma = 1;
+			}
+		}
+	else
+		printf("none");
+	printf("\n");
+}
+
+static void
+print_info(struct cfg *cfg)
+{
+	const char *c;
+	int i;
+	
+	c = strrchr(cfg->controlfile, '/');
+	if (c != NULL)
+		c = c + 1;
+	else
+		c = cfg->controlfile;
+	if (cfg->verbose)
+		printf("%s: %s with %d ports and %d VLAN groups\n",
+			c, cfg->info.es_name, cfg->info.es_nports, cfg->info.es_nvlangroups);
+	for (i=0; i<cfg->info.es_nports; i++) {
+		print_port(cfg, i);
+	}
+	for (i=0; i<cfg->info.es_nvlangroups; i++) {
+		print_vlangroup(cfg, i);
+	}
+}
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: etherswitchctl\n");
+	exit(EX_USAGE);
+}
+
+static void
+newmode(struct cfg *cfg, enum cmdmode mode)
+{
+	if (mode == cfg->mode)
+		return;
+	switch (cfg->mode) {
+	case MODE_NONE:
+		break;
+	case MODE_PORT:
+		print_port(cfg, cfg->unit);
+		break;
+	case MODE_VLANGROUP:
+		print_vlangroup(cfg, cfg->unit);
+		break;
+	case MODE_REGISTER:
+	case MODE_PHYREG:
+		break;
+	}
+	cfg->mode = mode;
+}
+
+int
+main(int argc, char *argv[])
+{
+	int ch;
+	struct cfg cfg;
+	int i;
+	
+	bzero(&cfg, sizeof(cfg));
+	cfg.controlfile = "/dev/etherswitch0";
+	while ((ch = getopt(argc, argv, "f:mv?")) != -1)
+		switch(ch) {
+		case 'f':
+			cfg.controlfile = optarg;
+			break;
+		case 'm':
+			cfg.mediatypes++;
+			break;
+		case 'v':
+			cfg.verbose++;
+			break;
+		case '?':
+			/* FALLTHROUGH */
+		default:
+			usage();
+		}
+	argc -= optind;
+	argv += optind;
+	cfg.fd = open(cfg.controlfile, O_RDONLY);
+	if (cfg.fd < 0)
+		err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
+	if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
+		err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
+	if (argc == 0) {
+		print_info(&cfg);
+		return (0);
+	}
+	cfg.mode = MODE_NONE;
+	while (argc > 0) {
+		switch(cfg.mode) {
+		case MODE_NONE:
+			if (strcmp(argv[0], "info") == 0) {
+				print_info(&cfg);
+			} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
+				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
+					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports);
+				newmode(&cfg, MODE_PORT);
+			} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
+				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
+					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nvlangroups);
+				newmode(&cfg, MODE_VLANGROUP);
+			} else if (strcmp(argv[0], "phy") == 0) {
+				newmode(&cfg, MODE_PHYREG);
+			} else if (strcmp(argv[0], "reg") == 0) {
+				newmode(&cfg, MODE_REGISTER);
+			} else {
+				errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
+			}
+			break;
+		case MODE_PORT:
+		case MODE_VLANGROUP:
+			for(i=0; cmds[i].name != NULL; i++) {
+				if (cfg.mode == cmds[i].mode && strcmp(argv[0], cmds[i].name) == 0
+					&& argc >= cmds[i].args) {
+					(cmds[i].f)(&cfg, argv);
+					argc -= cmds[i].args;
+					argv += cmds[i].args;
+					break;
+				}
+			}
+			if (cmds[i].name == NULL) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		case MODE_REGISTER:
+			if (set_register(&cfg, argv[0]) != 0) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		case MODE_PHYREG:
+			if (set_phyregister(&cfg, argv[0]) != 0) {
+				newmode(&cfg, MODE_NONE);
+				continue;
+			}
+			break;
+		}
+		argc--;
+		argv++;
+	}
+	/* switch back to command mode to print configuration for last command */
+	newmode(&cfg, MODE_NONE);
+	close(cfg.fd);
+	return (0);
+}
+
+struct cmds cmds[] = {
+	{ MODE_PORT, "vlangroup", 1, set_port_vlangroup },
+	{ MODE_PORT, "media", 1, set_port_media },
+	{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
+	{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
+	{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
+	{ 0, NULL, 0, NULL }
+};
diff --git a/sbin/etherswitchcfg/ifmedia.c b/sbin/etherswitchcfg/ifmedia.c
new file mode 100644
index 0000000..49881fe
--- /dev/null
+++ b/sbin/etherswitchcfg/ifmedia.c
@@ -0,0 +1,812 @@
+/*	$NetBSD: ifconfig.c,v 1.34 1997/04/21 01:17:58 lukem Exp $	*/
+/* $FreeBSD: head/sbin/ifconfig/ifmedia.c 221954 2011-05-15 12:51:00Z marius $ */
+
+/*
+ * Copyright (c) 1997 Jason R. Thorpe.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project
+ *	by Jason R. Thorpe.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * based on sbin/ifconfig/ifmedia.c r221954
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void	domediaopt(const char *, int, int);
+int	get_media_subtype(int, const char *);
+int	get_media_mode(int, const char *);
+int	get_media_options(int, const char *);
+int	lookup_media_word(struct ifmedia_description *, const char *);
+void	print_media_word(int, int);
+void	print_media_word_ifconfig(int);
+
+#if 0
+static struct ifmedia_description *get_toptype_desc(int);
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int);
+static struct ifmedia_description *get_subtype_desc(int,
+    struct ifmedia_type_to_subtype *ttos);
+
+#define	IFM_OPMODE(x) \
+	((x) & (IFM_IEEE80211_ADHOC | IFM_IEEE80211_HOSTAP | \
+	 IFM_IEEE80211_IBSS | IFM_IEEE80211_WDS | IFM_IEEE80211_MONITOR | \
+	 IFM_IEEE80211_MBSS))
+#define	IFM_IEEE80211_STA	0
+
+static void
+media_status(int s)
+{
+	struct ifmediareq ifmr;
+	int *media_list, i;
+
+	(void) memset(&ifmr, 0, sizeof(ifmr));
+	(void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name));
+
+	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+		/*
+		 * Interface doesn't support SIOC{G,S}IFMEDIA.
+		 */
+		return;
+	}
+
+	if (ifmr.ifm_count == 0) {
+		warnx("%s: no media types?", name);
+		return;
+	}
+
+	media_list = (int *)malloc(ifmr.ifm_count * sizeof(int));
+	if (media_list == NULL)
+		err(1, "malloc");
+	ifmr.ifm_ulist = media_list;
+
+	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
+		err(1, "SIOCGIFMEDIA");
+
+	printf("\tmedia: ");
+	print_media_word(ifmr.ifm_current, 1);
+	if (ifmr.ifm_active != ifmr.ifm_current) {
+		putchar(' ');
+		putchar('(');
+		print_media_word(ifmr.ifm_active, 0);
+		putchar(')');
+	}
+
+	putchar('\n');
+
+	if (ifmr.ifm_status & IFM_AVALID) {
+		printf("\tstatus: ");
+		switch (IFM_TYPE(ifmr.ifm_active)) {
+		case IFM_ETHER:
+		case IFM_ATM:
+			if (ifmr.ifm_status & IFM_ACTIVE)
+				printf("active");
+			else
+				printf("no carrier");
+			break;
+
+		case IFM_FDDI:
+		case IFM_TOKEN:
+			if (ifmr.ifm_status & IFM_ACTIVE)
+				printf("inserted");
+			else
+				printf("no ring");
+			break;
+
+		case IFM_IEEE80211:
+			if (ifmr.ifm_status & IFM_ACTIVE) {
+				/* NB: only sta mode associates */
+				if (IFM_OPMODE(ifmr.ifm_active) == IFM_IEEE80211_STA)
+					printf("associated");
+				else
+					printf("running");
+			} else
+				printf("no carrier");
+			break;
+		}
+		putchar('\n');
+	}
+
+	if (ifmr.ifm_count > 0 && supmedia) {
+		printf("\tsupported media:\n");
+		for (i = 0; i < ifmr.ifm_count; i++) {
+			printf("\t\t");
+			print_media_word_ifconfig(media_list[i]);
+			putchar('\n');
+		}
+	}
+
+	free(media_list);
+}
+
+struct ifmediareq *
+ifmedia_getstate(int s)
+{
+	static struct ifmediareq *ifmr = NULL;
+	int *mwords;
+
+	if (ifmr == NULL) {
+		ifmr = (struct ifmediareq *)malloc(sizeof(struct ifmediareq));
+		if (ifmr == NULL)
+			err(1, "malloc");
+
+		(void) memset(ifmr, 0, sizeof(struct ifmediareq));
+		(void) strncpy(ifmr->ifm_name, name,
+		    sizeof(ifmr->ifm_name));
+
+		ifmr->ifm_count = 0;
+		ifmr->ifm_ulist = NULL;
+
+		/*
+		 * We must go through the motions of reading all
+		 * supported media because we need to know both
+		 * the current media type and the top-level type.
+		 */
+
+		if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0) {
+			err(1, "SIOCGIFMEDIA");
+		}
+
+		if (ifmr->ifm_count == 0)
+			errx(1, "%s: no media types?", name);
+
+		mwords = (int *)malloc(ifmr->ifm_count * sizeof(int));
+		if (mwords == NULL)
+			err(1, "malloc");
+  
+		ifmr->ifm_ulist = mwords;
+		if (ioctl(s, SIOCGIFMEDIA, (caddr_t)ifmr) < 0)
+			err(1, "SIOCGIFMEDIA");
+	}
+
+	return ifmr;
+}
+
+static void
+setifmediacallback(int s, void *arg)
+{
+	struct ifmediareq *ifmr = (struct ifmediareq *)arg;
+	static int did_it = 0;
+
+	if (!did_it) {
+		ifr.ifr_media = ifmr->ifm_current;
+		if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0)
+			err(1, "SIOCSIFMEDIA (media)");
+		free(ifmr->ifm_ulist);
+		free(ifmr);
+		did_it = 1;
+	}
+}
+
+static void
+setmedia(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int subtype;
+
+	ifmr = ifmedia_getstate(s);
+
+	/*
+	 * We are primarily concerned with the top-level type.
+	 * However, "current" may be only IFM_NONE, so we just look
+	 * for the top-level type in the first "supported type"
+	 * entry.
+	 *
+	 * (I'm assuming that all supported media types for a given
+	 * interface will be the same top-level type..)
+	 */
+	subtype = get_media_subtype(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & IFM_IMASK) |
+	    IFM_TYPE(ifmr->ifm_ulist[0]) | subtype;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+	domediaopt(val, 0, s);
+}
+
+static void
+unsetmediaopt(const char *val, int d, int s, const struct afswtch *afp)
+{
+
+	domediaopt(val, 1, s);
+}
+
+static void
+domediaopt(const char *val, int clear, int s)
+{
+	struct ifmediareq *ifmr;
+	int options;
+
+	ifmr = ifmedia_getstate(s);
+
+	options = get_media_options(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = ifmr->ifm_current;
+	if (clear)
+		ifr.ifr_media &= ~options;
+	else {
+		if (options & IFM_HDX) {
+			ifr.ifr_media &= ~IFM_FDX;
+			options &= ~IFM_HDX;
+		}
+		ifr.ifr_media |= options;
+	}
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediainst(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int inst;
+
+	ifmr = ifmedia_getstate(s);
+
+	inst = atoi(val);
+	if (inst < 0 || inst > (int)IFM_INST_MAX)
+		errx(1, "invalid media instance: %s", val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & ~IFM_IMASK) | inst << IFM_ISHIFT;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+
+static void
+setmediamode(const char *val, int d, int s, const struct afswtch *afp)
+{
+	struct ifmediareq *ifmr;
+	int mode;
+
+	ifmr = ifmedia_getstate(s);
+
+	mode = get_media_mode(IFM_TYPE(ifmr->ifm_ulist[0]), val);
+
+	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+	ifr.ifr_media = (ifmr->ifm_current & ~IFM_MMASK) | mode;
+
+	ifmr->ifm_current = ifr.ifr_media;
+	callback_register(setifmediacallback, (void *)ifmr);
+}
+#endif
+
+/**********************************************************************
+ * A good chunk of this is duplicated from sys/net/ifmedia.c
+ **********************************************************************/
+
+static struct ifmedia_description ifm_type_descriptions[] =
+    IFM_TYPE_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_descriptions[] =
+    IFM_SUBTYPE_ETHERNET_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ethernet_aliases[] =
+    IFM_SUBTYPE_ETHERNET_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] =
+    IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_descriptions[] =
+    IFM_SUBTYPE_TOKENRING_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_tokenring_aliases[] =
+    IFM_SUBTYPE_TOKENRING_ALIASES;
+
+static struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] =
+    IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_descriptions[] =
+    IFM_SUBTYPE_FDDI_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_fddi_aliases[] =
+    IFM_SUBTYPE_FDDI_ALIASES;
+
+static struct ifmedia_description ifm_subtype_fddi_option_descriptions[] =
+    IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_ieee80211_aliases[] =
+    IFM_SUBTYPE_IEEE80211_ALIASES;
+
+static struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] =
+    IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS;
+
+struct ifmedia_description ifm_subtype_ieee80211_mode_aliases[] =
+    IFM_SUBTYPE_IEEE80211_MODE_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_descriptions[] =
+    IFM_SUBTYPE_ATM_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_atm_aliases[] =
+    IFM_SUBTYPE_ATM_ALIASES;
+
+static struct ifmedia_description ifm_subtype_atm_option_descriptions[] =
+    IFM_SUBTYPE_ATM_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_descriptions[] =
+    IFM_SUBTYPE_SHARED_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_subtype_shared_aliases[] =
+    IFM_SUBTYPE_SHARED_ALIASES;
+
+static struct ifmedia_description ifm_shared_option_descriptions[] =
+    IFM_SHARED_OPTION_DESCRIPTIONS;
+
+static struct ifmedia_description ifm_shared_option_aliases[] =
+    IFM_SHARED_OPTION_ALIASES;
+
+struct ifmedia_type_to_subtype {
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} subtypes[5];
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} options[4];
+	struct {
+		struct ifmedia_description *desc;
+		int alias;
+	} modes[3];
+};
+
+/* must be in the same order as IFM_TYPE_DESCRIPTIONS */
+static struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = {
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_ethernet_descriptions[0], 0 },
+			{ &ifm_subtype_ethernet_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_ethernet_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_tokenring_descriptions[0], 0 },
+			{ &ifm_subtype_tokenring_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_tokenring_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_fddi_descriptions[0], 0 },
+			{ &ifm_subtype_fddi_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_fddi_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_ieee80211_descriptions[0], 0 },
+			{ &ifm_subtype_ieee80211_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_ieee80211_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_subtype_ieee80211_mode_descriptions[0], 0 },
+			{ &ifm_subtype_ieee80211_mode_aliases[0], 0 },
+			{ NULL, 0 },
+		},
+	},
+	{
+		{
+			{ &ifm_subtype_shared_descriptions[0], 0 },
+			{ &ifm_subtype_shared_aliases[0], 1 },
+			{ &ifm_subtype_atm_descriptions[0], 0 },
+			{ &ifm_subtype_atm_aliases[0], 1 },
+			{ NULL, 0 },
+		},
+		{
+			{ &ifm_shared_option_descriptions[0], 0 },
+			{ &ifm_shared_option_aliases[0], 1 },
+			{ &ifm_subtype_atm_option_descriptions[0], 0 },
+			{ NULL, 0 },
+		},
+		{
+			{ NULL, 0 },
+		},
+	},
+};
+
+int
+get_media_subtype(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int rval, i;
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media type 0x%x", type);
+
+	for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+		rval = lookup_media_word(ttos->subtypes[i].desc, val);
+		if (rval != -1)
+			return (rval);
+	}
+	errx(1, "unknown media subtype: %s", val);
+	/*NOTREACHED*/
+}
+
+int
+get_media_mode(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int rval, i;
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media mode 0x%x", type);
+
+	for (i = 0; ttos->modes[i].desc != NULL; i++) {
+		rval = lookup_media_word(ttos->modes[i].desc, val);
+		if (rval != -1)
+			return (rval);
+	}
+	return -1;
+}
+
+int
+get_media_options(int type, const char *val)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	char *optlist, *optptr;
+	int option = 0, i, rval = 0;
+
+	/* We muck with the string, so copy it. */
+	optlist = strdup(val);
+	if (optlist == NULL)
+		err(1, "strdup");
+
+	/* Find the top-level interface type. */
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (type == desc->ifmt_word)
+			break;
+	if (desc->ifmt_string == NULL)
+		errx(1, "unknown media type 0x%x", type);
+
+	/*
+	 * Look up the options in the user-provided comma-separated
+	 * list.
+	 */
+	optptr = optlist;
+	for (; (optptr = strtok(optptr, ",")) != NULL; optptr = NULL) {
+		for (i = 0; ttos->options[i].desc != NULL; i++) {
+			option = lookup_media_word(ttos->options[i].desc, optptr);
+			if (option != -1)
+				break;
+		}
+		if (option == 0)
+			errx(1, "unknown option: %s", optptr);
+		rval |= option;
+	}
+
+	free(optlist);
+	return (rval);
+}
+
+int
+lookup_media_word(struct ifmedia_description *desc, const char *val)
+{
+
+	for (; desc->ifmt_string != NULL; desc++)
+		if (strcasecmp(desc->ifmt_string, val) == 0)
+			return (desc->ifmt_word);
+
+	return (-1);
+}
+
+static struct ifmedia_description *get_toptype_desc(int ifmw)
+{
+	struct ifmedia_description *desc;
+
+	for (desc = ifm_type_descriptions; desc->ifmt_string != NULL; desc++)
+		if (IFM_TYPE(ifmw) == desc->ifmt_word)
+			break;
+
+	return desc;
+}
+
+static struct ifmedia_type_to_subtype *get_toptype_ttos(int ifmw)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+
+	for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes;
+	    desc->ifmt_string != NULL; desc++, ttos++)
+		if (IFM_TYPE(ifmw) == desc->ifmt_word)
+			break;
+
+	return ttos;
+}
+
+static struct ifmedia_description *get_subtype_desc(int ifmw, 
+    struct ifmedia_type_to_subtype *ttos)
+{
+	int i;
+	struct ifmedia_description *desc;
+
+	for (i = 0; ttos->subtypes[i].desc != NULL; i++) {
+		if (ttos->subtypes[i].alias)
+			continue;
+		for (desc = ttos->subtypes[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (IFM_SUBTYPE(ifmw) == desc->ifmt_word)
+				return desc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct ifmedia_description *get_mode_desc(int ifmw, 
+    struct ifmedia_type_to_subtype *ttos)
+{
+	int i;
+	struct ifmedia_description *desc;
+
+	for (i = 0; ttos->modes[i].desc != NULL; i++) {
+		if (ttos->modes[i].alias)
+			continue;
+		for (desc = ttos->modes[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (IFM_MODE(ifmw) == desc->ifmt_word)
+				return desc;
+		}
+	}
+
+	return NULL;
+}
+
+void
+print_media_word(int ifmw, int print_toptype)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int seen_option = 0, i;
+
+	/* Find the top-level interface type. */
+	desc = get_toptype_desc(ifmw);
+	ttos = get_toptype_ttos(ifmw);
+	if (desc->ifmt_string == NULL) {
+		printf("<unknown type>");
+		return;
+	} else if (print_toptype) {
+		printf("%s", desc->ifmt_string);
+	}
+
+	/*
+	 * Don't print the top-level type; it's not like we can
+	 * change it, or anything.
+	 */
+
+	/* Find subtype. */
+	desc = get_subtype_desc(ifmw, ttos);
+	if (desc == NULL) {
+		printf("<unknown subtype>");
+		return;
+	}
+
+	if (print_toptype)
+		putchar(' ');
+
+	printf("%s", desc->ifmt_string);
+
+	if (print_toptype) {
+		desc = get_mode_desc(ifmw, ttos);
+		if (desc != NULL && strcasecmp("autoselect", desc->ifmt_string))
+			printf(" mode %s", desc->ifmt_string);
+	}
+
+	/* Find options. */
+	for (i = 0; ttos->options[i].desc != NULL; i++) {
+		if (ttos->options[i].alias)
+			continue;
+		for (desc = ttos->options[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (ifmw & desc->ifmt_word) {
+				if (seen_option == 0)
+					printf(" <");
+				printf("%s%s", seen_option++ ? "," : "",
+				    desc->ifmt_string);
+			}
+		}
+	}
+	printf("%s", seen_option ? ">" : "");
+
+	if (print_toptype && IFM_INST(ifmw) != 0)
+		printf(" instance %d", IFM_INST(ifmw));
+}
+
+void
+print_media_word_ifconfig(int ifmw)
+{
+	struct ifmedia_description *desc;
+	struct ifmedia_type_to_subtype *ttos;
+	int seen_option = 0, i;
+
+	/* Find the top-level interface type. */
+	desc = get_toptype_desc(ifmw);
+	ttos = get_toptype_ttos(ifmw);
+	if (desc->ifmt_string == NULL) {
+		printf("<unknown type>");
+		return;
+	}
+
+	/*
+	 * Don't print the top-level type; it's not like we can
+	 * change it, or anything.
+	 */
+
+	/* Find subtype. */
+	desc = get_subtype_desc(ifmw, ttos);
+	if (desc == NULL) {
+		printf("<unknown subtype>");
+		return;
+	}
+
+	printf("media %s", desc->ifmt_string);
+
+	desc = get_mode_desc(ifmw, ttos);
+	if (desc != NULL)
+		printf(" mode %s", desc->ifmt_string);
+
+	/* Find options. */
+	for (i = 0; ttos->options[i].desc != NULL; i++) {
+		if (ttos->options[i].alias)
+			continue;
+		for (desc = ttos->options[i].desc;
+		    desc->ifmt_string != NULL; desc++) {
+			if (ifmw & desc->ifmt_word) {
+				if (seen_option == 0)
+					printf(" mediaopt ");
+				printf("%s%s", seen_option++ ? "," : "",
+				    desc->ifmt_string);
+			}
+		}
+	}
+
+	if (IFM_INST(ifmw) != 0)
+		printf(" instance %d", IFM_INST(ifmw));
+}
+
+/**********************************************************************
+ * ...until here.
+ **********************************************************************/
diff --git a/sys/conf/files b/sys/conf/files
index 2f8b5f6..ace37f3 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3523,3 +3523,11 @@ dev/xen/control/control.c	optional xen | xenhvm
 dev/xen/netfront/netfront.c	optional xen | xenhvm
 dev/xen/xenpci/xenpci.c		optional xenpci
 dev/xen/xenpci/evtchn.c         optional xenpci
+
+dev/etherswitch/arswitch.c		optional arswitch
+dev/etherswitch/etherswitch.c	optional etherswitch
+dev/etherswitch/etherswitch_if.m		optional etherswitch
+dev/etherswitch/mdio_if.m		optional miiproxy
+dev/etherswitch/mdio.c			optional miiproxy
+dev/etherswitch/miiproxy.c		optional miiproxy
+dev/etherswitch/rtl8366rb.c		optional rtl8366rb
diff --git a/sys/conf/kmod.mk b/sys/conf/kmod.mk
index 7bf7643..182a687 100644
--- a/sys/conf/kmod.mk
+++ b/sys/conf/kmod.mk
@@ -352,7 +352,8 @@ MFILES?= dev/acpica/acpi_if.m dev/acpi_support/acpi_wmi_if.m \
 	kern/bus_if.m kern/clock_if.m \
 	kern/cpufreq_if.m kern/device_if.m kern/serdev_if.m \
 	libkern/iconv_converter_if.m opencrypto/cryptodev_if.m \
-	pc98/pc98/canbus_if.m
+	pc98/pc98/canbus_if.m dev/etherswitch/etherswitch_if.m \
+	dev/etherswitch/mdio_if.h
 
 .for _srcsrc in ${MFILES}
 .for _ext in c h
diff --git a/sys/conf/options.mips b/sys/conf/options.mips
index 0384283..5a03c80 100644
--- a/sys/conf/options.mips
+++ b/sys/conf/options.mips
@@ -70,7 +70,8 @@ OCTEON_BOARD_CAPK_0100ND	opt_cvmx.h
 #
 # Options that control the Atheros SoC peripherals
 #
-ARGE_DEBUG			opt_global.h
+ARGE_DEBUG			opt_arge.h
+ARGE_MDIO			opt_arge.h
 
 #
 # Options that control the Ralink RT305xF Etherenet MAC.
diff --git a/sys/dev/etherswitch/arswitch.c b/sys/dev/etherswitch/arswitch.c
new file mode 100644
index 0000000..460e42b
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch.c
@@ -0,0 +1,675 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/etherswitch/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/arswitchreg.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+struct arswitch_softc {
+	struct mtx	sc_mtx;		/* serialize access to softc */
+	device_t	dev;
+	int		phy4cpu;	/* PHY4 is connected to the CPU */
+	int		numphys;	/* PHYs we manage */
+	int		page;
+	char		*ifname[AR8X16_NUM_PHYS];
+	device_t	miibus[AR8X16_NUM_PHYS];
+	struct ifnet	*ifp[AR8X16_NUM_PHYS];
+	struct callout	callout_tick;
+	etherswitch_info_t info;
+};
+
+
+
+#define ARSWITCH_LOCK(_sc)	mtx_lock(&(_sc)->sc_mtx)
+#define ARSWITCH_UNLOCK(_sc)	mtx_unlock(&(_sc)->sc_mtx)
+#define ARSWITCH_LOCK_ASSERT(_sc, _what)	mtx_assert(&(_s)c->sc_mtx, (_what))
+#define ARSWITCH_TRYLOCK(_sc)	mtx_trylock(&(_sc)->sc_mtx)
+
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#define DEVERR(dev, err, fmt, args...) do { \
+		if (err != 0) device_printf(dev, fmt, err, args); \
+	} while (0)
+#define DEBUG_INCRVAR(var)	do { \
+		var++; \
+	} while (0)
+
+static SYSCTL_NODE(_debug, OID_AUTO, arswitch, CTLFLAG_RD, 0, "arswitch");
+#else
+#define DPRINTF(dev, args...)
+#define DEVERR(dev, err, fmt, args...)
+#define DEBUG_INCRVAR(var)
+#endif
+
+static inline int arswitch_portforphy(int phy);
+static void arswitch_tick(void *arg);
+static int arswitch_ifmedia_upd(struct ifnet *);
+static void arswitch_ifmedia_sts(struct ifnet *, struct ifmediareq *);
+static int arswitch_readreg(device_t dev, int addr);
+static int arswitch_writereg(device_t dev, int addr, int value);
+int arswitch_modifyreg(device_t dev, int addr, int mask, int set);
+
+static void
+arswitch_identify(driver_t *driver, device_t parent)
+{
+	device_t child;
+
+	if (device_find_child(parent, driver->name, -1) == NULL) {
+		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
+	}
+}
+
+static int
+arswitch_probe(device_t dev)
+{
+	struct arswitch_softc *sc;
+	uint32_t id;
+	char *chipname, desc[256];
+
+	sc = device_get_softc(dev);
+	bzero(sc, sizeof(*sc));
+	sc->page = -1;
+	id = arswitch_readreg(dev, AR8X16_REG_MASK_CTRL);
+	switch ((id & AR8X16_MASK_CTRL_VER_MASK) >> AR8X16_MASK_CTRL_VER_SHIFT) {
+	case 1:
+		chipname = "AR8216";
+		break;
+	case 2:
+		chipname = "AR8226";
+		break;
+	case 16:
+		chipname = "AR8316";
+		break;
+	default:
+		chipname = NULL;
+	}
+	DPRINTF(dev, "chipname=%s, rev=%02x\n", chipname, id & AR8X16_MASK_CTRL_REV_MASK);
+	if (chipname != NULL) {
+		snprintf(desc, sizeof(desc), "Atheros %s Ethernet Switch", chipname);
+		device_set_desc_copy(dev, desc);
+		return (BUS_PROBE_DEFAULT);
+	}
+	return (ENXIO);
+}
+
+static int
+arswitch_attach_phys(struct arswitch_softc *sc)
+{
+	int phy, err = 0;
+	char name[IFNAMSIZ];
+
+	/* PHYs need an interface, so we generate a dummy one */
+	snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev));
+	for (phy = 0; phy < sc->numphys; phy++) {
+		sc->ifp[phy] = if_alloc(IFT_ETHER);
+		sc->ifp[phy]->if_softc = sc;
+		sc->ifp[phy]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING
+			| IFF_SIMPLEX;
+		sc->ifname[phy] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK);
+		bcopy(name, sc->ifname[phy], strlen(name)+1);
+		if_initname(sc->ifp[phy], sc->ifname[phy], arswitch_portforphy(phy));
+		err = mii_attach(sc->dev, &sc->miibus[phy], sc->ifp[phy],
+		    arswitch_ifmedia_upd, arswitch_ifmedia_sts, \
+		    BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+		DPRINTF(sc->dev, "%s attached to pseudo interface %s\n",
+		    device_get_nameunit(sc->miibus[phy]), sc->ifp[phy]->if_xname);
+		if (err != 0) {
+			device_printf(sc->dev, "attaching PHY %d failed\n", phy);
+		}
+	}
+	return (err);
+}
+
+static int
+arswitch_attach(device_t dev)
+{
+	struct arswitch_softc *sc;
+	int err = 0;
+
+	sc = device_get_softc(dev);
+	bzero(sc, sizeof(*sc));
+	sc->dev = dev;
+	mtx_init(&sc->sc_mtx, "arswitch", NULL, MTX_DEF);
+	sc->page = -1;
+
+	sc->info.es_nports = 5; /* XXX technically 6, but 6th not used */
+	sc->info.es_nvlangroups = 16;
+	strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name));
+	
+	/* XXX make this configurable */
+	sc->numphys = 4;
+	sc->phy4cpu = 1;
+
+#ifdef NOTYET	
+	arswitch_writereg(dev, AR8X16_REG_MASK_CTRL, AR8X16_MASK_CTRL_SOFT_RESET);
+	DELAY(1000);
+	if (arswitch_readreg(dev, AR8X16_REG_MASK_CTRL) & AR8X16_MASK_CTRL_SOFT_RESET) {
+		device_printf(dev, "unable to reset switch\n");
+		return (ENXIO);
+	}
+	arswitch_modifyreg(dev, AR8X16_REG_GLOBAL_CTRL,
+	    AR8X16_FLOOD_MASK_BCAST_TO_CPU,
+	    AR8X16_FLOOD_MASK_BCAST_TO_CPU);
+#endif
+	err = arswitch_attach_phys(sc);
+	if (err != 0)
+		return (err);
+
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	err = bus_generic_attach(dev);
+	if (err != 0)
+		return (err);
+	
+	callout_init_mtx(&sc->callout_tick, &sc->sc_mtx, 0);
+	arswitch_tick(sc);
+	
+	return (err);
+}
+
+static int
+arswitch_detach(device_t dev)
+{
+	struct arswitch_softc *sc = device_get_softc(dev);
+	int i;
+
+	callout_drain(&sc->callout_tick);
+
+	for (i=0; i < sc->numphys; i++) {
+		if (sc->miibus[i] != NULL)
+			device_delete_child(dev, sc->miibus[i]);
+		if (sc->ifp[i] != NULL)
+			if_free(sc->ifp[i]);
+		free(sc->ifname[i], M_DEVBUF);
+	}
+
+	bus_generic_detach(dev);
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+/*
+ * Convert PHY number to port number. PHY0 is connected to port 1, PHY1 to
+ * port 2, etc.
+ */
+static inline int
+arswitch_portforphy(int phy)
+{
+	return (phy+1);
+}
+
+static inline struct mii_data *
+arswitch_miiforport(struct arswitch_softc *sc, int port)
+{
+	int phy = port-1;
+
+	if (phy < 0 || phy >= sc->numphys)
+		return (NULL);
+	return (device_get_softc(sc->miibus[phy]));
+}
+
+/*
+ * Convert port status to ifmedia.
+ */
+static void
+arswitch_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active)
+{
+	*media_active = IFM_ETHER;
+	*media_status = IFM_AVALID;
+
+	if ((portstatus & AR8X16_PORT_STS_LINK_UP) != 0)
+		*media_status |= IFM_ACTIVE;
+	else {
+		*media_active |= IFM_NONE;
+		return;
+	}
+	switch (portstatus & AR8X16_PORT_STS_SPEED_MASK) {
+	case AR8X16_PORT_STS_SPEED_10:
+		*media_active |= IFM_10_T;
+		break;
+	case AR8X16_PORT_STS_SPEED_100:
+		*media_active |= IFM_100_TX;
+		break;
+	case AR8X16_PORT_STS_SPEED_1000:
+		*media_active |= IFM_1000_T;
+		break;
+	}
+	if ((portstatus & AR8X16_PORT_STS_DUPLEX) == 0)
+		*media_active |= IFM_FDX;
+	else
+		*media_active |= IFM_HDX;
+	if ((portstatus & AR8X16_PORT_STS_TXFLOW) != 0)
+		*media_active |= IFM_ETH_TXPAUSE;
+	if ((portstatus & AR8X16_PORT_STS_RXFLOW) != 0)
+		*media_active |= IFM_ETH_RXPAUSE;
+}
+
+/*
+ * Poll the status for all PHYs.  We're using the switch port status because
+ * thats a lot quicker to read than talking to all the PHYs.  Care must be
+ * taken that the resulting ifmedia_active is identical to what the PHY will
+ * compute, or gratuitous link status changes will occur whenever the PHYs
+ * update function is called.
+ */
+static void
+arswitch_miipollstat(struct arswitch_softc *sc)
+{
+	int i;
+	struct mii_data *mii;
+	struct mii_softc *miisc;
+	int portstatus;
+
+	for (i = 0; i < sc->numphys; i++) {
+		if (sc->miibus[i] == NULL)
+			continue;
+		mii = device_get_softc(sc->miibus[i]);
+		portstatus = arswitch_readreg(sc->dev,
+		    AR8X16_REG_PORT_STS(arswitch_portforphy(i)));
+		//DPRINTF(sc->dev, "p[%d]=%b\n", arge_portforphy(i), portstatus,
+		//	"\20\3TXMAC\4RXMAC\5TXFLOW\6RXFLOW\7DUPLEX\11LINK_UP\12LINK_AUTO\13LINK_PAUSE");
+		arswitch_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active);
+		LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+			if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst)
+				continue;
+			mii_phy_update(miisc, MII_POLLSTAT);
+		}
+	}	
+}
+
+static void
+arswitch_tick(void *arg)
+{
+	struct arswitch_softc *sc = arg;
+
+	arswitch_miipollstat(sc);
+	callout_reset(&sc->callout_tick, hz, arswitch_tick, sc);
+}
+
+static etherswitch_info_t *
+arswitch_getinfo(device_t dev)
+{
+	struct arswitch_softc *sc = device_get_softc(dev);
+	
+	return (&sc->info);
+}
+
+static inline void
+arswitch_split_setpage(device_t dev, uint32_t addr, uint16_t *phy, uint16_t *reg)
+{
+	struct arswitch_softc *sc = device_get_softc(dev);
+	uint16_t page;
+
+	page = ((addr) >> 9) & 0xffff;
+	*phy = (((addr) >> 6) & 0x07) | 0x10;
+	*reg = ((addr) >> 1) & 0x1f;
+
+	if (sc->page != page) {
+		MDIO_WRITEREG(device_get_parent(dev), 0x18, 0, page);
+		sc->page = page;
+	}
+}
+
+/*
+ * Read half a register.  Some of the registers define control bits, and
+ * the sequence of half-word accesses matters.  The register addresses
+ * are word-even (mod 4).
+ */
+static inline int
+arswitch_readreg16(device_t dev, int addr)
+{
+	uint16_t phy, reg;
+	
+	arswitch_split_setpage(dev, addr, &phy, &reg);
+	return (MDIO_READREG(device_get_parent(dev), phy, reg));
+}
+
+/*
+ * Write half a register
+ */
+static inline int
+arswitch_writereg16(device_t dev, int addr, int data)
+{
+	uint16_t phy, reg;
+	
+	arswitch_split_setpage(dev, addr, &phy, &reg);
+	return (MDIO_WRITEREG(device_get_parent(dev), phy, reg, data));
+}
+
+static inline int
+arswitch_readreg_lsb(device_t dev, int addr)
+{
+	return (arswitch_readreg16(dev, addr));
+}
+
+static inline int
+arswitch_readreg_msb(device_t dev, int addr)
+{
+	return (arswitch_readreg16(dev, addr + 2) << 16);
+}
+
+static inline int
+arswitch_writereg_lsb(device_t dev, int addr, int data)
+{
+	return (arswitch_writereg16(dev, addr, data & 0xffff));
+}
+
+static inline int
+arswitch_writereg_msb(device_t dev, int addr, int data)
+{
+	return (arswitch_writereg16(dev, addr + 2, data >> 16));
+}
+
+static int
+arswitch_readreg(device_t dev, int addr)
+{
+	return (arswitch_readreg_lsb(dev, addr) | arswitch_readreg_msb(dev, addr));
+}
+
+static int
+arswitch_writereg(device_t dev, int addr, int value)
+{
+	arswitch_writereg_lsb(dev, addr, value);
+	return (arswitch_writereg_msb(dev, addr, value));
+}
+
+int
+arswitch_modifyreg(device_t dev, int addr, int mask, int set)
+{
+	int value;
+	
+	value = arswitch_readreg(dev, addr);
+	value &= ~mask;
+	value |= set;
+	return arswitch_writereg(dev, addr, value);
+}
+
+static int
+arswitch_getport(device_t dev, etherswitch_port_t *p)
+{
+	struct arswitch_softc *sc = device_get_softc(dev);
+	struct mii_data *mii;
+	struct ifmediareq *ifmr = &p->es_ifmr;
+	int err;
+	
+	if (p->es_port < 0 || p->es_port >= AR8X16_NUM_PORTS)
+		return (ENXIO);
+	p->es_vlangroup = 0;
+
+	mii = arswitch_miiforport(sc, p->es_port);
+	if (p->es_port == 0) {
+		/* fill in fixed values for CPU port */
+		ifmr->ifm_count = 0;
+		ifmr->ifm_current = ifmr->ifm_active =
+		    IFM_ETHER | IFM_1000_T | IFM_FDX;
+		ifmr->ifm_mask = 0;
+		ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+	} else if (mii != NULL) {
+		err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+		    &mii->mii_media, SIOCGIFMEDIA);
+		if (err)
+			return (err);
+	} else {
+		return (ENXIO);
+	}
+	return (0);
+}
+
+static int
+arswitch_setport(device_t dev, etherswitch_port_t *p)
+{
+#if 0
+	int err;
+	struct arswitch_softc *sc;
+	struct ifmedia *ifm;
+	struct mii_data *mii;
+
+	if (p->es_port < 0 || p->es_port >= RTL8366RB_NUM_PHYS)
+		return (ENXIO);
+	err = smi_rmw(dev, RTL8366RB_PVCR_REG(p->es_port),
+		RTL8366RB_PVCR_VAL(p->es_port, RTL8366RB_PVCR_PORT_MASK),
+		RTL8366RB_PVCR_VAL(p->es_port, p->es_vlangroup), ARSWITCH_WAITOK);
+	if (err)
+		return (err);
+	sc = device_get_softc(dev);
+	mii = device_get_softc(sc->miibus[p->es_port]);
+	ifm = &mii->mii_media;
+	err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCSIFMEDIA);
+	return (err);
+#else
+	return (0);
+#endif
+}
+
+static int
+arswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+#if 0
+	uint16_t vmcr[3];
+	int i;
+	
+	for (i=0; i<3; i++)
+		vmcr[i] = arswitch_readreg(dev, RTL8366RB_VMCR(i, vg->es_vlangroup));
+#endif
+		
+	vg->es_vid = 0;
+	vg->es_member_ports = 0;
+	vg->es_untagged_ports = 0;
+	vg->es_fid = 0;
+	return (0);
+}
+
+static int
+arswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+#if 0
+	int g = vg->es_vlangroup;
+
+	arswitch_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_DOT1Q_REG, g),
+		(vg->es_vid << RTL8366RB_VMCR_DOT1Q_VID_SHIFT) & RTL8366RB_VMCR_DOT1Q_VID_MASK);
+	arswitch_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_MU_REG, g),
+		((vg->es_member_ports << RTL8366RB_VMCR_MU_MEMBER_SHIFT) & RTL8366RB_VMCR_MU_MEMBER_MASK) |
+		((vg->es_untagged_ports << RTL8366RB_VMCR_MU_UNTAG_SHIFT) & RTL8366RB_VMCR_MU_UNTAG_MASK));
+	arswitch_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_FID_REG, g),
+		vg->es_fid);
+#endif
+	return (0);
+}
+
+/*
+ * access PHYs integrated into the switch chip through the switch's MDIO
+ * control register.
+ */
+static int
+arswitch_readphy(device_t dev, int phy, int reg)
+{
+	uint32_t data = 0, ctrl;
+	int err, timeout;
+
+	if (phy < 0 || phy >= 32)
+		return (ENXIO);
+	if (reg < 0 || reg >= 32)
+		return (ENXIO);
+	err = arswitch_writereg_msb(dev, AR8X16_REG_MDIO_CTRL,
+	    AR8X16_MDIO_CTRL_BUSY | AR8X16_MDIO_CTRL_MASTER_EN |
+	    AR8X16_MDIO_CTRL_CMD_READ |
+	    (phy << AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT) |
+	    (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT));
+	DEVERR(dev, err, "arswitch_readphy()=%d: phy=%d.%02x\n", phy, reg);
+	if (err != 0)
+		return (-1);
+	for (timeout = 100; timeout--; ) {
+		ctrl = arswitch_readreg_msb(dev, AR8X16_REG_MDIO_CTRL);
+		if ((ctrl & AR8X16_MDIO_CTRL_BUSY) == 0)
+			break;
+	}
+	if (timeout < 0)
+		err = EIO;
+	data = arswitch_readreg_lsb(dev, AR8X16_REG_MDIO_CTRL) & AR8X16_MDIO_CTRL_DATA_MASK;
+	return (data);
+}
+
+static int
+arswitch_writephy(device_t dev, int phy, int reg, int data)
+{
+	uint32_t ctrl;
+	int err, timeout;
+	
+	if (reg < 0 || reg >= 32)
+		return (ENXIO);
+	err = arswitch_writereg_lsb(dev, AR8X16_REG_MDIO_CTRL,
+	    (data & AR8X16_MDIO_CTRL_DATA_MASK));
+	DEVERR(dev, err, "arswitch_writephy()=%d: phy=%d.%02x\n", phy, reg);
+	if (err != 0)
+		return (err);
+	err = arswitch_writereg_msb(dev, AR8X16_REG_MDIO_CTRL,
+	    AR8X16_MDIO_CTRL_BUSY |
+	    AR8X16_MDIO_CTRL_MASTER_EN |
+	    AR8X16_MDIO_CTRL_CMD_WRITE |
+	    (phy << AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT) |
+	    (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT));
+	DEVERR(dev, err, "arswitch_writephy()=%d: phy=%d.%02x\n", phy, reg);
+	if (err != 0)
+		return (err);
+	for (timeout = 100; timeout--; ) {
+		ctrl = arswitch_readreg(dev, AR8X16_REG_MDIO_CTRL);
+		if ((ctrl & AR8X16_MDIO_CTRL_BUSY) == 0)
+			break;
+	}
+	if (timeout < 0)
+		err = EIO;
+	DEVERR(dev, err, "arswitch_writephy()=%d: phy=%d.%02x\n", phy, reg);
+	return (err);
+}
+
+static void
+arswitch_statchg(device_t dev)
+{
+	device_printf(dev, "%s\n", __func__);
+}
+
+static int
+arswitch_ifmedia_upd(struct ifnet *ifp)
+{
+	struct arswitch_softc *sc = ifp->if_softc;
+	struct mii_data *mii = arswitch_miiforport(sc, ifp->if_dunit);
+
+	if (mii == NULL)
+		return (ENXIO);
+	mii_mediachg(mii);
+	return (0);
+}
+
+static void
+arswitch_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+	struct arswitch_softc *sc = ifp->if_softc;
+	struct mii_data *mii = arswitch_miiforport(sc, ifp->if_dunit);
+
+	device_printf(sc->dev, "%s\n", __func__);
+	if (mii == NULL)
+		return;
+	mii_pollstat(mii);
+	ifmr->ifm_active = mii->mii_media_active;
+	ifmr->ifm_status = mii->mii_media_status;
+}
+
+static device_method_t arswitch_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_identify,	arswitch_identify),
+	DEVMETHOD(device_probe,		arswitch_probe),
+	DEVMETHOD(device_attach,	arswitch_attach),
+	DEVMETHOD(device_detach,	arswitch_detach),
+	
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	
+	/* MII interface */
+	DEVMETHOD(miibus_readreg,	arswitch_readphy),
+	DEVMETHOD(miibus_writereg,	arswitch_writephy),
+	DEVMETHOD(miibus_statchg,	arswitch_statchg),
+
+	/* MDIO interface */
+	DEVMETHOD(mdio_readreg,		arswitch_readphy),
+	DEVMETHOD(mdio_writereg,	arswitch_writephy),
+
+	/* etherswitch interface */
+	DEVMETHOD(etherswitch_getinfo,	arswitch_getinfo),
+	DEVMETHOD(etherswitch_readreg,	arswitch_readreg),
+	DEVMETHOD(etherswitch_writereg,	arswitch_writereg),
+	DEVMETHOD(etherswitch_readphyreg,	arswitch_readphy),
+	DEVMETHOD(etherswitch_writephyreg,	arswitch_writephy),
+	DEVMETHOD(etherswitch_getport,	arswitch_getport),
+	DEVMETHOD(etherswitch_setport,	arswitch_setport),
+	DEVMETHOD(etherswitch_getvgroup,	arswitch_getvgroup),
+	DEVMETHOD(etherswitch_setvgroup,	arswitch_setvgroup),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(arswitch, arswitch_driver, arswitch_methods,
+    sizeof(struct arswitch_softc));
+static devclass_t arswitch_devclass;
+
+DRIVER_MODULE(arswitch, mdio, arswitch_driver, arswitch_devclass, 0, 0);
+DRIVER_MODULE(miibus, arswitch, miibus_driver, miibus_devclass, 0, 0);
+DRIVER_MODULE(mdio, arswitch, mdio_driver, mdio_devclass, 0, 0);
+DRIVER_MODULE(etherswitch, arswitch, etherswitch_driver, etherswitch_devclass, 0, 0);
+MODULE_VERSION(arswitch, 1);
+MODULE_DEPEND(arswitch, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(arswitch, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/arswitchreg.h b/sys/dev/etherswitch/arswitchreg.h
new file mode 100644
index 0000000..cd827cf
--- /dev/null
+++ b/sys/dev/etherswitch/arswitchreg.h
@@ -0,0 +1,268 @@
+/*-
+ * Copyright (c) 2011 Aleksandr Rybalko.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __AR8X16_SWITCHREG_H__
+#define __AR8X16_SWITCHREG_H__
+
+#define AR8X16_REG_MASK_CTRL		0x0000
+#define 	AR8X16_MASK_CTRL_REV_MASK	0x000000ff
+#define 	AR8X16_MASK_CTRL_VER_MASK	0x0000ff00
+#define 	AR8X16_MASK_CTRL_VER_SHIFT	8
+#define 	AR8X16_MASK_CTRL_SOFT_RESET	(1 << 31)
+
+#define AR8X16_REG_MODE			0x0008
+/* DIR-615 E4 U-Boot */
+#define		AR8X16_MODE_DIR_615_UBOOT	0x8d1003e0
+/* From Ubiquiti RSPRO */
+#define		AR8X16_MODE_RGMII_PORT4_ISO	0x81461bea
+#define		AR8X16_MODE_RGMII_PORT4_SWITCH	0x01261be2
+/* AVM Fritz!Box 7390 */
+#define		AR8X16_MODE_GMII		0x010e5b71
+/* from avm_cpmac/linux_ar_reg.h */
+#define		AR8X16_MODE_RESERVED		0x000e1b20
+#define		AR8X16_MODE_MAC0_GMII_EN	(1u <<  0)
+#define		AR8X16_MODE_MAC0_RGMII_EN	(1u <<  1)
+#define		AR8X16_MODE_PHY4_GMII_EN	(1u <<  2)
+#define		AR8X16_MODE_PHY4_RGMII_EN	(1u <<  3)
+#define		AR8X16_MODE_MAC0_MAC_MODE	(1u <<  4)
+#define		AR8X16_MODE_RGMII_RXCLK_DELAY_EN (1u <<  6)
+#define		AR8X16_MODE_RGMII_TXCLK_DELAY_EN (1u <<  7)
+#define		AR8X16_MODE_MAC5_MAC_MODE	(1u << 14)
+#define		AR8X16_MODE_MAC5_PHY_MODE	(1u << 15)
+#define		AR8X16_MODE_TXDELAY_S0		(1u << 21)
+#define		AR8X16_MODE_TXDELAY_S1		(1u << 22)
+#define		AR8X16_MODE_RXDELAY_S0		(1u << 23)
+#define		AR8X16_MODE_LED_OPEN_EN		(1u << 24)
+#define		AR8X16_MODE_SPI_EN		(1u << 25)
+#define		AR8X16_MODE_RXDELAY_S1		(1u << 26)
+#define		AR8X16_MODE_POWER_ON_SEL	(1u << 31)
+
+#define AR8X16_REG_ISR			0x0010
+#define AR8X16_REG_IMR			0x0014
+
+#define AR8X16_REG_SW_MAC_ADDR0		0x0020
+#define AR8X16_REG_SW_MAC_ADDR1		0x0024
+
+#define AR8X16_REG_FLOOD_MASK		0x002c
+#define 	AR8X16_FLOOD_MASK_BCAST_TO_CPU	(1 << 26)
+
+#define AR8X16_REG_GLOBAL_CTRL		0x0030
+#define 	AR8X16_GLOBAL_CTRL_MTU_MASK	0x00000fff
+
+#define AR8X16_REG_VLAN_CTRL			0x0040
+#define		AR8X16_VLAN_OP			0x00000007
+#define		AR8X16_VLAN_OP_NOOP		0x0
+#define		AR8X16_VLAN_OP_FLUSH		0x1
+#define		AR8X16_VLAN_OP_LOAD		0x2
+#define		AR8X16_VLAN_OP_PURGE		0x3
+#define		AR8X16_VLAN_OP_REMOVE_PORT	0x4
+#define		AR8X16_VLAN_OP_GET_NEXT		0x5
+#define		AR8X16_VLAN_OP_GET		0x6
+#define		AR8X16_VLAN_ACTIVE		(1 << 3)
+#define		AR8X16_VLAN_FULL		(1 << 4)
+#define		AR8X16_VLAN_PORT		0x00000f00
+#define		AR8X16_VLAN_PORT_SHIFT		8
+#define		AR8X16_VLAN_VID			0x0fff0000
+#define		AR8X16_VLAN_VID_SHIFT		16
+#define		AR8X16_VLAN_PRIO		0x70000000
+#define		AR8X16_VLAN_PRIO_SHIFT		28
+#define		AR8X16_VLAN_PRIO_EN		(1 << 31)
+
+#define AR8X16_REG_VLAN_DATA		0x0044
+#define		AR8X16_VLAN_MEMBER		0x000003ff
+#define		AR8X16_VLAN_VALID		(1 << 11)
+
+#define AR8X16_REG_ARL_CTRL0		0x0050
+#define AR8X16_REG_ARL_CTRL1		0x0054
+#define AR8X16_REG_ARL_CTRL2		0x0058
+
+#define AR8X16_REG_AT_CTRL		0x005c
+#define		AR8X16_AT_CTRL_ARP_EN		(1 << 20)
+
+#define AR8X16_REG_IP_PRIORITY_1     	0x0060
+#define AR8X16_REG_IP_PRIORITY_2     	0x0064
+#define AR8X16_REG_IP_PRIORITY_3     	0x0068
+#define AR8X16_REG_IP_PRIORITY_4     	0x006C
+
+#define AR8X16_REG_TAG_PRIO		0x0070
+
+#define AR8X16_REG_SERVICE_TAG		0x0074
+#define		AR8X16_SERVICE_TAG_MASK		0x0000ffff
+
+#define AR8X16_REG_CPU_PORT		0x0078
+#define		AR8X16_MIRROR_PORT_SHIFT	4
+#define		AR8X16_CPU_PORT_EN		(1 << 8)
+
+#define AR8X16_REG_MIB_FUNC0		0x0080
+#define		AR8X16_MIB_TIMER_MASK		0x0000ffff
+#define		AR8X16_MIB_AT_HALF_EN		(1 << 16)
+#define		AR8X16_MIB_BUSY			(1 << 17)
+#define		AR8X16_MIB_FUNC_SHIFT		24
+#define		AR8X16_MIB_FUNC_NO_OP		0x0
+#define		AR8X16_MIB_FUNC_FLUSH		0x1
+#define		AR8X16_MIB_FUNC_CAPTURE		0x3
+#define		AR8X16_MIB_FUNC_XXX		(1 << 30) /* 0x40000000 */
+
+#define AR8X16_REG_MDIO_HIGH_ADDR	0x0094
+
+#define AR8X16_REG_MDIO_CTRL		0x0098
+#define		AR8X16_MDIO_CTRL_DATA_MASK	0x0000ffff
+#define		AR8X16_MDIO_CTRL_REG_ADDR_SHIFT	16
+#define		AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT	21
+#define		AR8X16_MDIO_CTRL_CMD_WRITE	0
+#define		AR8X16_MDIO_CTRL_CMD_READ	(1 << 27)
+#define		AR8X16_MDIO_CTRL_MASTER_EN	(1 << 30)
+#define		AR8X16_MDIO_CTRL_BUSY		(1 << 31)
+
+#define AR8X16_REG_PORT_BASE(_p)	(0x0100 + (_p) * 0x0100)
+
+#define AR8X16_REG_PORT_STS(_p)		(AR8X16_REG_PORT_BASE((_p)) + 0x0000)
+#define		AR8X16_PORT_STS_SPEED_MASK	0x00000003
+#define		AR8X16_PORT_STS_SPEED_10	0
+#define		AR8X16_PORT_STS_SPEED_100	1
+#define		AR8X16_PORT_STS_SPEED_1000	2
+#define		AR8X16_PORT_STS_TXMAC		(1 << 2)
+#define		AR8X16_PORT_STS_RXMAC		(1 << 3)
+#define		AR8X16_PORT_STS_TXFLOW		(1 << 4)
+#define		AR8X16_PORT_STS_RXFLOW		(1 << 5)
+#define		AR8X16_PORT_STS_DUPLEX		(1 << 6)
+#define		AR8X16_PORT_STS_LINK_UP		(1 << 8)
+#define		AR8X16_PORT_STS_LINK_AUTO	(1 << 9)
+#define		AR8X16_PORT_STS_LINK_PAUSE	(1 << 10)
+
+#define AR8X16_REG_PORT_CTRL(_p)	(AR8X16_REG_PORT_BASE((_p)) + 0x0004)
+#define		AR8X16_PORT_CTRL_STATE_MASK	0x00000007
+#define		AR8X16_PORT_CTRL_STATE_DISABLED	0
+#define		AR8X16_PORT_CTRL_STATE_BLOCK	1
+#define		AR8X16_PORT_CTRL_STATE_LISTEN	2
+#define		AR8X16_PORT_CTRL_STATE_LEARN	3
+#define		AR8X16_PORT_CTRL_STATE_FORWARD	4
+#define		AR8X16_PORT_CTRL_LEARN_LOCK	(1 << 7)
+#define		AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT 8
+#define		AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_KEEP	0
+#define		AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP 1
+#define		AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD 2
+#define		AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_DOUBLE_TAG 3
+#define		AR8X16_PORT_CTRL_IGMP_SNOOP	(1 << 10)
+#define		AR8X16_PORT_CTRL_HEADER		(1 << 11)
+#define		AR8X16_PORT_CTRL_MAC_LOOP	(1 << 12)
+#define		AR8X16_PORT_CTRL_SINGLE_VLAN	(1 << 13)
+#define		AR8X16_PORT_CTRL_LEARN		(1 << 14)
+#define		AR8X16_PORT_CTRL_DOUBLE_TAG	(1 << 15)
+#define		AR8X16_PORT_CTRL_MIRROR_TX	(1 << 16)
+#define		AR8X16_PORT_CTRL_MIRROR_RX	(1 << 17)
+
+#define AR8X16_REG_PORT_VLAN(_p)	(AR8X16_REG_PORT_BASE((_p)) + 0x0008)
+
+#define		AR8X16_PORT_VLAN_DEFAULT_ID_SHIFT	0
+#define		AR8X16_PORT_VLAN_DEST_PORTS_SHIFT	16
+#define		AR8X16_PORT_VLAN_MODE_MASK		0xc0000000
+#define		AR8X16_PORT_VLAN_MODE_SHIFT		30
+#define		AR8X16_PORT_VLAN_MODE_PORT_ONLY		0
+#define		AR8X16_PORT_VLAN_MODE_PORT_FALLBACK	1
+#define		AR8X16_PORT_VLAN_MODE_VLAN_ONLY		2
+#define		AR8X16_PORT_VLAN_MODE_SECURE		3
+
+#define AR8X16_REG_PORT_RATE_LIM(_p)	(AR8X16_REG_PORT_BASE((_p)) + 0x000c)
+#define 	AR8X16_PORT_RATE_LIM_128KB	0
+#define 	AR8X16_PORT_RATE_LIM_256KB	1
+#define 	AR8X16_PORT_RATE_LIM_512KB	2
+#define 	AR8X16_PORT_RATE_LIM_1MB	3
+#define 	AR8X16_PORT_RATE_LIM_2MB	4
+#define 	AR8X16_PORT_RATE_LIM_4MB	5
+#define 	AR8X16_PORT_RATE_LIM_8MB	6
+#define 	AR8X16_PORT_RATE_LIM_16MB	7
+#define 	AR8X16_PORT_RATE_LIM_32MB	8
+#define 	AR8X16_PORT_RATE_LIM_64MB	9
+#define 	AR8X16_PORT_RATE_LIM_IN_EN	(1 << 24)
+#define 	AR8X16_PORT_RATE_LIM_OUT_EN	(1 << 23)
+#define 	AR8X16_PORT_RATE_LIM_IN_MASK	0x000f0000
+#define 	AR8X16_PORT_RATE_LIM_IN_SHIFT	16
+#define 	AR8X16_PORT_RATE_LIM_OUT_MASK	0x0000000f
+#define 	AR8X16_PORT_RATE_LIM_OUT_SHIFT	0
+
+#define AR8X16_REG_PORT_PRIORITY(_p)	(AR8X16_REG_PORT_BASE((_p)) + 0x0010)
+
+#define AR8X16_REG_STATS_BASE(_p)	(0x20000 + (_p) * 0x100)
+
+#define AR8X16_STATS_RXBROAD		0x0000
+#define AR8X16_STATS_RXPAUSE		0x0004
+#define AR8X16_STATS_RXMULTI		0x0008
+#define AR8X16_STATS_RXFCSERR		0x000c
+#define AR8X16_STATS_RXALIGNERR		0x0010
+#define AR8X16_STATS_RXRUNT		0x0014
+#define AR8X16_STATS_RXFRAGMENT		0x0018
+#define AR8X16_STATS_RX64BYTE		0x001c
+#define AR8X16_STATS_RX128BYTE		0x0020
+#define AR8X16_STATS_RX256BYTE		0x0024
+#define AR8X16_STATS_RX512BYTE		0x0028
+#define AR8X16_STATS_RX1024BYTE		0x002c
+#define AR8X16_STATS_RX1518BYTE		0x0030
+#define AR8X16_STATS_RXMAXBYTE		0x0034
+#define AR8X16_STATS_RXTOOLONG		0x0038
+#define AR8X16_STATS_RXGOODBYTE		0x003c
+#define AR8X16_STATS_RXBADBYTE		0x0044
+#define AR8X16_STATS_RXOVERFLOW		0x004c
+#define AR8X16_STATS_FILTERED		0x0050
+#define AR8X16_STATS_TXBROAD		0x0054
+#define AR8X16_STATS_TXPAUSE		0x0058
+#define AR8X16_STATS_TXMULTI		0x005c
+#define AR8X16_STATS_TXUNDERRUN		0x0060
+#define AR8X16_STATS_TX64BYTE		0x0064
+#define AR8X16_STATS_TX128BYTE		0x0068
+#define AR8X16_STATS_TX256BYTE		0x006c
+#define AR8X16_STATS_TX512BYTE		0x0070
+#define AR8X16_STATS_TX1024BYTE		0x0074
+#define AR8X16_STATS_TX1518BYTE		0x0078
+#define AR8X16_STATS_TXMAXBYTE		0x007c
+#define AR8X16_STATS_TXOVERSIZE		0x0080
+#define AR8X16_STATS_TXBYTE		0x0084
+#define AR8X16_STATS_TXCOLLISION	0x008c
+#define AR8X16_STATS_TXABORTCOL		0x0090
+#define AR8X16_STATS_TXMULTICOL		0x0094
+#define AR8X16_STATS_TXSINGLECOL	0x0098
+#define AR8X16_STATS_TXEXCDEFER		0x009c
+#define AR8X16_STATS_TXDEFER		0x00a0
+#define AR8X16_STATS_TXLATECOL		0x00a4
+
+#define AR8X16_PORT_CPU			0
+#define AR8X16_NUM_PORTS		6
+#define AR8X16_NUM_PHYS			5
+#define	AR8X16_MAGIC			0xc000050e
+
+#define AR8X16_PHY_ID1			0x004d
+#define AR8X16_PHY_ID2			0xd041
+
+#define AR8X16_PORT_MASK(_port)		(1 << (_port))
+#define AR8X16_PORT_MASK_ALL		((1<<AR8X16_NUM_PORTS)-1)
+#define AR8X16_PORT_MASK_BUT(_port)	(AR8X16_PORT_MASK_ALL & ~(1 << (_port)))
+
+#define AR8X16_MAX_VLANS		16
+
+#endif /* __AR8X16_SWITCHREG_H__ */
+
diff --git a/sys/dev/etherswitch/arswitchvar.h b/sys/dev/etherswitch/arswitchvar.h
new file mode 100644
index 0000000..9f8bcb0
--- /dev/null
+++ b/sys/dev/etherswitch/arswitchvar.h
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2011 Aleksandr Rybalko.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _AR8X16_SWITCHVAR_H_
+#define _AR8X16_SWITCHVAR_H_
+
+struct ar8x16_switch_softc {
+	device_t		sc_dev;
+	device_t		parent;
+	int 			ports;
+	int 			vlans;
+	struct switch_capability *caps;
+	int			page_reg;
+	int			page_phy;
+	int			devid;
+	int			revid;
+	uint32_t		sc_mii_mode;
+	uint16_t		*vlan_idx;
+};
+
+/* Ask switch bus to read/write phy registers */
+#define MII_READ(sc, phy, reg)				\
+	MIIBUS_READREG(sc->parent, phy, reg)
+#define MII_WRITE(sc, phy, reg, val)			\
+	MIIBUS_WRITEREG(sc->parent, phy, reg, val)
+
+#define	REG_REG(reg)		(((reg) >> 1) & 0x01e)
+#define	REG_PHY(reg)		(((reg) >> 6) & 0x007)
+#define	REG_PAGE(reg)		(((reg) >> 9) & 0x1ff)
+#define	VLAN_IDX_VALID		0x8000
+
+#define SET4(sc, reg, mask, val)			\
+	WRITE4(sc, reg, (READ4(sc, reg) & ~mask) | val)
+
+#define WAIT4(sc, reg, field, value, timeout_usec)	\
+    ({int result;					\
+    do {						\
+	uint32_t c, timeout = timeout_usec;		\
+	while (1) {					\
+	    c = (READ4(sc, reg) & field);		\
+	    if (c == (value)) {				\
+		result = 0;				\
+		break;					\
+	    } else if (!timeout) {			\
+		result = -1;				\
+		break;					\
+	    } else {					\
+		DELAY(1); timeout--;			\
+	    }						\
+	}						\
+    } while (0);					\
+    result;})
+
+#endif /* _AR8X16_SWITCHVAR_H_ */
diff --git a/sys/dev/etherswitch/etherswitch.c b/sys/dev/etherswitch/etherswitch.c
new file mode 100644
index 0000000..a216289
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch.c
@@ -0,0 +1,257 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/lock.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sx.h>
+#include <sys/systm.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include "etherswitch_if.h"
+
+#define BUFSIZE 1024
+
+struct etherswitch_softc {
+	device_t sc_dev;
+	int sc_count;
+
+	struct cdev *sc_devnode;
+	struct sx sc_lock;
+};
+
+#define	SWITCH_LOCK(sc)			sx_xlock(&(sc)->sc_lock)
+#define	SWITCH_UNLOCK(sc)			sx_xunlock(&(sc)->sc_lock)
+
+static int etherswitch_probe(device_t);
+static int etherswitch_attach(device_t);
+static int etherswitch_detach(device_t);
+static void etherswitch_identify(driver_t *driver, device_t parent);
+
+devclass_t etherswitch_devclass;
+
+static device_method_t etherswitch_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_identify,	etherswitch_identify),
+	DEVMETHOD(device_probe,		etherswitch_probe),
+	DEVMETHOD(device_attach,	etherswitch_attach),
+	DEVMETHOD(device_detach,	etherswitch_detach),
+
+	{ 0, 0 }
+};
+
+driver_t etherswitch_driver = {
+	"etherswitch",
+	etherswitch_methods,
+	sizeof(struct etherswitch_softc),
+};
+
+static	d_open_t	etherswitchopen;
+static	d_close_t	etherswitchclose;
+static	d_write_t	etherswitchwrite;
+static	d_read_t	etherswitchread;
+static	d_ioctl_t	etherswitchioctl;
+
+static struct cdevsw etherswitch_cdevsw = {
+	.d_version =	D_VERSION,
+	.d_flags =	D_TRACKCLOSE,
+	.d_open =	etherswitchopen,
+	.d_close =	etherswitchclose,
+	.d_read =	etherswitchread,
+	.d_write =	etherswitchwrite,
+	.d_ioctl =	etherswitchioctl,
+	.d_name =	"etherswitch",
+};
+
+static void
+etherswitch_identify(driver_t *driver, device_t parent)
+{
+	if (device_find_child(parent, "etherswitch", -1) == NULL)
+		BUS_ADD_CHILD(parent, 0, "etherswitch", -1);
+}
+
+static int
+etherswitch_probe(device_t dev)
+{
+	device_set_desc(dev, "Switch controller");
+
+	return (0);
+}
+	
+static int
+etherswitch_attach(device_t dev)
+{
+	struct etherswitch_softc *sc = (struct etherswitch_softc *)device_get_softc(dev);
+
+	sc->sc_dev = dev;
+	sx_init(&sc->sc_lock, "etherswitch");
+	sc->sc_devnode = make_dev(&etherswitch_cdevsw, device_get_unit(dev),
+			UID_ROOT, GID_WHEEL,
+			0600, "etherswitch%d", device_get_unit(dev));
+	if (sc->sc_devnode == NULL) {
+		device_printf(dev, "failed to create character device\n");
+		sx_destroy(&sc->sc_lock);
+		return (ENXIO);
+	}
+	sc->sc_devnode->si_drv1 = sc;
+
+	return (0);
+}
+
+static int
+etherswitch_detach(device_t dev)
+{
+	struct etherswitch_softc *sc = (struct etherswitch_softc *)device_get_softc(dev);
+
+	if (sc->sc_devnode)
+		destroy_dev(sc->sc_devnode);
+	sx_destroy(&sc->sc_lock);
+
+	return (0);
+}
+
+static int
+etherswitchopen(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+	struct etherswitch_softc *sc = dev->si_drv1;
+
+	SWITCH_LOCK(sc);
+	if (sc->sc_count > 0) {
+		SWITCH_UNLOCK(sc);
+		return (EBUSY);
+	}
+
+	sc->sc_count++;
+	SWITCH_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+etherswitchclose(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+	struct etherswitch_softc *sc = dev->si_drv1;
+
+	SWITCH_LOCK(sc);
+	if (sc->sc_count == 0) {
+		SWITCH_UNLOCK(sc);
+		return (EINVAL);
+	}
+
+	sc->sc_count--;
+
+	if (sc->sc_count < 0)
+		panic("%s: etherswitch_count < 0!", __func__);
+	SWITCH_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+etherswitchwrite(struct cdev *dev, struct uio * uio, int ioflag)
+{
+	return (EINVAL);
+}
+
+static int
+etherswitchread(struct cdev *dev, struct uio * uio, int ioflag)
+{
+	return (EINVAL);
+}
+
+static int
+etherswitchioctl(struct cdev *cdev, u_long cmd, caddr_t data, int flags, struct thread *td)
+{
+	struct etherswitch_softc *sc = cdev->si_drv1;
+	device_t dev = sc->sc_dev;
+	device_t etherswitch = device_get_parent(dev);
+	etherswitch_info_t *info;
+	etherswitch_reg_t *reg;
+	etherswitch_phyreg_t *phyreg;
+	int error = 0;
+
+	switch (cmd) {
+	case IOETHERSWITCHGETINFO:
+		info = ETHERSWITCH_GETINFO(etherswitch);
+		bcopy(info, data, sizeof(etherswitch_info_t));
+		break;
+		
+	case IOETHERSWITCHGETREG:
+		reg = (etherswitch_reg_t *)data;
+		reg->val = ETHERSWITCH_READREG(etherswitch, reg->reg);
+		break;
+	
+	case IOETHERSWITCHSETREG:
+		reg = (etherswitch_reg_t *)data;
+		error = ETHERSWITCH_WRITEREG(etherswitch, reg->reg, reg->val);
+		break;
+
+	case IOETHERSWITCHGETPORT:
+		error = ETHERSWITCH_GETPORT(etherswitch, (etherswitch_port_t *)data);
+		break;
+
+	case IOETHERSWITCHSETPORT:
+		error = ETHERSWITCH_SETPORT(etherswitch, (etherswitch_port_t *)data);
+		break;
+
+	case IOETHERSWITCHGETVLANGROUP:
+		error = ETHERSWITCH_GETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data);
+		break;
+
+	case IOETHERSWITCHSETVLANGROUP:
+		error = ETHERSWITCH_SETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data);
+		break;
+
+	case IOETHERSWITCHGETPHYREG:
+		phyreg = (etherswitch_phyreg_t *)data;
+		phyreg->val = ETHERSWITCH_READPHYREG(etherswitch, phyreg->phy, phyreg->reg);
+		break;
+	
+	case IOETHERSWITCHSETPHYREG:
+		phyreg = (etherswitch_phyreg_t *)data;
+		error = ETHERSWITCH_WRITEPHYREG(etherswitch, phyreg->phy, phyreg->reg, phyreg->val);
+		break;
+
+	default:
+		error = ENOTTY;
+	}
+
+	return (error);
+}
+
+MODULE_VERSION(etherswitch, 1);
diff --git a/sys/dev/etherswitch/etherswitch.h b/sys/dev/etherswitch/etherswitch.h
new file mode 100644
index 0000000..90ecb48
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch.h
@@ -0,0 +1,69 @@
+/*
+ * $FreeBSD$
+ */
+
+#ifndef __SYS_DEV_ETHERSWITCH_ETHERSWITCH_H
+#define __SYS_DEV_ETHERSWITCH_ETHERSWITCH_H
+
+#include <sys/ioccom.h>
+
+#ifdef _KERNEL
+extern devclass_t       etherswitch_devclass;
+extern driver_t         etherswitch_driver;
+#endif /* _KERNEL */
+
+struct etherswitch_reg {
+	uint16_t	reg;
+	uint16_t	val;
+};
+typedef struct etherswitch_reg etherswitch_reg_t;
+
+struct etherswitch_phyreg {
+	uint16_t	phy;
+	uint16_t	reg;
+	uint16_t	val;
+};
+typedef struct etherswitch_phyreg etherswitch_phyreg_t;
+
+#define ETHERSWITCH_NAMEMAX	64
+
+struct etherswitch_info {
+	int		es_nports;
+	int		es_nvlangroups;
+	char		es_name[ETHERSWITCH_NAMEMAX];
+};
+typedef struct etherswitch_info etherswitch_info_t;
+
+struct etherswitch_port {
+	int		es_port;
+	int		es_vlangroup;
+	union {
+		struct ifreq		es_uifr;
+		struct ifmediareq	es_uifmr;
+	} es_ifu;
+#define es_ifr		es_ifu.es_uifr
+#define es_ifmr		es_ifu.es_uifmr
+};
+typedef struct etherswitch_port etherswitch_port_t;
+
+struct etherswitch_vlangroup {
+	int		es_vlangroup;
+	int		es_vid;
+	int		es_member_ports;
+	int		es_untagged_ports;
+	int		es_fid;
+};
+typedef struct etherswitch_vlangroup etherswitch_vlangroup_t;
+#define ETHERSWITCH_PORTMASK(_port)	(1 << (_port))
+
+#define IOETHERSWITCHGETINFO		_IOR('i', 1, etherswitch_info_t)
+#define IOETHERSWITCHGETREG		_IOWR('i', 2, etherswitch_reg_t)
+#define IOETHERSWITCHSETREG		_IOW('i', 3, etherswitch_reg_t)
+#define IOETHERSWITCHGETPORT		_IOWR('i', 4, etherswitch_port_t)
+#define IOETHERSWITCHSETPORT		_IOW('i', 5, etherswitch_port_t)
+#define IOETHERSWITCHGETVLANGROUP	_IOWR('i', 6, etherswitch_vlangroup_t)
+#define IOETHERSWITCHSETVLANGROUP	_IOW('i', 7, etherswitch_vlangroup_t)
+#define IOETHERSWITCHGETPHYREG		_IOWR('i', 8, etherswitch_phyreg_t)
+#define IOETHERSWITCHSETPHYREG		_IOW('i', 9, etherswitch_phyreg_t)
+
+#endif
diff --git a/sys/dev/etherswitch/etherswitch_if.m b/sys/dev/etherswitch/etherswitch_if.m
new file mode 100644
index 0000000..7429c04
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch_if.m
@@ -0,0 +1,86 @@
+# $FreeBSD$
+
+#include <sys/bus.h>
+
+# Needed for ifreq/ifmediareq
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+INTERFACE etherswitch;
+
+#
+# Return device info
+#
+METHOD etherswitch_info_t* getinfo {
+	device_t	dev;
+}
+
+#
+# Read switch register
+#
+METHOD int readreg {
+	device_t	dev;
+	int		reg;
+};
+
+#
+# Write switch register
+#
+METHOD int writereg {
+	device_t	dev;
+	int		reg;
+	int		value;
+};
+
+#
+# Read PHY register
+#
+METHOD int readphyreg {
+	device_t	dev;
+	int		phy;
+	int		reg;
+};
+
+#
+# Write PHY register
+#
+METHOD int writephyreg {
+	device_t	dev;
+	int		phy;
+	int		reg;
+	int		value;
+};
+
+#
+# Get port configuration
+#
+METHOD int getport {
+	device_t	dev;
+	etherswitch_port_t *vg;
+}
+
+#
+# Set port configuration
+#
+METHOD int setport {
+	device_t	dev;
+	etherswitch_port_t *vg;
+}
+
+#
+# Get VLAN group configuration
+#
+METHOD int getvgroup {
+	device_t	dev;
+	etherswitch_vlangroup_t *vg;
+}
+
+#
+# Set VLAN group configuration
+#
+METHOD int setvgroup {
+	device_t	dev;
+	etherswitch_vlangroup_t *vg;
+}
diff --git a/sys/dev/etherswitch/mdio.c b/sys/dev/etherswitch/mdio.c
new file mode 100644
index 0000000..9302075
--- /dev/null
+++ b/sys/dev/etherswitch/mdio.c
@@ -0,0 +1,156 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/systm.h>
+
+#include <dev/etherswitch/mdio.h>
+
+#include "mdio_if.h"
+
+static void
+mdio_identify(driver_t *driver, device_t parent)
+{
+	if (device_find_child(parent, mdio_driver.name, -1) == NULL)
+		BUS_ADD_CHILD(parent, 0, mdio_driver.name, -1);
+}
+
+static int
+mdio_probe(device_t dev)
+{
+	device_set_desc(dev, "MDIO");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+mdio_attach(device_t dev)
+{
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	return (bus_generic_attach(dev));
+}
+
+static int
+mdio_detach(device_t dev)
+{
+	bus_generic_detach(dev);
+	return (0);
+}
+
+static int
+mdio_readreg(device_t dev, int phy, int reg)
+{
+	return MDIO_READREG(device_get_parent(dev), phy, reg);
+}
+
+static int
+mdio_writereg(device_t dev, int phy, int reg, int val)
+{
+	return MDIO_WRITEREG(device_get_parent(dev), phy, reg, val);
+}
+
+static int
+mdio_print_child(device_t dev, device_t child)
+{
+	int retval;
+
+	retval = bus_print_child_header(dev, child);
+	retval += bus_print_child_footer(dev, child);
+
+	return (retval);
+}
+
+static int
+mdio_read_ivar(device_t dev, device_t child __unused, int which,
+    uintptr_t *result)
+{
+	struct miibus_ivars *ivars;
+
+	ivars = device_get_ivars(dev);
+	switch (which) {
+	default:
+		return (ENOENT);
+	}
+	return (0);
+}
+
+static int
+mdio_child_pnpinfo_str(device_t dev __unused, device_t child, char *buf,
+    size_t buflen)
+{
+	buf[0] = '\0';
+	return (0);
+}
+
+static int
+mdio_child_location_str(device_t dev __unused, device_t child, char *buf,
+    size_t buflen)
+{
+	buf[0] = '\0';
+	return (0);
+}
+
+static void
+mdio_hinted_child(device_t dev, const char *name, int unit)
+{
+	device_add_child(dev, name, unit);
+}
+
+static device_method_t mdio_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_identify,	mdio_identify),
+	DEVMETHOD(device_probe,		mdio_probe),
+	DEVMETHOD(device_attach,	mdio_attach),
+	DEVMETHOD(device_detach,	mdio_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	/* bus interface */
+	DEVMETHOD(bus_print_child,	mdio_print_child),
+	DEVMETHOD(bus_read_ivar,	mdio_read_ivar),
+	DEVMETHOD(bus_child_pnpinfo_str, mdio_child_pnpinfo_str),
+	DEVMETHOD(bus_child_location_str, mdio_child_location_str),
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	DEVMETHOD(bus_hinted_child,	mdio_hinted_child),
+
+	/* MDIO access */
+	DEVMETHOD(mdio_readreg,		mdio_readreg),
+	DEVMETHOD(mdio_writereg,	mdio_writereg),
+
+	DEVMETHOD_END
+};
+
+driver_t mdio_driver = {
+	"mdio",
+	mdio_methods,
+	0
+};
+
+devclass_t mdio_devclass;
+
diff --git a/sys/dev/etherswitch/mdio.h b/sys/dev/etherswitch/mdio.h
new file mode 100644
index 0000000..52eddbd
--- /dev/null
+++ b/sys/dev/etherswitch/mdio.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DEV_MII_MDIO_H_
+#define	_DEV_MII_MDIO_H_
+
+extern driver_t mdio_driver;
+extern devclass_t mdio_devclass;
+
+#endif
diff --git a/sys/dev/etherswitch/mdio_if.m b/sys/dev/etherswitch/mdio_if.m
new file mode 100644
index 0000000..9aedd92
--- /dev/null
+++ b/sys/dev/etherswitch/mdio_if.m
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+#include <sys/bus.h>
+
+INTERFACE mdio;
+
+#
+# Read register from device on MDIO bus
+#
+METHOD int readreg {
+	device_t		dev;
+	int			phy;
+	int			reg;
+};
+
+#
+# Write register to device on MDIO bus
+#
+METHOD int writereg {
+	device_t		dev;
+	int			phy;
+	int			reg;
+	int			val;
+};
diff --git a/sys/dev/etherswitch/miiproxy.c b/sys/dev/etherswitch/miiproxy.c
new file mode 100644
index 0000000..2791082
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.c
@@ -0,0 +1,437 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <dev/etherswitch/miiproxy.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+
+
+MALLOC_DECLARE(M_MIIPROXY);
+MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
+
+driver_t miiproxy_driver;
+driver_t mdioproxy_driver;
+
+struct miiproxy_softc {
+	device_t	parent;
+	device_t	proxy;
+	device_t	mdio;
+	miiproxy_attach_callback_t	attach_callback;
+	void		*attach_arg;
+};
+
+struct mdioproxy_softc {
+};
+
+/*
+ * The rendevous data structures and functions allow two device endpoints to
+ * match up, so that the proxy endpoint can be associated with a target
+ * endpoint.  The proxy has to know the device name of the target that it
+ * wants to associate with, for example through a hint.  The rendevous code
+ * makes no assumptions about the devices that want to meet.
+ */
+struct rendevous_entry;
+
+enum rendevous_op {
+	RENDEVOUS_ATTACH,
+	RENDEVOUS_DETACH
+};
+
+typedef int (*rendevous_callback_t)(enum rendevous_op,
+    struct rendevous_entry *);
+
+static SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead =
+    SLIST_HEAD_INITIALIZER(rendevoushead);
+
+struct rendevous_endpoint {
+	device_t		device;
+	const char		*name;
+	rendevous_callback_t	callback;
+};
+
+struct rendevous_entry {
+	SLIST_ENTRY(rendevous_entry)	entries;
+	struct rendevous_endpoint	proxy;
+	struct rendevous_endpoint	target;
+};
+
+/*
+ * Call the callback routines for both the proxy and the target.  If either
+ * returns an error, undo the attachment.
+ */
+static int
+rendevous_attach(struct rendevous_entry *e, struct rendevous_endpoint *ep)
+{
+	int error;
+	
+	error = e->proxy.callback(RENDEVOUS_ATTACH, e);
+	if (error == 0)
+		error = e->target.callback(RENDEVOUS_ATTACH, e);
+	if (error != 0) {
+		e->proxy.callback(RENDEVOUS_DETACH, e);
+		ep->device = NULL;
+		ep->callback = NULL;
+	}
+	return (error);
+}
+
+/*
+ * Create an entry for the proxy in the rendevous list.  The name parameter
+ * indicates the name of the device that is the target endpoint for this
+ * rendevous.  The callback will be invoked as soon as the target is
+ * registered: either immediately if the target registered itself earlier,
+ * or once the target registers.
+ */
+static int
+rendevous_register_proxy(device_t dev, const char *name,
+    rendevous_callback_t callback)
+{
+	struct rendevous_entry *e;
+
+	KASSERT(callback != NULL, ("callback must not be NULL"));
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (strcmp(name, e->target.name) == 0) {
+			/* the target is already attached */
+		    	e->proxy.name = device_get_nameunit(dev);
+		    	e->proxy.device = dev;
+		    	e->proxy.callback = callback;
+			return (rendevous_attach(e, &e->proxy));
+		}
+	}
+	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+    	e->proxy.name = device_get_nameunit(dev);
+    	e->proxy.device = dev;
+    	e->proxy.callback = callback;
+	e->target.name = name;
+	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
+	return (0);
+}
+
+/*
+ * Create an entry in the rendevous list for the target.  The callback will
+ * be called once the proxy has registered.
+ */
+static int
+rendevous_register_target(device_t dev, rendevous_callback_t callback)
+{
+	struct rendevous_entry *e;
+	const char *name;
+	
+	KASSERT(callback != NULL, ("callback must not be NULL"));
+	name = device_get_nameunit(dev);
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (strcmp(name, e->target.name) == 0) {
+			e->target.device = dev;
+			e->target.callback = callback;
+			return (rendevous_attach(e, &e->target));
+		}
+	}
+	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+	e->target.name = name;
+    	e->target.device = dev;
+	e->target.callback = callback;
+	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
+	return (0);
+}
+
+/*
+ * Remove the registration for the proxy.
+ */
+static int
+rendevous_unregister_proxy(device_t dev)
+{
+	struct rendevous_entry *e;
+	int error = 0;
+	
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (e->proxy.device == dev) {
+			if (e->target.device == NULL) {
+				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
+				free(e, M_MIIPROXY);
+				return (0);
+			} else {
+				e->proxy.callback(RENDEVOUS_DETACH, e);
+				e->target.callback(RENDEVOUS_DETACH, e);
+			}
+			e->proxy.device = NULL;
+			e->proxy.callback = NULL;
+			return (error);
+		}
+	}
+	return (ENOENT);
+}
+
+/*
+ * Remove the registration for the target.
+ */
+static int
+rendevous_unregister_target(device_t dev)
+{
+	struct rendevous_entry *e;
+	int error = 0;
+	
+	SLIST_FOREACH(e, &rendevoushead, entries) {
+		if (e->target.device == dev) {
+			if (e->proxy.device == NULL) {
+				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
+				free(e, M_MIIPROXY);
+				return (0);
+			} else {
+				e->proxy.callback(RENDEVOUS_DETACH, e);
+				e->target.callback(RENDEVOUS_DETACH, e);
+			}
+			e->target.device = NULL;
+			e->target.callback = NULL;
+			return (error);
+		}
+	}
+	return (ENOENT);
+}
+
+/*
+ * Functions of the proxy that is interposed between the ethernet interface
+ * driver and the miibus device.
+ */
+
+static int
+miiproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
+{
+	struct miiproxy_softc *sc = device_get_softc(rendevous->proxy.device);
+
+	switch (op) {
+	case RENDEVOUS_ATTACH:
+		sc->mdio = device_get_parent(rendevous->target.device);
+		(sc->attach_callback)(sc->attach_arg);
+		break;
+	case RENDEVOUS_DETACH:
+		sc->mdio = NULL;
+		/* detach miibus */
+	}
+	return (0);
+}
+
+static int
+miiproxy_probe(device_t dev)
+{
+	device_set_desc(dev, "MII/MDIO proxy, MII side");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+miiproxy_attach(device_t dev)
+{
+	/*
+	 * The ethernet interface needs to call mii_attach_proxy() to pass
+	 * the relevant parameters for rendevous with the MDIO target.
+	 */
+	return (bus_generic_attach(dev));
+}
+
+static int
+miiproxy_detach(device_t dev)
+{
+	rendevous_unregister_proxy(dev);
+	bus_generic_detach(dev);
+	return (0);
+}
+
+static int
+miiproxy_readreg(device_t dev, int phy, int reg)
+{
+	struct miiproxy_softc *sc = device_get_softc(dev);
+
+	if (sc->mdio != NULL)
+		return (MDIO_READREG(sc->mdio, phy, reg));
+	return (-1);
+}
+
+static int
+miiproxy_writereg(device_t dev, int phy, int reg, int val)
+{
+	struct miiproxy_softc *sc = device_get_softc(dev);
+
+	if (sc->mdio != NULL)
+		return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
+	return (-1);
+}
+
+static void
+miiproxy_statchg(device_t dev)
+{
+	MIIBUS_STATCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_linkchg(device_t dev)
+{
+	MIIBUS_LINKCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_mediainit(device_t dev)
+{
+	MIIBUS_MEDIAINIT(device_get_parent(dev));
+}
+
+/*
+ * Functions for the MDIO target device driver.
+ */
+static int
+mdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
+{
+	return (0);
+}
+
+static void
+mdioproxy_identify(driver_t *driver, device_t parent)
+{
+	device_t child;
+
+	if (device_find_child(parent, driver->name, -1) == NULL) {
+		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
+	}
+}
+
+static int
+mdioproxy_probe(device_t dev)
+{
+	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
+
+	return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+mdioproxy_attach(device_t dev)
+{
+	rendevous_register_target(dev, mdioproxy_rendevous_callback);
+	return (bus_generic_attach(dev));
+}
+
+static int
+mdioproxy_detach(device_t dev)
+{
+	rendevous_unregister_target(dev);
+	bus_generic_detach(dev);
+	return (0);
+}
+
+/*
+ * Attach this proxy in place of miibus.  The callback is called once all
+ * parts are in place, so that it can attach the miibus to the proxy device,
+ * and finish interface initialization.
+ */
+device_t
+mii_attach_proxy(device_t dev, miiproxy_attach_callback_t cb, void *aa)
+{
+	struct miiproxy_softc *sc;
+	int		error;
+	const char	*name;
+	device_t	miiproxy;
+	
+	if (resource_string_value(device_get_name(dev),
+	    device_get_unit(dev), "mdio", &name) != 0) {
+	    	if (bootverbose)
+			printf("mii_attach_proxy: not attaching, no mdio"
+			    " device hint for %s\n", device_get_nameunit(dev));
+		return (NULL);
+	}
+
+	miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
+	error = bus_generic_attach(dev);
+	if (error != 0) {
+		device_printf(dev, "can't attach miiproxy\n");
+		return (NULL);
+	}
+	sc = device_get_softc(miiproxy);
+	sc->parent = dev;
+	sc->proxy = miiproxy;
+	sc->attach_callback = cb;
+	sc->attach_arg = aa;
+	rendevous_register_proxy(miiproxy, name, miiproxy_rendevous_callback);
+	return (miiproxy);
+}
+
+static device_method_t miiproxy_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_probe,		miiproxy_probe),
+	DEVMETHOD(device_attach,	miiproxy_attach),
+	DEVMETHOD(device_detach,	miiproxy_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	/* MII interface */
+	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
+	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
+	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
+	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
+	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
+
+	DEVMETHOD_END
+};
+
+static device_method_t mdioproxy_methods[] = {
+	/* device interface */
+	DEVMETHOD(device_identify,	mdioproxy_identify),
+	DEVMETHOD(device_probe,		mdioproxy_probe),
+	DEVMETHOD(device_attach,	mdioproxy_attach),
+	DEVMETHOD(device_detach,	mdioproxy_detach),
+	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
+    sizeof(struct miiproxy_softc));
+DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
+    sizeof(struct mdioproxy_softc));
+
+devclass_t miiproxy_devclass;
+static devclass_t mdioproxy_devclass;
+
+DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
+DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
+MODULE_VERSION(miiproxy, 1);
+MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
diff --git a/sys/dev/etherswitch/miiproxy.h b/sys/dev/etherswitch/miiproxy.h
new file mode 100644
index 0000000..5b8ee7c
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DEV_ETHERSWITCH_MIIPROXY_H_
+#define	_DEV_ETHERSWITCH_MIIPROXY_H_
+
+typedef void (*miiproxy_attach_callback_t)(void *);
+
+extern devclass_t miiproxy_devclass;
+extern driver_t miiproxy_driver;
+
+device_t mii_attach_proxy(device_t dev, miiproxy_attach_callback_t cb, void *aa);
+
+#endif
diff --git a/sys/dev/etherswitch/rtl8366rb.c b/sys/dev/etherswitch/rtl8366rb.c
new file mode 100644
index 0000000..afc6eef
--- /dev/null
+++ b/sys/dev/etherswitch/rtl8366rb.c
@@ -0,0 +1,755 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/rtl8366rbvar.h>
+
+#include "iicbus_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+struct rtl8366rb_softc {
+	struct mtx	sc_mtx;		/* serialize access to softc */
+	int		smi_acquired;	/* serialize access to SMI/I2C bus */
+	struct mtx	callout_mtx;	/* serialize callout */
+	device_t	dev;
+	char		*ifname[RTL8366RB_NUM_PHYS];
+	device_t	miibus[RTL8366RB_NUM_PHYS];
+	struct ifnet	*ifp[RTL8366RB_NUM_PHYS];
+	struct callout	callout_tick;
+};
+
+static etherswitch_info_t etherswitch_info = {
+	.es_nports =		6,
+	.es_nvlangroups =	16,
+	.es_name =			"Realtek RTL8366RB"
+};
+
+#define RTL_LOCK(_sc)	mtx_lock(&(_sc)->sc_mtx)
+#define RTL_UNLOCK(_sc)	mtx_unlock(&(_sc)->sc_mtx)
+#define RTL_LOCK_ASSERT(_sc, _what)	mtx_assert(&(_s)c->sc_mtx, (_what))
+#define RTL_TRYLOCK(_sc)	mtx_trylock(&(_sc)->sc_mtx)
+
+#define RTL_WAITOK	0
+#define	RTL_NOWAIT	1
+
+#define RTL_SMI_ACQUIRED	1
+#define RTL_SMI_ACQUIRED_ASSERT(_sc) \
+	KASSERT((_sc)->smi_acquired == RTL_SMI_ACQUIRED, ("smi must be acquired @%s", __FUNCTION__))
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#define DEVERR(dev, err, fmt, args...) do { \
+		if (err != 0) device_printf(dev, fmt, err, args); \
+	} while (0)
+#define DEBUG_INCRVAR(var)	do { \
+		var++; \
+	} while (0)
+
+static int callout_blocked = 0;
+static int iic_select_retries = 0;
+static int phy_access_retries = 0;
+static SYSCTL_NODE(_debug, OID_AUTO, rtl8366rb, CTLFLAG_RD, 0, "rtl8366rb");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, callout_blocked, CTLFLAG_RW, &callout_blocked, 0,
+	"number of times the callout couldn't acquire the bus");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, iic_select_retries, CTLFLAG_RW, &iic_select_retries, 0,
+	"number of times the I2C bus selection had to be retried");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, phy_access_retries, CTLFLAG_RW, &phy_access_retries, 0,
+	"number of times PHY register access had to be retried");
+#else
+#define DPRINTF(dev, args...)
+#define DEVERR(dev, err, fmt, args...)
+#define DEBUG_INCRVAR(var)
+#endif
+
+static int smi_probe(device_t dev);
+static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep);
+static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep);
+static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep);
+static void rtl8366rb_tick(void *arg);
+static int rtl8366rb_ifmedia_upd(struct ifnet *);
+static void rtl8366rb_ifmedia_sts(struct ifnet *, struct ifmediareq *);
+
+static void
+rtl8366rb_identify(driver_t *driver, device_t parent)
+{
+	device_t child;
+	struct iicbus_ivar *devi;
+
+	if (device_find_child(parent, "rtl8366rb", -1) == NULL) {
+		child = BUS_ADD_CHILD(parent, 0, "rtl8366rb", -1);
+		devi = IICBUS_IVAR(child);
+		devi->addr = RTL8366RB_IIC_ADDR;
+	}
+}
+
+static int
+rtl8366rb_probe(device_t dev)
+{
+	if (smi_probe(dev) != 0)
+		return (ENXIO);
+	device_set_desc(dev, "RTL8366RB Ethernet Switch Controller");
+	return (BUS_PROBE_DEFAULT);
+}
+
+static void
+rtl8366rb_init(device_t dev)
+{
+	/* Initialisation for TL-WR1043ND */
+	smi_rmw(dev, RTL8366RB_RCR,
+		RTL8366RB_RCR_HARD_RESET,
+		RTL8366RB_RCR_HARD_RESET, RTL_WAITOK);
+	DELAY(100000);
+	/* Enable 16 VLAN mode */
+	smi_rmw(dev, RTL8366RB_SGCR,
+		RTL8366RB_SGCR_EN_VLAN | RTL8366RB_SGCR_EN_VLAN_4KTB,
+		RTL8366RB_SGCR_EN_VLAN, RTL_WAITOK);
+	/* remove port 0 form VLAN 0 */
+	smi_rmw(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_MU_REG, 0),
+		(1 << 0), 0, RTL_WAITOK);
+	/* add port 0 untagged and port 5 tagged to VLAN 1 */
+	smi_rmw(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_MU_REG, 1),
+		((1 << 5 | 1 << 0) << RTL8366RB_VMCR_MU_MEMBER_SHIFT)
+			| ((1 << 5 | 1 << 0) << RTL8366RB_VMCR_MU_UNTAG_SHIFT),
+		((1 << 5 | 1 << 0) << RTL8366RB_VMCR_MU_MEMBER_SHIFT
+			| ((1 << 0) << RTL8366RB_VMCR_MU_UNTAG_SHIFT)),
+		RTL_WAITOK);
+	/* set PVLAN 1 for port 0 */
+	smi_rmw(dev, RTL8366RB_PVCR_REG(0),
+		RTL8366RB_PVCR_VAL(0, RTL8366RB_PVCR_PORT_MASK),
+		RTL8366RB_PVCR_VAL(0, 1), RTL_WAITOK);
+}
+
+static int
+rtl8366rb_attach(device_t dev)
+{
+	uint16_t rev = 0;
+	struct rtl8366rb_softc *sc;
+	char name[IFNAMSIZ];
+	int err = 0;
+	int i;
+
+	sc = device_get_softc(dev);
+	bzero(sc, sizeof(*sc));
+	sc->dev = dev;
+	mtx_init(&sc->sc_mtx, "rtl8366rb", NULL, MTX_DEF);
+	sc->smi_acquired = 0;
+	mtx_init(&sc->callout_mtx, "rtl8366rbcallout", NULL, MTX_DEF);
+
+	rtl8366rb_init(dev);
+	smi_read(dev, RTL8366RB_CVCR, &rev, RTL_WAITOK);
+	device_printf(dev, "rev. %d\n", rev & 0x000f);
+
+	/* attach miibus and phys */
+	/* PHYs need an interface, so we generate a dummy one */
+	for (i = 0; i < RTL8366RB_NUM_PHYS; i++) {
+		sc->ifp[i] = if_alloc(IFT_ETHER);
+		sc->ifp[i]->if_softc = sc;
+		sc->ifp[i]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING
+			| IFF_SIMPLEX;
+		snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(dev));
+		sc->ifname[i] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK);
+		bcopy(name, sc->ifname[i], strlen(name)+1);
+		if_initname(sc->ifp[i], sc->ifname[i], i);
+		err = mii_attach(dev, &sc->miibus[i], sc->ifp[i], rtl8366rb_ifmedia_upd, \
+			rtl8366rb_ifmedia_sts, BMSR_DEFCAPMASK, \
+			i, MII_OFFSET_ANY, 0);
+		if (err != 0) {
+			device_printf(dev, "attaching PHY %d failed\n", i);
+			return (err);
+		}
+	}
+
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	err = bus_generic_attach(dev);
+	if (err != 0)
+		return (err);
+	
+	callout_init_mtx(&sc->callout_tick, &sc->callout_mtx, 0);
+	rtl8366rb_tick(sc);
+	
+	return (err);
+}
+
+static int
+rtl8366rb_detach(device_t dev)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	int i;
+
+	for (i=0; i < RTL8366RB_NUM_PHYS; i++) {
+		if (sc->miibus[i])
+			device_delete_child(dev, sc->miibus[i]);
+		if (sc->ifp[i] != NULL)
+			if_free(sc->ifp[i]);
+		free(sc->ifname[i], M_DEVBUF);
+	}
+	bus_generic_detach(dev);
+	callout_drain(&sc->callout_tick);
+	mtx_destroy(&sc->callout_mtx);
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+static void
+rtl8366rb_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active)
+{
+	*media_active = IFM_ETHER;
+	*media_status = IFM_AVALID;
+	if ((portstatus & RTL8366RB_PLSR_LINK) != 0)
+		*media_status |= IFM_ACTIVE;
+	else {
+		*media_active |= IFM_NONE;
+		return;
+	}
+	switch (portstatus & RTL8366RB_PLSR_SPEED_MASK) {
+	case RTL8366RB_PLSR_SPEED_10:
+		*media_active |= IFM_10_T;
+		break;
+	case RTL8366RB_PLSR_SPEED_100:
+		*media_active |= IFM_100_TX;
+		break;
+	case RTL8366RB_PLSR_SPEED_1000:
+		*media_active |= IFM_1000_T;
+		break;
+	}
+	if ((portstatus & RTL8366RB_PLSR_FULLDUPLEX) == 0)
+		*media_active |= IFM_FDX;
+	else
+		*media_active |= IFM_HDX;
+	if ((portstatus & RTL8366RB_PLSR_TXPAUSE) != 0)
+		*media_active |= IFM_ETH_TXPAUSE;
+	if ((portstatus & RTL8366RB_PLSR_RXPAUSE) != 0)
+		*media_active |= IFM_ETH_RXPAUSE;
+}
+
+static void
+rtl833rb_miipollstat(struct rtl8366rb_softc *sc)
+{
+	int i;
+	struct mii_data *mii;
+	struct mii_softc *miisc;
+	uint16_t value;
+	int portstatus;
+
+	for (i = 0; i < RTL8366RB_NUM_PHYS; i++) {
+		mii = device_get_softc(sc->miibus[i]);
+		if ((i % 2) == 0) {
+			if (smi_read(sc->dev, RTL8366RB_PLSR_BASE + i/2, &value, RTL_NOWAIT) != 0) {
+				DEBUG_INCRVAR(callout_blocked);
+				return;
+			}
+			portstatus = value & 0xff;
+		} else {
+			portstatus = (value >> 8) & 0xff;
+		}
+		rtl8366rb_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active);
+		LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+			if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst)
+				continue;
+			mii_phy_update(miisc, MII_POLLSTAT);
+		}
+	}	
+}
+
+static void
+rtl8366rb_tick(void *arg)
+{
+	struct rtl8366rb_softc *sc = arg;
+
+	rtl833rb_miipollstat(sc);
+	callout_reset(&sc->callout_tick, hz, rtl8366rb_tick, sc);
+}
+
+static int
+smi_probe(device_t dev)
+{
+	device_t iicbus, iicha;
+	int err, i;
+	uint16_t chipid;
+	char bytes[2];
+	int xferd;
+
+	bytes[0] = RTL8366RB_CIR & 0xff;
+	bytes[1] = (RTL8366RB_CIR >> 8) & 0xff;
+	iicbus = device_get_parent(dev);
+	iicha = device_get_parent(iicbus);
+	iicbus_reset(iicbus, IIC_FASTEST, RTL8366RB_IIC_ADDR, NULL);
+	for (i=3; i--; ) {
+		IICBUS_STOP(iicha);
+		/*
+		 * we go directly to the host adapter because iicbus.c
+		 * only issues a stop on a bus that was successfully started.
+		 */
+	}
+	err = iicbus_request_bus(iicbus, dev, IIC_WAIT);
+	if (err != 0)
+		goto out;
+	err = iicbus_start(iicbus, RTL8366RB_IIC_ADDR | RTL_IICBUS_READ, RTL_IICBUS_TIMEOUT);
+	if (err != 0)
+		goto out;
+	err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT);
+	if (err != 0)
+		goto out;
+	err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0);
+	if (err != 0)
+		goto out;
+	chipid = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff);
+	DPRINTF(dev, "chip id 0x%04x\n", chipid);
+	if (chipid != RTL8366RB_CIR_ID8366RB)
+		err = ENXIO;
+out:
+	iicbus_stop(iicbus);
+	iicbus_release_bus(iicbus, dev);
+	return (err == 0 ? 0 : ENXIO);
+}
+
+static int
+smi_acquire(struct rtl8366rb_softc *sc, int sleep)
+{
+	int r = 0;
+	if (sleep == RTL_WAITOK)
+		RTL_LOCK(sc);
+	else
+		if (RTL_TRYLOCK(sc) == 0)
+			return (EWOULDBLOCK);
+	if (sc->smi_acquired == RTL_SMI_ACQUIRED)
+		r = EBUSY;
+	else {
+		r = iicbus_request_bus(device_get_parent(sc->dev), sc->dev, \
+			sleep == RTL_WAITOK ? IIC_WAIT : IIC_DONTWAIT);
+		if (r == 0)
+			sc->smi_acquired = RTL_SMI_ACQUIRED;
+	}
+	RTL_UNLOCK(sc);
+	return (r);
+}
+
+static int
+smi_release(struct rtl8366rb_softc *sc, int sleep)
+{
+	if (sleep == RTL_WAITOK)
+		RTL_LOCK(sc);
+	else
+		if (RTL_TRYLOCK(sc) == 0)
+			return (EWOULDBLOCK);
+	RTL_SMI_ACQUIRED_ASSERT(sc);
+	iicbus_release_bus(device_get_parent(sc->dev), sc->dev);
+	sc->smi_acquired = 0;
+	RTL_UNLOCK(sc);
+	return (0);
+}
+
+static int
+smi_select(device_t dev, int op, int sleep)
+{
+	int err, i;
+	device_t iicbus = device_get_parent(dev);
+	struct iicbus_ivar *devi = IICBUS_IVAR(dev);
+	int slave = devi->addr;
+
+	RTL_SMI_ACQUIRED_ASSERT((struct rtl8366rb_softc *)device_get_softc(dev));
+	/*
+	 * The chip does not use clock stretching when it is busy,
+	 * instead ignoring the command. Retry a few times.
+	 */
+	for (i = RTL_IICBUS_RETRIES; i--; ) {
+		err = iicbus_start(iicbus, slave | op, RTL_IICBUS_TIMEOUT);
+		if (err != IIC_ENOACK)
+			break;
+		if (sleep == RTL_WAITOK) {
+			DEBUG_INCRVAR(iic_select_retries);
+			pause("smi_select", RTL_IICBUS_RETRY_SLEEP);
+		} else
+			break;
+	}
+	return (err);
+}
+
+static int
+smi_read_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t *data, int sleep)
+{
+	int err;
+	device_t iicbus = device_get_parent(sc->dev);
+	char bytes[2];
+	int xferd;
+
+	RTL_SMI_ACQUIRED_ASSERT(sc);
+	bytes[0] = addr & 0xff;
+	bytes[1] = (addr >> 8) & 0xff;
+	err = smi_select(sc->dev, RTL_IICBUS_READ, sleep);
+	if (err != 0)
+		goto out;
+	err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT);
+	if (err != 0)
+		goto out;
+	err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0);
+	if (err != 0)
+		goto out;
+	*data = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff);
+
+out:
+	iicbus_stop(iicbus);
+	return (err);
+}
+
+static int
+smi_write_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t data, int sleep)
+{
+	int err;
+	device_t iicbus = device_get_parent(sc->dev);
+	char bytes[4];
+	int xferd;
+
+	RTL_SMI_ACQUIRED_ASSERT(sc);
+	bytes[0] = addr & 0xff;
+	bytes[1] = (addr >> 8) & 0xff;
+	bytes[2] = data & 0xff;
+	bytes[3] = (data >> 8) & 0xff;
+
+	err = smi_select(sc->dev, RTL_IICBUS_WRITE, sleep);
+	if (err == 0)
+		err = iicbus_write(iicbus, bytes, 4, &xferd, RTL_IICBUS_TIMEOUT);
+	iicbus_stop(iicbus);
+
+	return (err);
+}
+
+static int
+smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	int err;
+
+	err = smi_acquire(sc, sleep);
+	if (err != 0)
+		return (EBUSY);
+	err = smi_read_locked(sc, addr, data, sleep);
+	smi_release(sc, sleep);
+	DEVERR(dev, err, "smi_read()=%d: addr=%04x\n", addr);
+	return (err == 0 ? 0 : EIO);
+}
+
+static int
+smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	int err;
+	
+	err = smi_acquire(sc, sleep);
+	if (err != 0)
+		return (EBUSY);
+	err = smi_write_locked(sc, addr, data, sleep);
+	smi_release(sc, sleep);
+	DEVERR(dev, err, "smi_write()=%d: addr=%04x\n", addr);
+	return (err == 0 ? 0 : EIO);
+}
+
+static int
+smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	int err;
+	uint16_t oldv, newv;
+	
+	err = smi_acquire(sc, sleep);
+	if (err != 0)
+		return (EBUSY);
+	if (err == 0) {
+		err = smi_read_locked(sc, addr, &oldv, sleep);
+		if (err == 0) {
+			newv = oldv & ~mask;
+			newv |= data & mask;
+			if (newv != oldv)
+				err = smi_write_locked(sc, addr, newv, sleep);
+		}
+	}
+	smi_release(sc, sleep);
+	DEVERR(dev, err, "smi_rmw()=%d: addr=%04x\n", addr);
+	return (err == 0 ? 0 : EIO);
+}
+
+static etherswitch_info_t *
+rtl_getinfo(device_t dev)
+{
+	return (&etherswitch_info);
+}
+
+static int
+rtl_readreg(device_t dev, int reg)
+{
+	uint16_t data = 0;
+
+	smi_read(dev, reg, &data, RTL_WAITOK);
+	return (data);
+}
+
+static int
+rtl_writereg(device_t dev, int reg, int value)
+{
+	return (smi_write(dev, reg, value, RTL_WAITOK));
+}
+
+static int
+rtl_getport(device_t dev, etherswitch_port_t *p)
+{
+	struct rtl8366rb_softc *sc;
+	struct ifmedia *ifm;
+	struct mii_data *mii;
+	struct ifmediareq *ifmr = &p->es_ifmr;
+	uint16_t v;
+	int err;
+	
+	if (p->es_port < 0 || p->es_port >= RTL8366RB_NUM_PORTS)
+		return (ENXIO);
+	p->es_vlangroup = RTL8366RB_PVCR_GET(p->es_port,
+		rtl_readreg(dev, RTL8366RB_PVCR_REG(p->es_port)));
+	
+	if (p->es_port < RTL8366RB_NUM_PHYS) {
+		sc = device_get_softc(dev);
+		mii = device_get_softc(sc->miibus[p->es_port]);
+		ifm = &mii->mii_media;
+		err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCGIFMEDIA);
+		if (err)
+			return (err);
+	} else {
+		/* fill in fixed values for CPU port */
+		ifmr->ifm_count = 0;
+		smi_read(dev, RTL8366RB_PLSR_BASE + (RTL8366RB_NUM_PHYS)/2, &v, RTL_WAITOK);
+		v = v >> (8 * ((RTL8366RB_NUM_PHYS) % 2));
+		rtl8366rb_update_ifmedia(v, &ifmr->ifm_status, &ifmr->ifm_active);
+		ifmr->ifm_current = ifmr->ifm_active;
+		ifmr->ifm_mask = 0;
+		ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+	}
+	return (0);
+}
+
+static int
+rtl_setport(device_t dev, etherswitch_port_t *p)
+{
+	int err;
+	struct rtl8366rb_softc *sc;
+	struct ifmedia *ifm;
+	struct mii_data *mii;
+
+	if (p->es_port < 0 || p->es_port >= RTL8366RB_NUM_PHYS)
+		return (ENXIO);
+	err = smi_rmw(dev, RTL8366RB_PVCR_REG(p->es_port),
+		RTL8366RB_PVCR_VAL(p->es_port, RTL8366RB_PVCR_PORT_MASK),
+		RTL8366RB_PVCR_VAL(p->es_port, p->es_vlangroup), RTL_WAITOK);
+	if (err)
+		return (err);
+	sc = device_get_softc(dev);
+	mii = device_get_softc(sc->miibus[p->es_port]);
+	ifm = &mii->mii_media;
+	err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCSIFMEDIA);
+	return (err);
+}
+
+static int
+rtl_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+	uint16_t vmcr[3];
+	int i;
+	
+	for (i=0; i<3; i++)
+		vmcr[i] = rtl_readreg(dev, RTL8366RB_VMCR(i, vg->es_vlangroup));
+		
+	vg->es_vid = RTL8366RB_VMCR_VID(vmcr);
+	vg->es_member_ports = RTL8366RB_VMCR_MEMBER(vmcr);
+	vg->es_untagged_ports = RTL8366RB_VMCR_UNTAG(vmcr);
+	vg->es_fid = RTL8366RB_VMCR_FID(vmcr);
+	return (0);
+}
+
+static int
+rtl_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+	int g = vg->es_vlangroup;
+
+	rtl_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_DOT1Q_REG, g),
+		(vg->es_vid << RTL8366RB_VMCR_DOT1Q_VID_SHIFT) & RTL8366RB_VMCR_DOT1Q_VID_MASK);
+	rtl_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_MU_REG, g),
+		((vg->es_member_ports << RTL8366RB_VMCR_MU_MEMBER_SHIFT) & RTL8366RB_VMCR_MU_MEMBER_MASK) |
+		((vg->es_untagged_ports << RTL8366RB_VMCR_MU_UNTAG_SHIFT) & RTL8366RB_VMCR_MU_UNTAG_MASK));
+	rtl_writereg(dev, RTL8366RB_VMCR(RTL8366RB_VMCR_FID_REG, g),
+		vg->es_fid);
+	return (0);
+}
+
+static int
+rtl_readphy(device_t dev, int phy, int reg)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	uint16_t data = 0;
+	int err, i, sleep;
+
+	if (phy < 0 || phy >= RTL8366RB_NUM_PHYS)
+		return (ENXIO);
+	if (reg < 0 || reg >= RTL8366RB_NUM_PHY_REG)
+		return (ENXIO);
+	sleep = RTL_WAITOK;
+	err = smi_acquire(sc, sleep);
+	if (err != 0)
+		return (EBUSY);
+	for (i = RTL_IICBUS_RETRIES; i--; ) {
+		err = smi_write_locked(sc, RTL8366RB_PACR, RTL8366RB_PACR_READ, sleep);
+		if (err == 0)
+			err = smi_write_locked(sc, RTL8366RB_PHYREG(phy, 0, reg), 0, sleep);
+		if (err == 0) {
+			err = smi_read_locked(sc, RTL8366RB_PADR, &data, sleep);
+			break;
+		}
+		DEBUG_INCRVAR(phy_access_retries);
+		DPRINTF(dev, "rtl_readphy(): chip not responsive, retrying %d more times\n", i);
+		pause("rtl_readphy", RTL_IICBUS_RETRY_SLEEP);
+	}
+	smi_release(sc, sleep);
+	DEVERR(dev, err, "rtl_readphy()=%d: phy=%d.%02x\n", phy, reg);
+	return (data);
+}
+
+static int
+rtl_writephy(device_t dev, int phy, int reg, int data)
+{
+	struct rtl8366rb_softc *sc = device_get_softc(dev);
+	int err, i, sleep;
+	
+	if (phy < 0 || phy >= RTL8366RB_NUM_PHYS)
+		return (ENXIO);
+	if (reg < 0 || reg >= RTL8366RB_NUM_PHY_REG)
+		return (ENXIO);
+	sleep = RTL_WAITOK;
+	err = smi_acquire(sc, sleep);
+	if (err != 0)
+		return (EBUSY);
+	for (i = RTL_IICBUS_RETRIES; i--; ) {
+		err = smi_write_locked(sc, RTL8366RB_PACR, RTL8366RB_PACR_WRITE, sleep);
+		if (err == 0)
+			err = smi_write_locked(sc, RTL8366RB_PHYREG(phy, 0, reg), data, sleep);
+		if (err == 0) {
+			break;
+		}
+		DEBUG_INCRVAR(phy_access_retries);
+		DPRINTF(dev, "rtl_writephy(): chip not responsive, retrying %d more tiems\n", i);
+		pause("rtl_writephy", RTL_IICBUS_RETRY_SLEEP);
+	}
+	smi_release(sc, sleep);
+	DEVERR(dev, err, "rtl_writephy()=%d: phy=%d.%02x\n", phy, reg);
+	return (err == 0 ? 0 : EIO);
+}
+
+static int
+rtl8366rb_ifmedia_upd(struct ifnet *ifp)
+{
+	struct rtl8366rb_softc *sc = ifp->if_softc;
+	struct mii_data *mii = device_get_softc(sc->miibus[ifp->if_dunit]);
+	
+	mii_mediachg(mii);
+	return (0);
+}
+
+static void
+rtl8366rb_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+	struct rtl8366rb_softc *sc = ifp->if_softc;
+	struct mii_data *mii = device_get_softc(sc->miibus[ifp->if_dunit]);
+
+	mii_pollstat(mii);
+	ifmr->ifm_active = mii->mii_media_active;
+	ifmr->ifm_status = mii->mii_media_status;
+}
+
+
+static device_method_t rtl8366rb_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_identify,	rtl8366rb_identify),
+	DEVMETHOD(device_probe,		rtl8366rb_probe),
+	DEVMETHOD(device_attach,	rtl8366rb_attach),
+	DEVMETHOD(device_detach,	rtl8366rb_detach),
+	
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	
+	/* MII interface */
+	DEVMETHOD(miibus_readreg,	rtl_readphy),
+	DEVMETHOD(miibus_writereg,	rtl_writephy),
+
+	/* etherswitch interface */
+	DEVMETHOD(etherswitch_getinfo,	rtl_getinfo),
+	DEVMETHOD(etherswitch_readreg,	rtl_readreg),
+	DEVMETHOD(etherswitch_writereg,	rtl_writereg),
+	DEVMETHOD(etherswitch_readphyreg,	rtl_readphy),
+	DEVMETHOD(etherswitch_writephyreg,	rtl_writephy),
+	DEVMETHOD(etherswitch_getport,	rtl_getport),
+	DEVMETHOD(etherswitch_setport,	rtl_setport),
+	DEVMETHOD(etherswitch_getvgroup,	rtl_getvgroup),
+	DEVMETHOD(etherswitch_setvgroup,	rtl_setvgroup),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(rtl8366rb, rtl8366rb_driver, rtl8366rb_methods,
+    sizeof(struct rtl8366rb_softc));
+static devclass_t rtl8366rb_devclass;
+
+DRIVER_MODULE(rtl8366rb, iicbus, rtl8366rb_driver, rtl8366rb_devclass, 0, 0);
+DRIVER_MODULE(miibus, rtl8366rb, miibus_driver, miibus_devclass, 0, 0);
+DRIVER_MODULE(etherswitch, rtl8366rb, etherswitch_driver, etherswitch_devclass, 0, 0);
+MODULE_VERSION(rtl8366rb, 1);
+MODULE_DEPEND(rtl8366rb, iicbus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(rtl8366rb, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(rtl8366rb, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/rtl8366rbvar.h b/sys/dev/etherswitch/rtl8366rbvar.h
new file mode 100644
index 0000000..4f3d1ba
--- /dev/null
+++ b/sys/dev/etherswitch/rtl8366rbvar.h
@@ -0,0 +1,176 @@
+/*-
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DEV_ETHERSWITCH_RTL8366RBVAR_H_
+#define	_DEV_ETHERSWITCH_RTL8366RBVAR_H_
+
+#define RTL8366RB_IIC_ADDR	0xa8
+#define RTL_IICBUS_TIMEOUT	100	/* us */
+#define RTL_IICBUS_READ		1
+#define	RTL_IICBUS_WRITE	0
+/* number of times to try and select the chip on the I2C bus */
+#define RTL_IICBUS_RETRIES	3
+#define RTL_IICBUS_RETRY_SLEEP	(hz/1000)
+
+/* Register definitions */
+
+/* Switch Global Configuration */
+#define RTL8366RB_SGCR				0x0000
+#define RTL8366RB_SGCR_EN_BC_STORM_CTRL		0x0001
+#define RTL8366RB_SGCR_MAX_LENGTH_MASK		0x0030
+#define RTL8366RB_SGCR_MAX_LENGTH_1522		0x0000
+#define RTL8366RB_SGCR_MAX_LENGTH_1536		0x0010
+#define RTL8366RB_SGCR_MAX_LENGTH_1552		0x0020
+#define RTL8366RB_SGCR_MAX_LENGTH_9216		0x0030
+#define RTL8366RB_SGCR_EN_VLAN			0x2000
+#define RTL8366RB_SGCR_EN_VLAN_4KTB		0x4000
+#define RTL8366RB_SGCR_EN_QOS			0x8000
+
+/* Port Enable Control: DISABLE_PORT[5:0] */
+#define RTL8366RB_PECR				0x0001
+
+/* Switch Security Control 0: DIS_LEARN[5:0] */
+#define RTL8366RB_SSCR0				0x0002
+
+/* Switch Security Control 1: DIS_AGE[5:0] */
+#define RTL8366RB_SSCR1				0x0003
+
+/* Switch Security Control 2 */
+#define RTL8366RB_SSCR2				0x0004
+#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA		0x0001
+
+/* Port Link Status: two ports per register */
+#define RTL8366RB_PLSR_BASE			0x0014
+#define RTL8366RB_PLSR_SPEED_MASK	0x03
+#define RTL8366RB_PLSR_SPEED_10		0x00
+#define RTL8366RB_PLSR_SPEED_100	0x01
+#define RTL8366RB_PLSR_SPEED_1000	0x02
+#define RTL8366RB_PLSR_FULLDUPLEX	0x08
+#define RTL8366RB_PLSR_LINK		0x10
+#define RTL8366RB_PLSR_TXPAUSE		0x20
+#define RTL8366RB_PLSR_RXPAUSE		0x40
+#define RTL8366RB_PLSR_NO_AUTO		0x80
+
+/* VLAN Member Configuration, 3 registers per VLAN */
+#define RTL8366RB_VMCR_BASE			0x0020
+#define RTL8366RB_VMCR_MULT		3
+#define RTL8366RB_VMCR_DOT1Q_REG	0
+#define RTL8366RB_VMCR_DOT1Q_VID_SHIFT	0
+#define RTL8366RB_VMCR_DOT1Q_VID_MASK	0x0fff
+#define RTL8366RB_VMCR_DOT1Q_PCP_SHIFT	12
+#define RTL8366RB_VMCR_DOT1Q_PCP_MASK	0x7000
+#define RTL8366RB_VMCR_MU_REG		1
+#define RTL8366RB_VMCR_MU_MEMBER_SHIFT	0
+#define RTL8366RB_VMCR_MU_MEMBER_MASK	0x00ff
+#define RTL8366RB_VMCR_MU_UNTAG_SHIFT	8
+#define RTL8366RB_VMCR_MU_UNTAG_MASK	0xff00
+#define RTL8366RB_VMCR_FID_REG		2
+#define RTL8366RB_VMCR_FID_FID_SHIFT	0
+#define RTL8366RB_VMCR_FID_FID_MASK	0x0007
+#define RTL8366RB_VMCR(_reg, _vlan) \
+	(RTL8366RB_VMCR_BASE + _reg + _vlan * RTL8366RB_VMCR_MULT)
+/* VLAN Identifier */
+#define RTL8366RB_VMCR_VID(_r) \
+	(_r[RTL8366RB_VMCR_DOT1Q_REG] & RTL8366RB_VMCR_DOT1Q_VID_MASK)
+/* Priority Code Point */
+#define RTL8366RB_VMCR_PCP(_r) \
+	((_r[RTL8366RB_VMCR_DOT1Q_REG] & RTL8366RB_VMCR_DOT1Q_PCP_MASK) \
+	>> RTL8366RB_VMCR_DOT1Q_PCP_SHIFT)
+/* Member ports */
+#define RTL8366RB_VMCR_MEMBER(_r) \
+	(_r[RTL8366RB_VMCR_MU_REG] & RTL8366RB_VMCR_MU_MEMBER_MASK)
+/* Untagged ports */
+#define RTL8366RB_VMCR_UNTAG(_r) \
+	((_r[RTL8366RB_VMCR_MU_REG] & RTL8366RB_VMCR_MU_UNTAG_MASK) \
+	>> RTL8366RB_VMCR_MU_UNTAG_SHIFT)
+/* Forwarding ID */
+#define RTL8366RB_VMCR_FID(_r) \
+	(_r[RTL8366RB_VMCR_FID_REG] & RTL8366RB_VMCR_FID_FID_MASK)
+
+/*
+ * Port VLAN Control, 4 ports per register
+ * Determines the VID for untagged ingress frames through
+ * index into VMC.
+ */
+#define RTL8366RB_PVCR_BASE			0x0063
+#define RTL8366RB_PVCR_PORT_SHIFT	4
+#define RTL8366RB_PVCR_PORT_PERREG	(16 / RTL8366RB_PVCR_PORT_SHIFT)
+#define RTL8366RB_PVCR_PORT_MASK	0x000f
+#define RTL8366RB_PVCR_REG(_port) \
+	(RTL8366RB_PVCR_BASE + _port / (RTL8366RB_PVCR_PORT_PERREG))
+#define RTL8366RB_PVCR_VAL(_port, _pvlan) \
+	((_pvlan & RTL8366RB_PVCR_PORT_MASK) << \
+	((_port % RTL8366RB_PVCR_PORT_PERREG) * RTL8366RB_PVCR_PORT_SHIFT))
+#define RTL8366RB_PVCR_GET(_port, _val) \
+	(((_val) >> ((_port % RTL8366RB_PVCR_PORT_PERREG) * RTL8366RB_PVCR_PORT_SHIFT)) & RTL8366RB_PVCR_PORT_MASK)
+
+/* Reset Control */
+#define RTL8366RB_RCR				0x0100
+#define RTL8366RB_RCR_HARD_RESET	0x0001
+#define RTL8366RB_RCR_SOFT_RESET	0x0002
+
+/* Chip Version Control: CHIP_VER[3:0] */
+#define RTL8366RB_CVCR				0x050A
+/* Chip Identifier */
+#define RTL8366RB_CIR				0x0509
+#define RTL8366RB_CIR_ID8366RB		0x5937
+
+/* VLAN Ingress Control 2: [5:0] */
+#define RTL8366RB_VIC2R				0x037f
+
+/* MIB registers */
+#define RTL8366RB_MCNT_BASE			0x1000
+#define RTL8366RB_MCTLR				0x13f0
+#define RTL8366RB_MCTLR_BUSY		0x0001
+#define RTL8366RB_MCTLR_RESET		0x0002
+#define RTL8366RB_MCTLR_RESET_PORT_MASK	0x00fc
+#define RTL8366RB_MCTLR_RESET_ALL	0x0800
+
+#define RTL8366RB_MCNT(_port, _r) \
+	(RTL8366RB_MCNT_BASE + 0x50 * (_port) + (_r))
+#define RTL8366RB_MCTLR_RESET_PORT(_p) \
+	(1 << ((_p) + 2))
+
+/* PHY Access Control */
+#define RTL8366RB_PACR				0x8000
+#define RTL8366RB_PACR_WRITE		0x0000
+#define RTL8366RB_PACR_READ			0x0001
+
+/* PHY Access Data */
+#define	RTL8366RB_PADR				0x8002
+
+#define RTL8366RB_PHYREG(phy, page, reg) \
+	(RTL8366RB_PACR | (1 << (((phy) & 0x1f) + 9)) | (((page) & 0xf) << 5) | ((reg) & 0x1f))
+
+/* general characteristics of the chip */
+#define RTL8366RB_NUM_PORTS			6
+#define RTL8366RB_NUM_PHYS			(RTL8366RB_NUM_PORTS-1)
+#define RTL8366RB_NUM_VLANS			16
+#define RTL8366RB_NUM_PHY_REG			32
+
+#endif
diff --git a/sys/dev/flash/mx25l.c b/sys/dev/flash/mx25l.c
index d5c2270..caaf963 100644
--- a/sys/dev/flash/mx25l.c
+++ b/sys/dev/flash/mx25l.c
@@ -101,7 +101,9 @@ struct mx25l_flash_ident flash_devices[] = {
 	{ "mx25ll64",  0xc2, 0x2017, 64 * 1024, 128, FL_NONE },
 	{ "mx25ll128", 0xc2, 0x2018, 64 * 1024, 256, FL_ERASE_4K | FL_ERASE_32K },
 	{ "s25fl128",  0x01, 0x2018, 64 * 1024, 256, FL_NONE },
+	{ "s25sl032",  0x01, 0x0215, 64 * 1024,  64, FL_NONE },
 	{ "s25sl064a", 0x01, 0x0216, 64 * 1024, 128, FL_NONE },
+	{ "w25q64bv",  0xef, 0x4017, 64 * 1024, 128, FL_ERASE_4K },
 };
 
 static uint8_t
diff --git a/sys/mips/atheros/apb.c b/sys/mips/atheros/apb.c
index a2f6163..a682034 100644
--- a/sys/mips/atheros/apb.c
+++ b/sys/mips/atheros/apb.c
@@ -36,6 +36,8 @@ __FBSDID("$FreeBSD$");
 #include <sys/module.h>
 #include <sys/rman.h>
 #include <sys/malloc.h>
+#include <sys/pcpu.h>
+#include <sys/proc.h>
 
 #include <machine/bus.h>
 #include <machine/intr_machdep.h>
@@ -360,8 +362,7 @@ apb_intr(void *arg)
 				continue;
 			}
 
-			/* TODO: frame instead of NULL? */
-			intr_event_handle(event, NULL);
+			intr_event_handle(event, curthread->td_intr_frame);
 			mips_intrcnt_inc(sc->sc_intr_counter[irq]);
 		}
 	}
diff --git a/sys/mips/atheros/ar71xx_gpio.c b/sys/mips/atheros/ar71xx_gpio.c
index 592cd2b..9639d1f 100644
--- a/sys/mips/atheros/ar71xx_gpio.c
+++ b/sys/mips/atheros/ar71xx_gpio.c
@@ -329,7 +329,7 @@ ar71xx_gpio_attach(device_t dev)
 	struct ar71xx_gpio_softc *sc = device_get_softc(dev);
 	int error = 0;
 	int i, j, maxpin;
-	int mask;
+	int mask, pinon;
 	int old = 0;
 
 	KASSERT((device_get_unit(dev) == 0),
@@ -394,6 +394,9 @@ ar71xx_gpio_attach(device_t dev)
 	if (resource_int_value(device_get_name(dev), device_get_unit(dev),
 	    "pinmask", &mask) != 0)
 		mask = 0;
+	if (resource_int_value(device_get_name(dev), device_get_unit(dev),
+	    "pinon", &pinon) != 0)
+		pinon = 0;
 	device_printf(dev, "gpio pinmask=0x%x\n", mask);
 	for (i = 0, j = 0; j < maxpin; j++) {
 		if ((mask & (1 << j)) == 0)
@@ -407,6 +410,11 @@ ar71xx_gpio_attach(device_t dev)
 		i++;
 	}
 	sc->gpio_npins = i;
+	for (i = 0; i < sc->gpio_npins; i++) {
+		j = sc->gpio_pins[i].gp_pin;
+		if ((pinon & (1 << j)) != 0)
+			ar71xx_gpio_pin_set(dev, j, 1);
+	}
 	device_add_child(dev, "gpioc", device_get_unit(dev));
 	device_add_child(dev, "gpiobus", device_get_unit(dev));
 	return (bus_generic_attach(dev));
diff --git a/sys/mips/atheros/ar71xx_pci.c b/sys/mips/atheros/ar71xx_pci.c
index a6c7c1b..38d5adb 100644
--- a/sys/mips/atheros/ar71xx_pci.c
+++ b/sys/mips/atheros/ar71xx_pci.c
@@ -37,6 +37,8 @@ __FBSDID("$FreeBSD$");
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/rman.h>
+#include <sys/pcpu.h>
+#include <sys/proc.h>
 
 #include <vm/vm.h>
 #include <vm/pmap.h>
@@ -503,8 +505,7 @@ ar71xx_pci_intr(void *arg)
 			/* Flush DDR FIFO for IP2 */
 			ar71xx_device_ddr_flush_ip2();
 
-			/* TODO: frame instead of NULL? */
-			intr_event_handle(event, NULL);
+			intr_event_handle(event, curthread->td_intr_frame);
 			mips_intrcnt_inc(sc->sc_intr_counter[irq]);
 		}
 	}
diff --git a/sys/mips/atheros/ar71xx_pmc.c b/sys/mips/atheros/ar71xx_pmc.c
new file mode 100644
index 0000000..fc6bb08
--- /dev/null
+++ b/sys/mips/atheros/ar71xx_pmc.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2011, Adrian Chadd <adrian@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Tie into the PMC interrupt line and call the HWPMC hooks as needed.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/watchdog.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/pcpu.h>
+#include <sys/pmckern.h>
+
+#include <mips/atheros/ar71xxreg.h>
+#include <mips/atheros/ar71xx_cpudef.h>
+
+struct ar71xx_pmc_softc {
+	device_t dev;
+	struct resource * pmc_irq;
+	void * pmc_intrhand;
+};
+
+static int
+ar71xx_pmc_probe(device_t dev)
+{
+
+	device_set_desc(dev, "Atheros AR71XX PMC interrupt device");
+	return (0);
+}
+
+static int
+ar71xx_pmc_intr_filter(void *arg)
+{
+	struct trapframe *tf = curthread->td_intr_frame;
+
+	printf("%s: called; tf=%p\n", __func__, tf);
+	if (pmc_intr && (*pmc_intr)(PCPU_GET(cpuid), tf) == 1) {
+		return (FILTER_HANDLED);
+	}
+	printf("%s: stray\n", __func__);
+	return (FILTER_STRAY);
+}
+
+static int
+ar71xx_pmc_attach(device_t dev)
+{
+	struct ar71xx_pmc_softc *sc = device_get_softc(dev);
+	int rid, error;
+
+	sc->dev = dev;
+	rid = 0;
+	sc->pmc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+	    RF_SHAREABLE | RF_ACTIVE);
+	if (sc->pmc_irq == NULL) {
+		device_printf(dev, "%s: couldn't allocate IRQ\n", __func__);
+		return ENXIO;
+	}
+
+	/* XXX INTR_TYPE_NET? */
+	error = bus_setup_intr(dev, sc->pmc_irq, INTR_TYPE_NET | INTR_MPSAFE,
+	    ar71xx_pmc_intr_filter, NULL, sc, &sc->pmc_intrhand);
+
+	return (0);
+}
+
+static int
+ar71xx_pmc_detach(device_t dev)
+{
+	struct ar71xx_pmc_softc *sc = device_get_softc(dev);
+
+	if (sc->pmc_intrhand)
+		bus_teardown_intr(dev, sc->pmc_irq, sc->pmc_intrhand);
+
+	return (0);
+}
+
+static device_method_t ar71xx_pmc_methods[] = {
+	DEVMETHOD(device_probe, ar71xx_pmc_probe),
+	DEVMETHOD(device_attach, ar71xx_pmc_attach),
+	DEVMETHOD(device_detach, ar71xx_pmc_detach),
+	{0, 0},
+};
+
+static driver_t ar71xx_pmc_driver = {
+	"ar71xx_pmc",
+	ar71xx_pmc_methods,
+	sizeof(struct ar71xx_pmc_softc),
+};
+static devclass_t ar71xx_pmc_devclass;
+
+DRIVER_MODULE(ar71xx_pmc, apb, ar71xx_pmc_driver, ar71xx_pmc_devclass, 0, 0);
diff --git a/sys/mips/atheros/ar71xxreg.h b/sys/mips/atheros/ar71xxreg.h
index da5fc5d..c4d0bfd 100644
--- a/sys/mips/atheros/ar71xxreg.h
+++ b/sys/mips/atheros/ar71xxreg.h
@@ -272,10 +272,6 @@
  */
 #define AR71XX_MAC0_BASE	0x19000000
 #define AR71XX_MAC1_BASE	0x1A000000
-/*
- * All 5 PHYs accessible only through MAC0 register space
- */
-#define AR71XX_MII_BASE		0x19000000
 
 #define		AR71XX_MAC_CFG1			0x00
 #define			MAC_CFG1_SOFT_RESET		(1 << 31)
diff --git a/sys/mips/atheros/ar724x_pci.c b/sys/mips/atheros/ar724x_pci.c
index 59be9c0..7098e9e 100644
--- a/sys/mips/atheros/ar724x_pci.c
+++ b/sys/mips/atheros/ar724x_pci.c
@@ -542,8 +542,7 @@ ar724x_pci_intr(void *arg)
 			return (FILTER_STRAY);
 		}
 
-		/* TODO: frame instead of NULL? */
-		intr_event_handle(event, NULL);
+		intr_event_handle(event, curthread->td_intr_frame);
 		mips_intrcnt_inc(sc->sc_intr_counter[irq]);
 	}
 
diff --git a/sys/mips/atheros/files.ar71xx b/sys/mips/atheros/files.ar71xx
index b0b7b02..941d354 100644
--- a/sys/mips/atheros/files.ar71xx
+++ b/sys/mips/atheros/files.ar71xx
@@ -21,3 +21,4 @@ mips/atheros/ar71xx_setup.c	standard
 mips/atheros/ar71xx_chip.c	standard
 mips/atheros/ar724x_chip.c	standard
 mips/atheros/ar91xx_chip.c	standard
+mips/atheros/ar71xx_pmc.c	optional ar71xx_pmc hwpmc
diff --git a/sys/mips/atheros/if_arge.c b/sys/mips/atheros/if_arge.c
index 0154ae2..3dd2e15 100644
--- a/sys/mips/atheros/if_arge.c
+++ b/sys/mips/atheros/if_arge.c
@@ -72,8 +72,18 @@ __FBSDID("$FreeBSD$");
 #include <dev/pci/pcireg.h>
 #include <dev/pci/pcivar.h>
 
+#include <opt_arge.h>
+
+#if defined(ARGE_MDIO)
+#include <dev/etherswitch/mdio.h>
+#include <dev/etherswitch/miiproxy.h>
+#include "mdio_if.h"
+#endif
+
+
 MODULE_DEPEND(arge, ether, 1, 1, 1);
 MODULE_DEPEND(arge, miibus, 1, 1, 1);
+MODULE_VERSION(arge, 1);
 
 #include "miibus_if.h"
 
@@ -102,6 +112,7 @@ typedef enum {
 #endif
 
 static int arge_attach(device_t);
+static int arge_attach_finish(struct arge_softc *sc);
 static int arge_detach(device_t);
 static void arge_flush_ddr(struct arge_softc *);
 static int arge_ifmedia_upd(struct ifnet *);
@@ -134,6 +145,8 @@ static void arge_intr(void *);
 static int arge_intr_filter(void *);
 static void arge_tick(void *);
 
+static void arge_hinted_child(device_t bus, const char *dname, int dunit);
+
 /*
  * ifmedia callbacks for multiPHY MAC
  */
@@ -160,6 +173,10 @@ static device_method_t arge_methods[] = {
 	DEVMETHOD(miibus_writereg,	arge_miibus_writereg),
 	DEVMETHOD(miibus_statchg,	arge_miibus_statchg),
 
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	DEVMETHOD(bus_hinted_child,	arge_hinted_child),
+
 	DEVMETHOD_END
 };
 
@@ -174,6 +191,37 @@ static devclass_t arge_devclass;
 DRIVER_MODULE(arge, nexus, arge_driver, arge_devclass, 0, 0);
 DRIVER_MODULE(miibus, arge, miibus_driver, miibus_devclass, 0, 0);
 
+#if defined(ARGE_MDIO)
+static int argemdio_probe(device_t);
+static int argemdio_attach(device_t);
+static int argemdio_detach(device_t);
+
+/*
+ * Declare an additional, separate driver for accessing the MDIO bus.
+ */
+static device_method_t argemdio_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		argemdio_probe),
+	DEVMETHOD(device_attach,	argemdio_attach),
+	DEVMETHOD(device_detach,	argemdio_detach),
+
+	/* bus interface */
+	DEVMETHOD(bus_add_child,	device_add_child_ordered),
+	
+	/* MDIO access */
+	DEVMETHOD(mdio_readreg,		arge_miibus_readreg),
+	DEVMETHOD(mdio_writereg,	arge_miibus_writereg),
+};
+
+DEFINE_CLASS_0(argemdio, argemdio_driver, argemdio_methods,
+    sizeof(struct arge_softc));
+static devclass_t argemdio_devclass;
+
+DRIVER_MODULE(miiproxy, arge, miiproxy_driver, miiproxy_devclass, 0, 0);
+DRIVER_MODULE(argemdio, nexus, argemdio_driver, argemdio_devclass, 0, 0);
+DRIVER_MODULE(mdio, argemdio, mdio_driver, mdio_devclass, 0, 0);
+#endif
+
 /*
  * RedBoot passes MAC address to entry point as environment 
  * variable. platfrom_start parses it and stores in this variable
@@ -234,15 +282,22 @@ arge_attach_sysctl(device_t dev)
 #endif
 }
 
+#if defined(ARGE_MDIO)
+static void
+arge_attach_proxy(void *aa)
+{
+	arge_attach_finish(aa);
+}
+#endif
+
 static int
 arge_attach(device_t dev)
 {
-	uint8_t			eaddr[ETHER_ADDR_LEN];
 	struct ifnet		*ifp;
 	struct arge_softc	*sc;
-	int			error = 0, rid, phymask;
+	int			error = 0, rid;
 	uint32_t		reg, rnd;
-	int			is_base_mac_empty, i, phys_total;
+	int			is_base_mac_empty, i;
 	uint32_t		hint;
 	long			eeprom_mac_addr = 0;
 
@@ -277,18 +332,18 @@ arge_attach(device_t dev)
 	 *  Get which PHY of 5 available we should use for this unit
 	 */
 	if (resource_int_value(device_get_name(dev), device_get_unit(dev), 
-	    "phymask", &phymask) != 0) {
+	    "phymask", &sc->arge_phymask) != 0) {
 		/*
 		 * Use port 4 (WAN) for GE0. For any other port use 
 		 * its PHY the same as its unit number 
 		 */
 		if (sc->arge_mac_unit == 0)
-			phymask = (1 << 4);
+			sc->arge_phymask = (1 << 4);
 		else
 			/* Use all phys up to 4 */
-			phymask = (1 << 4) - 1;
+			sc->arge_phymask = (1 << 4) - 1;
 
-		device_printf(dev, "No PHY specified, using mask %d\n", phymask);
+		device_printf(dev, "No PHY specified, using mask %d\n", sc->arge_phymask);
 	}
 
 	/*
@@ -313,8 +368,6 @@ arge_attach(device_t dev)
 	else
 		sc->arge_duplex_mode = 0;
 
-	sc->arge_phymask = phymask;
-
 	mtx_init(&sc->arge_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
 	    MTX_DEF);
 	callout_init_mtx(&sc->arge_stat_callout, &sc->arge_mtx, 0);
@@ -323,7 +376,7 @@ arge_attach(device_t dev)
 	/* Map control/status registers. */
 	sc->arge_rid = 0;
 	sc->arge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 
-	    &sc->arge_rid, RF_ACTIVE);
+	    &sc->arge_rid, RF_ACTIVE | RF_SHAREABLE);
 
 	if (sc->arge_res == NULL) {
 		device_printf(dev, "couldn't map memory\n");
@@ -371,8 +424,8 @@ arge_attach(device_t dev)
 
 	is_base_mac_empty = 1;
 	for (i = 0; i < ETHER_ADDR_LEN; i++) {
-		eaddr[i] = ar711_base_mac[i] & 0xff;
-		if (eaddr[i] != 0)
+		sc->arge_eaddr[i] = ar711_base_mac[i] & 0xff;
+		if (sc->arge_eaddr[i] != 0)
 			is_base_mac_empty = 0;
 	}
 
@@ -385,16 +438,15 @@ arge_attach(device_t dev)
 			    "Generating random ethernet address.\n");
 
 		rnd = arc4random();
-		eaddr[0] = 'b';
-		eaddr[1] = 's';
-		eaddr[2] = 'd';
-		eaddr[3] = (rnd >> 24) & 0xff;
-		eaddr[4] = (rnd >> 16) & 0xff;
-		eaddr[5] = (rnd >> 8) & 0xff;
+		sc->arge_eaddr[0] = 'b';
+		sc->arge_eaddr[1] = 's';
+		sc->arge_eaddr[2] = 'd';
+		sc->arge_eaddr[3] = (rnd >> 24) & 0xff;
+		sc->arge_eaddr[4] = (rnd >> 16) & 0xff;
+		sc->arge_eaddr[5] = (rnd >> 8) & 0xff;
 	}
-
 	if (sc->arge_mac_unit != 0)
-		eaddr[5] +=  sc->arge_mac_unit;
+		sc->arge_eaddr[5] +=  sc->arge_mac_unit;
 
 	if (arge_dma_alloc(sc) != 0) {
 		error = ENXIO;
@@ -423,19 +475,23 @@ arge_attach(device_t dev)
 
 	ARGE_WRITE(sc, AR71XX_MAC_MAX_FRAME_LEN, 1536);
 
+#if !defined(ARGE_MDIO)
 	/* Reset MII bus */
 	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_RESET);
 	DELAY(100);
 	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_CLOCK_DIV_28);
 	DELAY(100);
+#endif
 
 	/* 
 	 * Set all Ethernet address registers to the same initial values
 	 * set all four addresses to 66-88-aa-cc-dd-ee 
 	 */
-	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, 
-	    (eaddr[2] << 24) | (eaddr[3] << 16) | (eaddr[4] << 8)  | eaddr[5]);
-	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (eaddr[0] << 8) | eaddr[1]);
+	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, (sc->arge_eaddr[2] << 24)
+	    | (sc->arge_eaddr[3] << 16) | (sc->arge_eaddr[4] << 8)
+	    | sc->arge_eaddr[5]);
+	ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (sc->arge_eaddr[0] << 8)
+	    | sc->arge_eaddr[1]);
 
 	ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG0, 
 	    FIFO_CFG0_ALL << FIFO_CFG0_ENABLE_SHIFT);
@@ -458,30 +514,44 @@ arge_attach(device_t dev)
 	ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMASK, 
 	    FIFO_RX_FILTMASK_DEFAULT);
 
-	/* 
-	 * Check if we have single-PHY MAC or multi-PHY
-	 */
-	phys_total = 0;
-	for (i = 0; i < ARGE_NPHY; i++)
-		if (phymask & (1 << i))
-			phys_total ++;
+#if defined(ARGE_MDIO)
+	sc->arge_miiproxy = mii_attach_proxy(sc->arge_dev, arge_attach_proxy, sc);
+	if (sc->arge_miiproxy == NULL)
+		return (arge_attach_finish(sc));
+#else
+	return (arge_attach_finish(sc));
+#endif
+fail:
+	if (error) 
+		arge_detach(dev);
 
-	if (phys_total == 0) {
-		error = EINVAL;
-		goto fail;
-	}
+	return (error);
+}
 
-	if (phys_total == 1) {
-		/* Do MII setup. */
-		error = mii_attach(dev, &sc->arge_miibus, ifp,
-		    arge_ifmedia_upd, arge_ifmedia_sts, BMSR_DEFCAPMASK,
-		    MII_PHY_ANY, MII_OFFSET_ANY, 0);
-		if (error != 0) {
-			device_printf(dev, "attaching PHYs failed\n");
-			goto fail;
+static int
+arge_attach_finish(struct arge_softc *sc)
+{
+	int	error, phy;
+
+	device_printf(sc->arge_dev, "finishing attachment, phymask %04x"
+	    ", proxy %s \n", sc->arge_phymask, sc->arge_miiproxy == NULL ?
+	    "null" : "set");
+	for (phy = 0; phy < ARGE_NPHY; phy++) {
+		if (((1 << phy) & sc->arge_phymask) != 0) {
+			error = mii_attach(sc->arge_miiproxy != NULL ?
+			    sc->arge_miiproxy : sc->arge_dev,
+			    &sc->arge_miibus, sc->arge_ifp,
+			    arge_ifmedia_upd, arge_ifmedia_sts,
+			    BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+			if (error != 0) {
+				device_printf(sc->arge_dev, "unable to attach"
+				    " PHY %d: %d\n", phy, error);
+				goto fail;
+			}
 		}
 	}
-	else {
+	if (sc->arge_miibus == NULL) {
+		/* no PHY, so use hard-coded values */
 		ifmedia_init(&sc->arge_ifmedia, 0, 
 		    arge_multiphy_mediachange,
 		    arge_multiphy_mediastatus);
@@ -494,24 +564,23 @@ arge_attach(device_t dev)
 	}
 
 	/* Call MI attach routine. */
-	ether_ifattach(ifp, eaddr);
+	ether_ifattach(sc->arge_ifp, sc->arge_eaddr);
 
 	/* Hook interrupt last to avoid having to lock softc */
-	error = bus_setup_intr(dev, sc->arge_irq, INTR_TYPE_NET | INTR_MPSAFE,
+	error = bus_setup_intr(sc->arge_dev, sc->arge_irq, INTR_TYPE_NET | INTR_MPSAFE,
 	    arge_intr_filter, arge_intr, sc, &sc->arge_intrhand);
 
 	if (error) {
-		device_printf(dev, "couldn't set up irq\n");
-		ether_ifdetach(ifp);
+		device_printf(sc->arge_dev, "couldn't set up irq\n");
+		ether_ifdetach(sc->arge_ifp);
 		goto fail;
 	}
 
 	/* setup sysctl variables */
-	arge_attach_sysctl(dev);
-
+	arge_attach_sysctl(sc->arge_dev);
 fail:
 	if (error) 
-		arge_detach(dev);
+		arge_detach(sc->arge_dev);
 
 	return (error);
 }
@@ -542,6 +611,9 @@ arge_detach(device_t dev)
 	if (sc->arge_miibus)
 		device_delete_child(dev, sc->arge_miibus);
 
+	if (sc->arge_miiproxy)
+		device_delete_child(dev, sc->arge_miiproxy);
+
 	bus_generic_detach(dev);
 
 	if (sc->arge_intrhand)
@@ -592,6 +664,13 @@ arge_shutdown(device_t dev)
 	return (0);
 }
 
+static void
+arge_hinted_child(device_t bus, const char *dname, int dunit)
+{
+	BUS_ADD_CHILD(bus, 0, dname, dunit);
+	device_printf(bus, "hinted child %s%d\n", dname, dunit);
+}
+
 static int
 arge_miibus_readreg(device_t dev, int phy, int reg)
 {
@@ -600,16 +679,13 @@ arge_miibus_readreg(device_t dev, int phy, int reg)
 	uint32_t addr = (phy << MAC_MII_PHY_ADDR_SHIFT) 
 	    | (reg & MAC_MII_REG_MASK);
 
-	if ((sc->arge_phymask  & (1 << phy)) == 0)
-		return (0);
-
 	mtx_lock(&miibus_mtx);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_READ);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_ADDR, addr);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_READ);
 
 	i = ARGE_MII_TIMEOUT;
-	while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & 
+	while ((ARGE_MDIO_READ(sc, AR71XX_MAC_MII_INDICATOR) & 
 	    MAC_MII_INDICATOR_BUSY) && (i--))
 		DELAY(5);
 
@@ -620,8 +696,8 @@ arge_miibus_readreg(device_t dev, int phy, int reg)
 		return (-1);
 	}
 
-	result = ARGE_MII_READ(AR71XX_MAC_MII_STATUS) & MAC_MII_STATUS_MASK;
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
+	result = ARGE_MDIO_READ(sc, AR71XX_MAC_MII_STATUS) & MAC_MII_STATUS_MASK;
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE);
 	mtx_unlock(&miibus_mtx);
 
 	ARGEDEBUG(sc, ARGE_DBG_MII, "%s: phy=%d, reg=%02x, value[%08x]=%04x\n", __func__, 
@@ -638,19 +714,15 @@ arge_miibus_writereg(device_t dev, int phy, int reg, int data)
 	uint32_t addr = 
 	    (phy << MAC_MII_PHY_ADDR_SHIFT) | (reg & MAC_MII_REG_MASK);
 
-
-	if ((sc->arge_phymask  & (1 << phy)) == 0)
-		return (-1);
-
 	ARGEDEBUG(sc, ARGE_DBG_MII, "%s: phy=%d, reg=%02x, value=%04x\n", __func__, 
 	    phy, reg, data);
 
 	mtx_lock(&miibus_mtx);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr);
-	ARGE_MII_WRITE(AR71XX_MAC_MII_CONTROL, data);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_ADDR, addr);
+	ARGE_MDIO_WRITE(sc, AR71XX_MAC_MII_CONTROL, data);
 
 	i = ARGE_MII_TIMEOUT;
-	while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & 
+	while ((ARGE_MDIO_READ(sc, AR71XX_MAC_MII_INDICATOR) & 
 	    MAC_MII_INDICATOR_BUSY) && (i--))
 		DELAY(5);
 
@@ -715,6 +787,8 @@ arge_set_pll(struct arge_softc *sc, int media, int duplex)
 	uint32_t		fifo_tx;
 	int if_speed;
 
+	ARGEDEBUG(sc, ARGE_DBG_MII, "set_pll(%04x, %s)\n", media,
+	    duplex == IFM_FDX ? "full" : "half");
 	cfg = ARGE_READ(sc, AR71XX_MAC_CFG2);
 	cfg &= ~(MAC_CFG2_IFACE_MODE_1000 
 	    | MAC_CFG2_IFACE_MODE_10_100 
@@ -1923,3 +1997,47 @@ arge_multiphy_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
 	    sc->arge_duplex_mode;
 }
 
+#if defined(ARGE_MDIO)
+static int
+argemdio_probe(device_t dev)
+{
+	device_set_desc(dev, "Atheros AR71xx built-in ethernet interface, MDIO controller");
+	return (0);
+}
+
+static int
+argemdio_attach(device_t dev)
+{
+	struct arge_softc	*sc;
+	int			error = 0;
+
+	sc = device_get_softc(dev);
+	sc->arge_dev = dev;
+	sc->arge_mac_unit = device_get_unit(dev);
+	sc->arge_rid = 0;
+	sc->arge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 
+	    &sc->arge_rid, RF_ACTIVE | RF_SHAREABLE);
+	if (sc->arge_res == NULL) {
+		device_printf(dev, "couldn't map memory\n");
+		error = ENXIO;
+		goto fail;
+	}
+	/* Reset MII bus */
+	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_RESET);
+	DELAY(100);
+	ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_CLOCK_DIV_28);
+	DELAY(100);
+	bus_generic_probe(dev);
+	bus_enumerate_hinted_children(dev);
+	error = bus_generic_attach(dev);
+fail:
+	return (error);
+}
+
+static int
+argemdio_detach(device_t dev)
+{
+	return (0);
+}
+
+#endif
diff --git a/sys/mips/atheros/if_argevar.h b/sys/mips/atheros/if_argevar.h
index c1df678..dc3d931 100644
--- a/sys/mips/atheros/if_argevar.h
+++ b/sys/mips/atheros/if_argevar.h
@@ -67,15 +67,10 @@
 #define ARGE_CLEAR_BITS(sc, reg, bits)	\
 	ARGE_WRITE(sc, reg, ARGE_READ(sc, (reg)) & ~(bits))
 
-/*
- * MII registers access macros
- */
-#define ARGE_MII_READ(reg) \
-        *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg)))
-
-#define ARGE_MII_WRITE(reg, val) \
-        *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg))) = (val)
-
+#define ARGE_MDIO_WRITE(_sc, _reg, _val)	\
+	ARGE_WRITE((_sc), (_reg), (_val))
+#define ARGE_MDIO_READ(_sc, _reg)	\
+	ARGE_READ((_sc), (_reg))
 
 #define ARGE_DESC_EMPTY		(1 << 31)
 #define ARGE_DESC_MORE		(1 << 24)
@@ -132,11 +127,14 @@ struct arge_softc {
 	 */
 	uint32_t		arge_media_type;
 	uint32_t		arge_duplex_mode;
+	uint32_t		arge_phymask;
+	uint8_t			arge_eaddr[ETHER_ADDR_LEN];
 	struct resource		*arge_res;
 	int			arge_rid;
 	struct resource		*arge_irq;
 	void			*arge_intrhand;
 	device_t		arge_miibus;
+	device_t		arge_miiproxy;
 	bus_dma_tag_t		arge_parent_tag;
 	bus_dma_tag_t		arge_tag;
 	struct mtx		arge_mtx;
@@ -148,7 +146,6 @@ struct arge_softc {
 	int			arge_detach;
 	uint32_t		arge_intr_status;
 	int			arge_mac_unit;
-	int			arge_phymask;
 	int			arge_if_flags;
 	uint32_t		arge_debug;
 	struct {
diff --git a/sys/mips/conf/AR91XX_BASE b/sys/mips/conf/AR91XX_BASE
index a3686be..ba44bcf 100644
--- a/sys/mips/conf/AR91XX_BASE
+++ b/sys/mips/conf/AR91XX_BASE
@@ -16,6 +16,7 @@ cpu		CPU_MIPS4KC
 options 	ISA_MIPS32
 makeoptions	KERNLOADADDR=0x80050000
 options		HZ=1000
+options		HWPMC_HOOKS
 
 files		"../atheros/files.ar71xx"
 hints		"AR91XX_BASE.hints"
@@ -111,3 +112,9 @@ device		random
 device		if_bridge
 device		gpio
 device		gpioled
+device		vlan
+
+device		hwpmc
+
+# Tie into the interrupt line for PMC events
+device		ar71xx_pmc
diff --git a/sys/mips/conf/AR91XX_BASE.hints b/sys/mips/conf/AR91XX_BASE.hints
index 81442f3..b911d49 100644
--- a/sys/mips/conf/AR91XX_BASE.hints
+++ b/sys/mips/conf/AR91XX_BASE.hints
@@ -57,3 +57,6 @@ hint.gpio.0.at="apb0"
 hint.gpio.0.maddr=0x18040000
 hint.gpio.0.msize=0x1000
 hint.gpio.0.irq=2
+
+hint.ar71xx_pmc.0.at="apb0"
+hint.ar71xx_pmc.0.irq=5
diff --git a/sys/mips/conf/TL-MR3420 b/sys/mips/conf/TL-MR3420
new file mode 100644
index 0000000..6f89f31
--- /dev/null
+++ b/sys/mips/conf/TL-MR3420
@@ -0,0 +1,45 @@
+#
+# TL-WR3420 -- Kernel configuration file for the TP-Link MR3420
+#
+# $FreeBSD$
+#
+
+# Include the default AR913x parameters common to all AR913x SoC users.
+include         "AR91XX_BASE"
+
+ident           TL-MR3420
+
+# Override hints with board values
+hints           "TL-MR3420.hints"
+
+# Force the board memory - 32mb
+options         AR71XX_REALMEM=32*1024*1024
+
+nodevice	ath
+nodevice	ath_rate_sample
+nodevice	ath_ahb
+nodevice	ath_ar5212
+nodevice	ath_ar5416
+nodevice	ath_ar9130
+nodevice	ath_rf5111
+
+# ethernet switch device
+device		etherswitch
+device		arswitch
+device		miiproxy
+options		ARGE_MDIO
+
+# Enable the uboot environment stuff rather then the
+# redboot stuff.
+options		AR71XX_ENV_UBOOT
+
+# uzip - to boot natively from flash
+device		geom_uzip
+options		GEOM_UZIP
+
+# Used for the static uboot partition map
+device          geom_map
+
+# Boot off of the rootfs, as defined in the geom_map setup.
+options		ROOTDEVNAME=\"ufs:map/rootfs.uzip\"
+options		ROOTDEVNAME=\"ufs:da0s1a\"
diff --git a/sys/mips/conf/TL-MR3420.hints b/sys/mips/conf/TL-MR3420.hints
new file mode 100644
index 0000000..58adf3f
--- /dev/null
+++ b/sys/mips/conf/TL-MR3420.hints
@@ -0,0 +1,95 @@
+#
+# This file adds to the values in AR91XX_BASE.hints.
+#
+# $FreeBSD$
+
+# AR7241 has built in switch and hardwired MAC connections, so just supply
+# the appropriate values.
+hint.arge.0.phymask=0x10
+hint.arge.0.eeprommac=0x1f01fc00
+hint.arge.0.mdio=mdioproxy1
+
+hint.arge.1.phymask=0
+hint.arge.1.media=1000
+hint.arge.1.fduplex=1
+
+# MDIO bus of arge0 is unused, MDIO bus of arge1 is used to talk to switch
+hint.argemdio.0.at="nexus0"
+hint.argemdio.0.maddr=0x1A000000
+hint.argemdio.0.msize=0x1000
+
+# Where the ART is
+#hint.ath.0.eepromaddr=0x1fff1000
+
+# from uboot:
+# mtdparts=ar7240-nor0:256k(u-boot),64k(u-boot-env),2752k(rootfs),896k(uImage),64k(NVRAM),64k(ART)
+
+hint.map.0.at="flash/spi0"
+hint.map.0.start=0x00000000
+hint.map.0.end=0x00040000
+hint.map.0.name="uboot"
+hint.map.0.readonly=1
+
+hint.map.1.at="flash/spi0"
+hint.map.1.start=0x00004000
+hint.map.1.end=0x00050000
+hint.map.1.name="ubootenv"
+hint.map.1.readonly=1
+
+hint.map.2.at="flash/spi0"
+hint.map.2.start=0x00060000
+hint.map.2.end=0x00300000
+hint.map.2.name="rootfs"
+hint.map.2.readonly=1
+
+hint.map.3.at="flash/spi0"
+hint.map.3.start=0x00300000
+hint.map.3.end=0x003e00000
+hint.map.3.name="uimage"
+hint.map.3.readonly=0
+
+hint.map.3.at="flash/spi0"
+hint.map.3.start=0x003e0000
+hint.map.3.end=0x003f0000
+hint.map.3.name="nvram"
+hint.map.3.readonly=0
+
+hint.map.5.at="flash/spi0"
+hint.map.5.start=0x003f0000
+hint.map.5.end=0x00800000
+hint.map.5.name="art"
+hint.map.5.readonly=1
+
+# GPIO specific configuration block
+
+# Don't flip on anything that isn't already enabled.
+# This includes leaving the SPI CS1/CS2 pins as GPIO pins as they're
+# not used here.
+hint.gpio.0.function_set=0x00000000
+hint.gpio.0.function_clear=0x00000000
+
+# These are the GPIO LEDs and buttons which can be software controlled.
+hint.gpio.0.pinmask=0x1943
+# Switch on GPIO 6 to power up the USB port from the beginning
+hint.gpio.0.pinon=0x0040
+
+# Pin 0 - QSS (LED)
+# Pin 1 - System (LED)
+# Pin 6 - USB power (output)
+# pin 8 - 3G (LED)
+# Pin 11 - Reset (input)
+# Pin 12 - QSS Button (input)
+
+# LEDs are configured separately and driven by the LED device
+hint.gpioled.0.at="gpiobus0"
+hint.gpioled.0.name="qss"
+hint.gpioled.0.pins=0x0001
+
+hint.gpioled.1.at="gpiobus0"
+hint.gpioled.1.name="system"
+hint.gpioled.1.pins=0x0002
+
+hint.gpioled.2.at="gpiobus0"
+hint.gpioled.2.name="3g"
+hint.gpioled.2.pins=0x0008
+
diff --git a/sys/mips/conf/TP-WN1043ND b/sys/mips/conf/TP-WN1043ND
index d81a6f6..c05b870 100644
--- a/sys/mips/conf/TP-WN1043ND
+++ b/sys/mips/conf/TP-WN1043ND
@@ -15,6 +15,18 @@ hints           "TP-WN1043ND.hints"
 # Force the board memory - 32mb
 options         AR71XX_REALMEM=32*1024*1024
 
+# i2c GPIO bus
+device		gpioiic
+device		iicbb
+device		iicbus
+device		iic
+
+# ethernet switch device
+device		etherswitch
+
+# RTL8366RB support
+device		rtl8366rb
+
 # read MSDOS formatted disks - USB
 options		MSDOSFS
 options		GEOM_PART_BSD
diff --git a/sys/mips/conf/TP-WN1043ND.hints b/sys/mips/conf/TP-WN1043ND.hints
index dce3fea..f72f3ce 100644
--- a/sys/mips/conf/TP-WN1043ND.hints
+++ b/sys/mips/conf/TP-WN1043ND.hints
@@ -81,7 +81,7 @@ hint.map.4.readonly=1
 # Don't flip on anything that isn't already enabled.
 # This includes leaving the SPI CS1/CS2 pins as GPIO pins as they're
 # not used here.
-hint.gpio.0.function_set=0x00000000
+hint.gpio.0.function_set=0x00002000
 hint.gpio.0.function_clear=0x00000000
 
 # These are the GPIO LEDs and buttons which can be software controlled.
@@ -101,9 +101,9 @@ hint.gpio.0.pinmask=0x001c02ae
 # Pin 20 - "GPIO20"
 
 # LEDs are configured separately and driven by the LED device
-hint.gpioled.0.at="gpiobus0"
-hint.gpioled.0.name="usb"
-hint.gpioled.0.pins=0x0002
+#hint.gpioled.0.at="gpiobus0"
+#hint.gpioled.0.name="usb"
+#hint.gpioled.0.pins=0x0002
 
 hint.gpioled.1.at="gpiobus0"
 hint.gpioled.1.name="system"
@@ -116,3 +116,17 @@ hint.gpioled.2.pins=0x0020
 hint.gpioled.3.at="gpiobus0"
 hint.gpioled.3.name="wlan"
 hint.gpioled.3.pins=0x0200
+
+# GPIO I2C bus
+hint.gpioiic.0.at="gpiobus0"
+hint.gpioiic.0.pins=0xc0000
+hint.gpioiic.0.scl=1
+hint.gpioiic.0.sda=0
+
+# I2C bus
+# Don't be strict about I2C protocol - the relaxed semantics are required
+# by the realtek switch PHY.
+hint.iicbus.0.strict=0
+
+# Bit bang bus - override default delay
+#hint.iicbb.0.udelay=3
diff --git a/sys/mips/conf/WRT160NL b/sys/mips/conf/WRT160NL
new file mode 100644
index 0000000..083f128
--- /dev/null
+++ b/sys/mips/conf/WRT160NL
@@ -0,0 +1,49 @@
+#
+# WRT160NL -- Kernel configuration file for the Cisco WRT160NL
+#
+# $FreeBSD$
+#
+
+# Include the default AR913x parameters common to all AR913x SoC users.
+include         "AR91XX_BASE"
+
+ident           WRT160NL
+
+# Override hints with board values
+hints           "WRT160NL.hints"
+
+# Force the board memory - 32mb
+options         AR71XX_REALMEM=32*1024*1024
+
+# read MSDOS formatted disks - USB
+options		MSDOSFS
+options		GEOM_PART_BSD
+options		GEOM_PART_MBR
+
+# Enable the uboot environment stuff rather then the
+# redboot stuff.
+options		AR71XX_ENV_UBOOT
+
+# uzip - to boot natively from flash
+device		geom_uzip
+options		GEOM_UZIP
+
+# Used for the static uboot partition map
+device          geom_map
+
+# Boot off of the rootfs, as defined in the geom_map setup.
+#options		ROOTDEVNAME=\"ufs:map/rootfs.uzip\"
+options		ROOTDEVNAME=\"ufs:da0s1a\"
+
+# stb development stuff
+options		BREAK_TO_DEBUGGER
+options		ALT_BREAK_TO_DEBUGGER
+#nooptions		INVARIANTS
+#nooptions		INVARIANT_SUPPORT
+#nooptions		WITNESS
+#nooptions		WITNESS_SKIPSPIN
+options		BOOTP
+options		BOOTP_NFSROOT
+options		BOOTP_NFSV3
+options		NFSCL
+options		NFS_ROOT
diff --git a/sys/mips/conf/WRT160NL.hints b/sys/mips/conf/WRT160NL.hints
new file mode 100644
index 0000000..38ecdf0
--- /dev/null
+++ b/sys/mips/conf/WRT160NL.hints
@@ -0,0 +1,132 @@
+#
+# This file adds to the values in AR91XX_BASE.hints.
+#
+# $FreeBSD$
+
+# Hard-code the PHY for now, until there's switch phy support.
+hint.arge.0.phymask=0x0001
+hint.arge.0.media=1000
+hint.arge.0.fduplex=1
+# Where is the MAC address stored in flash for this particular unit.
+hint.arge.0.eeprommac=0x1f01fc00
+
+hint.arge.1.phymask=0x10
+
+# Where the ART is
+hint.ath.0.eepromaddr=0x1fff1000
+
+#
+# Define a slightly custom flash layout.
+
+# The default flash layout:
+#
+# 128k: uboot
+# 1024k: kernel
+# 4096k: rootfs
+# 2816: unknown
+# 64k: board config?
+# 64k: ART
+#
+# from printenv:
+# bootargs=console=ttyS0,115200 root=31:02 rootfstype=jffs2 init=/sbin/init
+#    mtdparts=ar9100-nor0:128k(u-boot),1024k(kernel),4096k(rootfs),64k(art)
+
+# This isn't a lot of space!
+# So:
+# 128k: uboot
+# 2048k: kernel
+# 5888k: rootfs
+# 64k: config
+# 64k: ART
+
+hint.map.0.at="flash/spi0"
+hint.map.0.start=0x00000000
+hint.map.0.end=0x000040000
+hint.map.0.name="uboot"
+hint.map.0.readonly=1
+
+hint.map.1.at="flash/spi0"
+hint.map.1.start=0x00040000
+hint.map.1.end=0x00220000
+hint.map.1.name="kernel"
+hint.map.1.readonly=1
+
+hint.map.2.at="flash/spi0"
+hint.map.2.start=0x00220000
+hint.map.2.end=0x007e0000
+hint.map.2.name="rootfs"
+hint.map.2.readonly=1
+
+hint.map.3.at="flash/spi0"
+hint.map.3.start=0x007e0000
+hint.map.3.end=0x007f0000
+hint.map.3.name="cfg"
+hint.map.3.readonly=0
+
+# This is radio calibration section.  It is (or should be!) unique
+# for each board, to take into account thermal and electrical differences
+# as well as the regulatory compliance data.
+#
+hint.map.4.at="flash/spi0"
+hint.map.4.start=0x007f0000
+hint.map.4.end=0x00800000
+hint.map.4.name="art"
+hint.map.4.readonly=1
+
+hint.map.5.at="flash/spi0"
+hint.map.5.start=0x00000000
+hint.map.5.end=0x00800000
+hint.map.5.name="flash"
+hint.map.5.readonly=1
+
+# GPIO specific configuration block
+
+# Don't flip on anything that isn't already enabled.
+# This includes leaving the SPI CS1/CS2 pins as GPIO pins as they're
+# not used here.
+hint.gpio.0.function_set=0x00000000
+hint.gpio.0.function_clear=0x00000000
+
+# These are the GPIO LEDs and buttons which can be software controlled.
+hint.gpio.0.pinmask=0x001c02ae
+
+# pin 1 - USB (LED)
+# pin 2 - System (LED)
+# Pin 3 - Reset (input)
+# Pin 5 - QSS (LED)
+# Pin 7 - QSS Button (input)
+# Pin 8 - wired into the chip reset line
+# Pin 9 - WLAN
+# Pin 10 - UART TX (not GPIO)
+# Pin 13 - UART RX (not GPIO)
+# Pin 18 - RTL8366RB switch data line
+# Pin 19 - RTL8366RB switch clock line
+# Pin 20 - "GPIO20"
+
+# LEDs are configured separately and driven by the LED device
+hint.gpioled.0.at="gpiobus0"
+hint.gpioled.0.name="usb"
+hint.gpioled.0.pins=0x0002
+
+hint.gpioled.1.at="gpiobus0"
+hint.gpioled.1.name="system"
+hint.gpioled.1.pins=0x0004
+
+hint.gpioled.2.at="gpiobus0"
+hint.gpioled.2.name="qss"
+hint.gpioled.2.pins=0x0020
+
+hint.gpioled.3.at="gpiobus0"
+hint.gpioled.3.name="wlan"
+hint.gpioled.3.pins=0x0200
+
+# GPIO I2C bus
+hint.gpioiic.0.at="gpiobus0"
+hint.gpioiic.0.pins=0xc0000
+hint.gpioiic.0.scl=1
+hint.gpioiic.0.sda=0
+
+# I2C bus
+# Don't be strict about I2C protocol - the relaxed semantics are required
+# by the realtek switch PHY.
+hint.iicbus.0.strict=0
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 3a3f5e2..2f53854 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -92,6 +92,7 @@ SUBDIR=	${_3dfx} \
 	${_epic} \
 	esp \
 	${_et} \
+	etherswitch \
 	${_ex} \
 	${_exca} \
 	${_ext2fs} \
@@ -265,6 +266,7 @@ SUBDIR=	${_3dfx} \
 	re \
 	reiserfs \
 	rl \
+	rtl8366rb \
 	runfw \
 	${_s3} \
 	${_safe} \
diff --git a/sys/modules/ath/Makefile b/sys/modules/ath/Makefile
index 6e2e7b7..acf61b1 100644
--- a/sys/modules/ath/Makefile
+++ b/sys/modules/ath/Makefile
@@ -105,7 +105,7 @@ SRCS+=	ar9160_attach.c
 # (currently) breaks non-AR9130 chipsets - since this is an embedded
 # chipset and no other radios are glued to it, this shouldn't pose a
 # problem.
-SRCS+=	ar9130_attach.c ar9130_eeprom.c ar9130_phy.c
+# SRCS+=	ar9130_attach.c ar9130_eeprom.c ar9130_phy.c
 
 # AR9002 series chips
 # + AR9220/AR9280 - Merlin
@@ -140,8 +140,10 @@ SRCS+=	dfs_null.c
 
 CFLAGS+=  -I. -I${.CURDIR}/../../dev/ath -I${.CURDIR}/../../dev/ath/ath_hal
 
+.if !defined(KERNBUILDDIR)
 opt_ah.h:
 	echo '#define AH_SUPPORT_AR5416 1' > $@
+.endif
 
 .include <bsd.kmod.mk>
 
diff --git a/sys/modules/etherswitch/Makefile b/sys/modules/etherswitch/Makefile
new file mode 100644
index 0000000..b40a41e
--- /dev/null
+++ b/sys/modules/etherswitch/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+.PATH:	${.CURDIR}/../../dev/etherswitch
+KMOD=	etherswitch
+SRCS=	etherswitch.c device_if.h bus_if.h etherswitch_if.h
+SRCS+=	opt_cputype.h etherswitch_if.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/modules/rtl8366rb/Makefile b/sys/modules/rtl8366rb/Makefile
new file mode 100644
index 0000000..40983ea
--- /dev/null
+++ b/sys/modules/rtl8366rb/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+.PATH:	${.CURDIR}/../../dev/etherswitch
+KMOD=	rtl8366rb
+SRCS=	rtl8366rb.c rtl8366rbvar.h 
+SRCS+=	device_if.h bus_if.h iicbus_if.h miibus_if.h etherswitch_if.h opt_cputype.h opt_global.h
+
+.include <bsd.kmod.mk>

Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?76C5AEDF-39BA-4824-B8F8-BBD451200EF2>