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

--Apple-Mail=_CE96FAC5-9AF0-44EF-BFDB-F3380CF6C0C4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
	charset=us-ascii

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=3Dmdioproxy1

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

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



--Apple-Mail=_CE96FAC5-9AF0-44EF-BFDB-F3380CF6C0C4
Content-Disposition: attachment;
	filename=etherswitchcfg.patch
Content-Type: application/octet-stream;
	name="etherswitchcfg.patch"
Content-Transfer-Encoding: 7bit

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.
+ **********************************************************************/

--Apple-Mail=_CE96FAC5-9AF0-44EF-BFDB-F3380CF6C0C4
Content-Disposition: attachment;
	filename=miiproxy.patch
Content-Type: application/octet-stream;
	name="miiproxy.patch"
Content-Transfer-Encoding: 7bit

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 {

--Apple-Mail=_CE96FAC5-9AF0-44EF-BFDB-F3380CF6C0C4
Content-Disposition: attachment;
	filename=etherswitch-complete.patch
Content-Type: application/octet-stream;
	name="etherswitch-complete.patch"
Content-Transfer-Encoding: 7bit

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>

--Apple-Mail=_CE96FAC5-9AF0-44EF-BFDB-F3380CF6C0C4--



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