Index | Thread | Search

From:
David Gwynne <david@gwynne.id.au>
Subject:
Re: Support RTL8367B-CG ethernet switch
To:
Georg Bege <georg@bege.email>
Cc:
tech@openbsd.org
Date:
Mon, 22 Jul 2024 17:45:31 +1000

Download raw body.

Thread
On Wed, Jul 17, 2024 at 05:24:42PM +0200, Georg Bege wrote:
> Hello OpenBSD folks,
> 
> Im interested in the RTL8367B-CG switch ethernet port.
> This one is for example found on an Banana Pi R2 pro, however it seems that OpenBSD has no support for this driver (yet)?
> 
> Anyone knows something about this?

you're right, openbsd does not have support for that chip. the only
switch it has any support for is a narrow set of the marvell switch
chips as found on things like the espressobin, and that support is
limited to using it as a dumb ethernet switch.

i have a bpi r2 pro, but it's a revision using a mediatek switch
chip. i got most of the way through a driver for it (and rejigged
the marvell switch driver) so you can use the switch ports as
separate ethernet interfaces in openbsd. it's mostly working, but
i ran out of time to push it further and no one else seemed interested
in it.

banana pi linked to doco for the realtek chip iirc. if you have a board
with the realtek chip you could probably hack the mvsw and mtsw code up
and have it work them same.

the bpi r2 pro with the mediatek switch looks like this. the diff is
below.

OpenBSD 7.3-current (GENERIC.MP) #395: Mon Apr 24 11:50:25 AEST 2023
    dlg@o1000.eait.uq.edu.au:/home/dlg/src/sys/arch/arm64/compile/GENERIC.MP
real mem  = 2143793152 (2044MB)
avail mem = 2041298944 (1946MB)
random: good seed from bootblocks
mainbus0 at root: Banana Pi BPI-R2 Pro (RK3568)
psci0 at mainbus0: PSCI 1.1, SMCCC 1.2, SYSTEM_SUSPEND
cpu0 at mainbus0 mpidr 0: ARM Cortex-A55 r2p0
cpu0: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
cpu0: 512KB 64b/line 16-way L2 cache
cpu0: DP,RDM,Atomic,CRC32,SHA2,SHA1,AES+PMULL,LRCPC,DPB,ASID16,PAN+ATS1E1,LO,HPDS,HAFDBS,SBSS
cpu1 at mainbus0 mpidr 100: ARM Cortex-A55 r2p0
cpu1: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
cpu1: 512KB 64b/line 16-way L2 cache
cpu1: DP,RDM,Atomic,CRC32,SHA2,SHA1,AES+PMULL,LRCPC,DPB,ASID16,PAN+ATS1E1,LO,HPDS,HAFDBS,SBSS
cpu2 at mainbus0 mpidr 200: ARM Cortex-A55 r2p0
cpu2: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
cpu2: 512KB 64b/line 16-way L2 cache
cpu2: DP,RDM,Atomic,CRC32,SHA2,SHA1,AES+PMULL,LRCPC,DPB,ASID16,PAN+ATS1E1,LO,HPDS,HAFDBS,SBSS
cpu3 at mainbus0 mpidr 300: ARM Cortex-A55 r2p0
cpu3: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
cpu3: 512KB 64b/line 16-way L2 cache
cpu3: DP,RDM,Atomic,CRC32,SHA2,SHA1,AES+PMULL,LRCPC,DPB,ASID16,PAN+ATS1E1,LO,HPDS,HAFDBS,SBSS
efi0 at mainbus0: UEFI 2.10
efi0: Das U-Boot rev 0x20230400
smbios0 at efi0: SMBIOS 3.0
smbios0: vendor U-Boot version "2023.04-rc5-00057-g455f346de6-dirty" date 04/01/2023
smbios0: Banana Pi BPI-R2 Pro
scmi0 at mainbus0: SCMI 2.0
apm0 at mainbus0
agintc0 at mainbus0 mbi shift 4:4 nirq 352 nredist 4 ipi: 0, 1, 2: "interrupt-controller"
syscon0 at mainbus0: "syscon"
rkiovd0 at syscon0
syscon1 at mainbus0: "syscon"
syscon2 at mainbus0: "syscon"
syscon3 at mainbus0: "syscon"
syscon4 at mainbus0: "syscon"
syscon5 at mainbus0: "syscon"
syscon6 at mainbus0: "syscon"
rkclock0 at mainbus0: PMUCRU
rkclock1 at mainbus0: CRU
syscon7 at mainbus0: "power-management"
rkpmu0 at syscon7: pwr 007f status 007f req 0000 idle 01ee ack 01ee
syscon8 at mainbus0: "qos"
syscon9 at mainbus0: "qos"
syscon10 at mainbus0: "qos"
syscon11 at mainbus0: "qos"
syscon12 at mainbus0: "qos"
syscon13 at mainbus0: "qos"
syscon14 at mainbus0: "qos"
syscon15 at mainbus0: "qos"
syscon16 at mainbus0: "qos"
syscon17 at mainbus0: "qos"
syscon18 at mainbus0: "qos"
syscon19 at mainbus0: "qos"
syscon20 at mainbus0: "qos"
syscon21 at mainbus0: "qos"
syscon22 at mainbus0: "qos"
syscon23 at mainbus0: "qos"
syscon24 at mainbus0: "qos"
syscon25 at mainbus0: "qos"
syscon26 at mainbus0: "qos"
syscon27 at mainbus0: "qos"
syscon28 at mainbus0: "qos"
syscon29 at mainbus0: "qos"
syscon30 at mainbus0: "qos"
syscon31 at mainbus0: "qos"
rkcomphy0 at mainbus0
rkcomphy1 at mainbus0
rkusbphy0 at mainbus0: phy 0
rkusbphy1 at mainbus0: phy 1
rkpinctrl0 at mainbus0: "pinctrl"
rkgpio0 at rkpinctrl0
rkgpio1 at rkpinctrl0
rkgpio2 at rkpinctrl0
rkgpio3 at rkpinctrl0
rkgpio4 at rkpinctrl0
syscon32 at mainbus0: "syscon"
syscon33 at mainbus0: "qos"
syscon34 at mainbus0: "qos"
syscon35 at mainbus0: "qos"
syscon36 at mainbus0: "syscon"
rkpciephy0 at mainbus0
rkcomphy2 at mainbus0
"fit-images" at mainbus0 not configured
"opp-table-0" at mainbus0 not configured
rkdrm0 at mainbus0
drm0 at rkdrm0
"firmware" at mainbus0 not configured
"opp-table-1" at mainbus0 not configured
simpleaudio0 at mainbus0
"pmu" at mainbus0 not configured
agtimer0 at mainbus0: 24000 kHz
"xin24m" at mainbus0 not configured
"xin32k" at mainbus0 not configured
ahci0 at mainbus0: AHCI 1.3
ahci0: port 0: 6.0Gb/s
scsibus0 at ahci0: 32 targets
sd0 at scsibus0 targ 0 lun 0: <ATA, WDC WDS200T2B0B, 4110> naa.5001b444a777478f
sd0: 1907729MB, 512 bytes/sector, 3907029168 sectors, thin
xhci0 at mainbus0, xHCI 1.10
usb0 at xhci0: USB revision 3.0
uhub0 at usb0 configuration 1 interface 0 "Generic xHCI root hub" rev 3.00/1.00 addr 1
xhci1 at mainbus0, xHCI 1.10
usb1 at xhci1: USB revision 3.0
uhub1 at usb1 configuration 1 interface 0 "Generic xHCI root hub" rev 3.00/1.00 addr 1
ehci0 at mainbus0
usb2 at ehci0: USB revision 2.0
uhub2 at usb2 configuration 1 interface 0 "Generic EHCI root hub" rev 2.00/1.00 addr 1
ohci0 at mainbus0: version 1.0
ehci1 at mainbus0
usb3 at ehci1: USB revision 2.0
uhub3 at usb3 configuration 1 interface 0 "Generic EHCI root hub" rev 2.00/1.00 addr 1
ohci1 at mainbus0: version 1.0
rkiic0 at mainbus0
iic0 at rkiic0
rkpmic0 at iic0 addr 0x20: RK809
"gpu" at mainbus0 not configured
"video-codec" at mainbus0 not configured
"iommu" at mainbus0 not configured
"video-codec" at mainbus0 not configured
"iommu" at mainbus0 not configured
dwqe0 at mainbus0 gmac 1: rev 0x00, address 5a:29:04:e3:56:68
rgephy0 at dwqe0 phy 0: RTL8169S/8110S/8211 PHY, rev. 6
"vop" at mainbus0 not configured
"iommu" at mainbus0 not configured
"hdmi" at mainbus0 not configured
dwmmc0 at mainbus0: 50 MHz base clock
sdmmc0 at dwmmc0: 4-bit, sd high-speed, dma
dwmshc0 at mainbus0
dwmshc0: SDHC 6.0, 200 MHz base clock
sdmmc1 at dwmshc0: 8-bit, sd high-speed, mmc high-speed, dma
"i2s" at mainbus0 not configured
"dma-controller" at mainbus0 not configured
"dma-controller" at mainbus0 not configured
rkiic1 at mainbus0
iic1 at rkiic1
pcxrtc0 at iic1 addr 0x51: battery ok
"watchdog" at mainbus0 not configured
com0 at mainbus0: dw16550, 64 byte fifo
com0: console
rktemp0 at mainbus0
"saradc" at mainbus0 not configured
rkpwm0 at mainbus0rkclock_get_frequency(rkclock1, 349)
: no clock
rkrng0 at mainbus0: ver 2
dwpcie0 at mainbus0
dwpcie1 at mainbus0
dwqe1 at mainbus0 gmac 0: rev 0x00, address 5a:29:04:e3:56:69
dwmdio0 at mainbus0
mtsw0 at dwmdio0 addr 31: MT7531 rev 1
gpioleds0 at mainbus0: "status", "power"
"dc-12v-regulator" at mainbus0 not configured
"hdmi-con" at mainbus0 not configured
"ir-receiver" at mainbus0 not configured
"vcc3v3-sys-regulator" at mainbus0 not configured
"vcc5v0-sys-regulator" at mainbus0 not configured
"pcie30-avdd0v9-regulator" at mainbus0 not configured
"pcie30-avdd1v8-regulator" at mainbus0 not configured
"vcc3v3-pi6c-05-regulator" at mainbus0 not configured
"vcc3v3-minipcie-regulator" at mainbus0 not configured
"vcc3v3-ngff-regulator" at mainbus0 not configured
"vcc5v0-usb-regulator" at mainbus0 not configured
"vcc5v0-usb-host-regulator" at mainbus0 not configured
"vcc5v0-usb-otg-regulator" at mainbus0 not configured
"binman" at mainbus0 not configured
"dmc" at mainbus0 not configured
"nvmem" at mainbus0 not configured
"smbios" at mainbus0 not configured
usb4 at ohci0: USB revision 1.0
uhub4 at usb4 configuration 1 interface 0 "Generic OHCI root hub" rev 1.00/1.00 addr 1
usb5 at ohci1: USB revision 1.0
uhub5 at usb5 configuration 1 interface 0 "Generic OHCI root hub" rev 1.00/1.00 addr 1
dwpcie0: can't initialize hardware
dwpcie1: can't initialize hardware
scsibus1 at sdmmc0: 2 targets, initiator 0
sd1 at scsibus1 targ 1 lun 0: <Sandisk, SC16G, 0080> removable
sd1: 15193MB, 512 bytes/sector, 31116288 sectors
mtsport0 at mtsw0 port 1: address fe:e1:ba:d0:cf:d9
ukphy0 at mtsport0 phy 1: Generic IEEE 802.3u media interface, rev. 1: OUI 0x00e8a5, model 0x0004
mtsport1 at mtsw0 port 2: address fe:e1:ba:d1:35:15
ukphy1 at mtsport1 phy 2: Generic IEEE 802.3u media interface, rev. 1: OUI 0x00e8a5, model 0x0004
mtsport2 at mtsw0 port 3: address fe:e1:ba:d2:0b:a1
ukphy2 at mtsport2 phy 3: Generic IEEE 802.3u media interface, rev. 1: OUI 0x00e8a5, model 0x0004
mtsport3 at mtsw0 port 4: address fe:e1:ba:d3:df:9d
ukphy3 at mtsport3 phy 4: Generic IEEE 802.3u media interface, rev. 1: OUI 0x00e8a5, model 0x0004
mtsw0: dwqe1 at port 5
scsibus2 at sdmmc1: 2 targets, initiator 0
sd2 at scsibus2 targ 1 lun 0: <Samsung, AJTD4R, 0000>
sd2: 14910MB, 512 bytes/sector, 30535680 sectors
ure0 at uhub1 port 2 configuration 1 interface 0 "Realtek USB 10/100/1G/2.5G LAN" rev 3.20/31.00 addr 2
ure0: RTL8156B (0x7410), address a0:ce:c8:f7:94:72
vscsi0 at root
scsibus3 at vscsi0: 256 targets
softraid0 at root
scsibus4 at softraid0: 256 targets
root on sd1a (4d451033c2c69cc9.a) swap on sd1b dump on sd1b
rkdrm0: no display interface ports configured

you need "up" in /etc/hostname.dwqe1 before you can move packets
on the switch ports. ifconfig looks like this:

dlg@bpi ~$ ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768
        index 4 priority 0 llprio 3
        groups: lo
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x4
        inet 127.0.0.1 netmask 0xff000000
dwqe0: flags=8802<BROADCAST,SIMPLEX,MULTICAST> mtu 1500
        lladdr 5a:29:04:e3:56:68
        description: wan
        index 1 priority 0 llprio 3
        media: Ethernet autoselect (none)
        status: no carrier
dwqe1: flags=8b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500
        lladdr 5a:29:04:e3:56:69
        index 2 priority 0 llprio 3
        trunk: trunkdev mtsw0
        status: active
enc0: flags=0<>
        index 3 priority 0 llprio 3
        groups: enc
        status: active
mtsport0: flags=8002<BROADCAST,MULTICAST> mtu 1500
        lladdr fe:e1:ba:d0:cf:d9
        description: lan0
        index 5 priority 0 llprio 3
        media: Ethernet autoselect (none)
        status: no carrier
mtsport1: flags=808043<UP,BROADCAST,RUNNING,MULTICAST,AUTOCONF4> mtu 1500
        lladdr fe:e1:ba:d1:84:62
        description: lan1
        index 6 priority 0 llprio 3
        groups: egress
        media: Ethernet autoselect (1000baseT full-duplex)
        status: active
        inet 10.0.127.197 netmask 0xffffff00 broadcast 10.0.127.255
mtsport2: flags=1008043<UP,BROADCAST,RUNNING,MULTICAST,MONITOR> mtu 1500
        lladdr fe:e1:ba:d2:0b:a1
        description: lan2
        index 7 priority 0 llprio 3
        media: Ethernet autoselect (none)
        status: no carrier
mtsport3: flags=8002<BROADCAST,MULTICAST> mtu 1500
        lladdr fe:e1:ba:d3:df:9d
        description: lan3
        index 8 priority 0 llprio 3
        media: Ethernet autoselect (none)
        status: no carrier
ure0: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,MONITOR> mtu 1500
        lladdr a0:ce:c8:f7:94:72
        index 9 priority 0 llprio 3
        media: Ethernet autoselect
        status: no carrier
pflog0: flags=141<UP,RUNNING,PROMISC> mtu 33136
        index 10 priority 0 llprio 3
        groups: pflog

you use mtsport interfaces like you would any other ethernet interface
in openbsd. that also means all packets are processed in software
on the cpu, even if they're moving between mtsport interfaces.

here's the diff. it almost certainly does not apply to current any
more though.

Index: fdt/dwmdio.c
===================================================================
RCS file: fdt/dwmdio.c
diff -N fdt/dwmdio.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fdt/dwmdio.c	24 Apr 2023 02:08:20 -0000
@@ -0,0 +1,102 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/mutex.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/fdt/dwmdiovar.h>
+#include <dev/fdt/mdiovar.h>
+
+/*
+ * the dwmdio node is a child of an ethernet/gmac node in the device
+ * tree. this grafts the dwmdio kernel device to the gmac parent bus
+ * to avoid confusing kernel autoconf, which can't tell the difference
+ * between the mii devices we usually want to attach and this dwmdio
+ * device we sometimes want to attach.
+ */
+
+extern int simplebus_print(void *, const char *);
+
+struct device *
+dwmdio_found(struct device *dv, int node)
+{
+	char name[32];
+	struct fdt_attach_args faa = {
+		.fa_node = node,
+		.fa_name = name,
+	};
+
+	OF_getpropstr(node, "name", name, sizeof(name));
+
+	return (config_found(dv->dv_parent, &faa, simplebus_print));
+}
+
+struct dwmdio_softc {
+	struct device		sc_dev;
+};
+#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname)
+
+static int		dwmdio_match(struct device *, void *, void *);
+static void		dwmdio_attach(struct device *, struct device *, void *);
+
+struct cfdriver dwmdio_cd = {
+	NULL, "dwmdio", DV_DULL
+};
+
+const struct cfattach dwmdio_ca = {
+	sizeof (struct dwmdio_softc), dwmdio_match, dwmdio_attach,
+};
+
+static int
+dwmdio_match(struct device *parent, void *cfdata, void *aux)
+{
+	struct fdt_attach_args *faa = aux;
+
+	return (OF_is_compatible(faa->fa_node, "snps,dwmac-mdio"));
+}
+
+static void
+dwmdio_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct fdt_attach_args *faa = aux;
+	int node;
+	struct mii_bus *mii;
+
+	/* find the mii ops the "parent" gmac device provides */
+	node = OF_parent(faa->fa_node);
+	mii = mii_bynode(node);
+	if (mii == NULL) {
+		printf(": mdio operations not found\n");
+		return;
+	}
+
+	printf("\n");
+
+	mdio_attach(self, mii, faa->fa_node);
+}
Index: fdt/dwmdiovar.h
===================================================================
RCS file: fdt/dwmdiovar.h
diff -N fdt/dwmdiovar.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fdt/dwmdiovar.h	24 Apr 2023 02:08:20 -0000
@@ -0,0 +1,19 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct device	*dwmdio_found(struct device *, int);
Index: fdt/files.fdt
===================================================================
RCS file: /cvs/src/sys/dev/fdt/files.fdt,v
retrieving revision 1.186
diff -u -p -r1.186 files.fdt
--- fdt/files.fdt	19 Apr 2023 00:19:17 -0000	1.186
+++ fdt/files.fdt	24 Apr 2023 02:08:20 -0000
@@ -3,6 +3,7 @@
 # Config file and device description for machine-independent FDT code.
 # Included by ports that need it.
 
+define	mdio {}
 define	spmi {}
 
 device	iicmux: i2cbus
@@ -216,6 +217,11 @@ file	dev/fdt/if_dwge.c		dwge
 attach	dwqe at fdt with dwqe_fdt
 file	dev/fdt/if_dwqe_fdt.c		dwqe_fdt
 
+# Synopsis Designware MAC MDIO
+device	dwmdio: mdio
+attach	dwmdio at fdt
+file	dev/fdt/dwmdio.c		dwmdio needs-flag
+
 attach	ehci at fdt with ehci_fdt
 file	dev/fdt/ehci_fdt.c		ehci_fdt
 
@@ -475,7 +493,7 @@ device	mvpinctrl
 attach	mvpinctrl at fdt
 file	dev/fdt/mvpinctrl.c		mvpinctrl
 
-device	mvmdio: fdt
+device	mvmdio: mdio
 attach	mvmdio at fdt
 file	dev/fdt/mvmdio.c		mvmdio
 
@@ -501,10 +519,6 @@ device	mvspi: spi
 attach	mvspi at fdt
 file	dev/fdt/mvspi.c			mvspi
 
-device	mvsw
-attach	mvsw at fdt
-file	dev/fdt/mvsw.c			mvsw
-
 device	mvtemp
 attach	mvtemp at fdt
 file	dev/fdt/mvtemp.c		mvtemp
@@ -720,3 +734,20 @@ file	dev/fdt/qcrtc.c			qcrtc
 device	tipd
 attach	tipd at i2c
 file	dev/fdt/tipd.c		tipd
+
+# mdio support
+
+file	dev/fdt/mdio.c			mdio
+
+device	mvsw {}
+attach	mvsw at mdio
+device	mvsport: ether, ifnet, mii, ifmedia
+attach	mvsport at mvsw
+file	dev/fdt/mvsw.c			mvsw | mvsport
+
+# Mediatek MT7531 switch
+device	mtsw {}
+attach	mtsw at mdio
+device	mtsport: ether, ifnet, mii, ifmedia
+attach	mtsport at mtsw
+file	dev/fdt/mtsw.c			mtsw | mtsport
Index: fdt/if_dwqe_fdt.c
===================================================================
RCS file: /cvs/src/sys/dev/fdt/if_dwqe_fdt.c,v
retrieving revision 1.11
diff -u -p -r1.11 if_dwqe_fdt.c
--- fdt/if_dwqe_fdt.c	24 Apr 2023 01:33:32 -0000	1.11
+++ fdt/if_dwqe_fdt.c	24 Apr 2023 02:08:20 -0000
@@ -21,6 +21,7 @@
  */
 
 #include "bpfilter.h"
+#include "dwmdio.h"
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -51,6 +52,10 @@
 #include <dev/mii/mii.h>
 #include <dev/mii/miivar.h>
 
+#if NDWMDIO > 0
+#include <dev/fdt/dwmdiovar.h>
+#endif
+
 #if NBPFILTER > 0
 #include <net/bpf.h>
 #endif
@@ -236,13 +241,31 @@ dwqe_fdt_attach(struct device *parent, s
 	if (sc->sc_ih == NULL)
 		printf("%s: can't establish interrupt\n", sc->sc_dev.dv_xname);
 
+	OF_getpropstr(faa->fa_node, "label",
+	    ifp->if_description, sizeof(ifp->if_description));
+
 	sc->sc_ifd.if_node = faa->fa_node;
 	sc->sc_ifd.if_ifp = ifp;
 	if_register(&sc->sc_ifd);
 
-	/* force a configuraton of the clocks/mac */
-	if (sc->sc_fixed_link)
+	if (sc->sc_fixed_link) {
+		/* force a configuraton of the clocks/mac */
 		sc->sc_mii.mii_statchg(self);
+
+#if NDWMDIO > 0
+		node = OF_getnodebyname(sc->sc_node, "mdio");
+		if (node == 0)
+			return;
+
+		sc->sc_mdio.md_node = sc->sc_node;
+		sc->sc_mdio.md_cookie = self;
+		sc->sc_mdio.md_readreg = dwqe_mii_readreg;
+		sc->sc_mdio.md_writereg = dwqe_mii_writereg;
+		mii_register(&sc->sc_mdio);
+
+		dwmdio_found(self, node);
+#endif
+	}
 }
 
 void
Index: fdt/if_mvneta.c
===================================================================
RCS file: /cvs/src/sys/dev/fdt/if_mvneta.c,v
retrieving revision 1.30
diff -u -p -r1.30 if_mvneta.c
--- fdt/if_mvneta.c	13 Apr 2023 02:19:05 -0000	1.30
+++ fdt/if_mvneta.c	24 Apr 2023 02:08:20 -0000
@@ -808,6 +808,9 @@ mvneta_attach_deferred(struct device *se
 	if_attach(ifp);
 	ether_ifattach(ifp);
 
+	OF_getpropstr(sc->sc_node, "label",
+	    ifp->if_description, sizeof(ifp->if_description));
+
 	sc->sc_ifd.if_node = sc->sc_node;
 	sc->sc_ifd.if_ifp = ifp;
 	if_register(&sc->sc_ifd);
Index: fdt/mdio.c
===================================================================
RCS file: fdt/mdio.c
diff -N fdt/mdio.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fdt/mdio.c	24 Apr 2023 02:08:20 -0000
@@ -0,0 +1,72 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/mutex.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/fdt/mdiovar.h>
+
+static int	mdio_print(void *, const char *);
+
+void
+mdio_attach(struct device *self, struct mii_bus *mii, int bnode)
+{
+	struct mdio_attach_args maa;
+	char name[32];
+	int addr;
+	int node;
+
+	for (node = OF_child(bnode); node != 0; node = OF_peer(node)) {
+		addr = OF_getpropint(node, "reg", -1);
+		if (addr == -1)
+			continue;
+
+		OF_getpropstr(node, "name", name, sizeof(name));
+
+		memset(&maa, 0, sizeof(maa));
+		maa.maa_bus = mii;
+		maa.maa_addr = addr;
+		maa.maa_name = name;
+		maa.maa_node = node;
+
+		config_found(self, &maa, mdio_print);
+	}
+}
+
+static int
+mdio_print(void *aux, const char *pnp)
+{
+	struct mdio_attach_args *maa = aux;
+
+	if (pnp != NULL)
+		printf("\"%s\" at %s", maa->maa_name, pnp);
+	printf(" addr %d", maa->maa_addr);
+
+	return (UNCONF);
+}
Index: fdt/mdiovar.h
===================================================================
RCS file: fdt/mdiovar.h
diff -N fdt/mdiovar.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fdt/mdiovar.h	24 Apr 2023 02:08:20 -0000
@@ -0,0 +1,27 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct mdio_attach_args {
+	struct mii_bus	*maa_bus;
+	const char	*maa_bname;
+	int		 maa_node;
+	int		 maa_addr;
+	char		*maa_name;
+};
+
+void	mdio_attach(struct device *, struct mii_bus *, int);
Index: fdt/mtsw.c
===================================================================
RCS file: fdt/mtsw.c
diff -N fdt/mtsw.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fdt/mtsw.c	24 Apr 2023 02:08:20 -0000
@@ -0,0 +1,1766 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Mediatek MT7531 switch driver
+ */
+
+#include "bpfilter.h"
+#include "kstat.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/mutex.h>
+#include <sys/rwlock.h>
+#include <sys/percpu.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+#include <dev/fdt/mdiovar.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+
+/* for mtsw_p_ioctl trunk handling */
+#include <crypto/siphash.h> /* if_trunk.h uses siphash bits */
+#include <net/if_trunk.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#if NKSTAT > 0
+#include <sys/kstat.h>
+#endif
+
+#define MTSW_PHY_ADDR_MASK		0x1f /* host talking to mtsw */
+
+#define MTSW_PORT_MIN			0
+#define MTSW_PORT_MAX			6
+#define MTSW_PORT_COUNT			7
+
+#define MTSW_PORT_PHY_MIN		0
+#define MTSW_PORT_PHY_MAX		4
+#define MTSW_PORT_PHY_COUNT		5
+
+#define MTSW_PORT_EXT			5
+#define MTSW_PORT_CPU			6
+
+/*
+ * actual mtsw register accesses involve a bunch of mdio ops
+ */
+
+#define MTSW_MDIO_HI			0x10
+#define MTSW_MDIO_PAGE_REG		0x1f
+
+#define MTSW_MDIO_PAGE_SHIFT		6
+#define MTSW_MDIO_PAGE_MASK		0x3ff
+#define MTSW_MDIO_REG_SHIFT		2
+#define MTSW_MDIO_REG_MASK		0xf
+
+/*
+ * registers
+ */
+
+/* Address Resolution Logic (ARL) Registers */
+#define MTSW_R_ATC			0x0080 /* Address Table Control */
+#define  MTSW_R_ATC_AC_CMD_MASK			(0x7 << 0)
+#define  MTSW_R_ATC_AC_CMD_READ			(0x0 << 0)
+#define  MTSW_R_ATC_AC_CMD_WRITE		(0x1 << 0)
+#define  MTSW_R_ATC_AC_CMD_CLEAN		(0x2 << 0)
+#define  MTSW_R_ATC_AC_CMD_SEARCH_START		(0x4 << 0)
+#define  MTSW_R_ATC_AC_CMD_SEARCH_NEXT		(0x5 << 0)
+#define  MTSW_R_ATC_AC_CAE			(1U << 3)
+#define  MTSW_R_ATC_AC_SAT			(0x3 << 4)
+#define  MTSW_R_ATC_AC_SAT_MAC			(0x0 << 4)
+#define  MTSW_R_ATC_AC_SAT_DIP			(0x1 << 4)
+#define  MTSW_R_ATC_AC_SAT_SIP			(0x2 << 4)
+#define  MTSW_R_ATC_AC_SAT_ADDRESS		(0x3 << 4)
+#define  MTSW_R_ATC_AC_MAT			(0xf << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_MAC		(0x0 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_DIP_GA		(0x1 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_SIP		(0x2 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_VALID		(0x3 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_DYN_MAC		(0x4 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_DYN_DIP		(0x5 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_STATIC_MAC	(0x6 << 8)
+#define  MTSW_R_ATC_AC_MAT_ALL_STATIC_DIP	(0x7 << 8)
+#define  MTSW_R_ATC_ADDR_INVLD			(1U << 12)
+#define  MTSW_R_ATC_SRCH_HIT			(1U << 13)
+#define  MTSW_R_ATC_SRCH_END			(1U << 14)
+#define  MTSW_R_ATC_BUSY			(1U << 15)
+#define  MTSW_R_ATC_ADDR_SHIFT			16
+#define  MTSW_R_ATC_ADDR_MASK			0xfff
+#define  MTSW_R_ATC_COL_ACC			(1U << 28)
+#define MTSW_R_VTCR			0x0090 /* VLAN Table Control */
+#define  MTSW_R_VTCR_VID_SHIFT			0
+#define  MTSW_R_VTCR_VID_MASK			0xfff
+#define  MTSW_R_VTCR_FUNC_MASK			(0xf << 12)
+#define  MTSW_R_VTCR_FUNC_READ_VID		(0x0 << 12)
+#define  MTSW_R_VTCR_FUNC_WRITE_VID		(0x1 << 12)
+#define  MTSW_R_VTCR_IDX_INVLD			(1U << 16)
+#define  MTSW_R_VTCR_BUSY			(1U << 31)
+#define MTSW_R_VAWD1			0x0094 /* VLAN and ACL Write Data I */
+#define MTSW_R_VAWD2			0x0098 /* VLAN and ACL Write Data II */
+
+/* Address Resolution Logic (ARL) Port-Based Registers */
+#define MTSW_R_SSC(_p)			(0x2000 + (_p) * 0x100)
+#define MTSW_R_PCR(_p)			(0x2004 + (_p) * 0x100)
+#define  MTSW_R_PCR_PORT_VLAN_MASK		(0x3 << 0)
+#define  MTSW_R_PCR_PORT_VLAN_MATRIX		(0x0 << 0)
+#define  MTSW_R_PCR_PORT_VLAN_FALLBACK		(0x1 << 0)
+#define  MTSW_R_PCR_PORT_VLAN_CHECK		(0x2 << 0)
+#define  MTSW_R_PCR_PORT_VLAN_SECURITY		(0x3 << 0)
+#define  MTSW_R_PCR_VLAN_MIS			(1U << 2)
+#define  MTSW_R_PCR_MIS_PORT_FW_MASK		(0x7 << 4)
+#define  MTSW_R_PCR_MIS_PORT_FW_DFLT		(0x0 << 4)
+#define  MTSW_R_PCR_MIS_PORT_FW_DFLT_CPU_EXC	(0x4 << 4)
+#define  MTSW_R_PCR_MIS_PORT_FW_DFLT_CPU_INC	(0x5 << 4)
+#define  MTSW_R_PCR_MIS_PORT_FW_CPU_ONLY	(0x6 << 4)
+#define  MTSW_R_PCR_MIS_PORT_FW_DROP		(0x7 << 4)
+#define  MTSW_R_PCR_ACL_MIR			(1U << 7)
+#define  MTSW_R_PCR_PORT_RX_MIR			(1U << 8)
+#define  MTSW_R_PCR_PORT_TX_MIR			(1U << 9)
+#define  MTSW_R_PCR_ACL_EN			(1U << 10)
+#define  MTSW_R_PCR_UP2TAG_EN			(1U << 11)
+#define  MTSW_R_PCR_UP2DSCP_EN			(1U << 12)
+#define  MTSW_R_PCR_PORT_MATRIX_SHIFT		16
+#define  MTSW_R_PCR_PORT_MATRIX_MASK		0xff
+#define  MTSW_R_PCR_PORT_PRI_SHIFT		24
+#define  MTSW_R_PCR_PORT_PRI_MASK		0x7
+#define  MTSW_R_PCR_EG_TAG_MASK			(0x3 << 28)
+#define  MTSW_R_PCR_EG_TAG_UNTAGGED		(0x0 << 28)
+#define  MTSW_R_PCR_EG_TAG_SWAP			(0x1 << 28)
+#define  MTSW_R_PCR_EG_TAG_TAGGED		(0x2 << 28)
+#define  MTSW_R_PCR_EG_TAG_STACK		(0x3 << 28)
+#define  MTSW_R_PCR_MDLV2_EN			(1U << 30)
+
+#define MTSW_R_PSC(_p)			(0x200c + (_p) * 0x100)
+#define  MTSW_R_PSC_RX_PORT_LOCK		(1U << 0)
+#define  MTSW_R_PSC_TX_PORT_LOCK		(1U << 1)
+#define  MTSW_R_PSC_SA_LOCK_MASK		(0x3 << 2)
+#define  MTSW_R_PSC_SA_DIS			(1U << 4)
+#define  MTSW_R_PSC_SA_CNT_EN			(1U << 5)
+#define  MTSW_R_PSC_MAC_SA_LRN_SHIFT		8
+#define  MTSW_R_PSC_MAC_SA_LRN_MASK		0xfff
+#define  MTSW_R_PSC_MAC_SA_LRN_DIS		0x0
+#define  MTSW_R_PSC_MAC_SA_LRN_UNLIMITED	0xfff
+#define  MTSW_R_PSC_SA_LRN_CNT_SHIFT		20
+#define MTSW_R_PVC(_p)			(0x2010 + (_p) * 0x100)
+#define  MTSW_R_PVC_ACC_FRM_MASK		(0x3 << 0)
+#define  MTSW_R_PVC_ACC_FRM_MASK_ALL		(0x0 << 0)
+#define  MTSW_R_PVC_ACC_FRM_MASK_ONLY_VLAN	(0x1 << 0)
+#define  MTSW_R_PVC_ACC_FRM_MASK_ONLY_VLAN_PRI	(0x2 << 0)
+#define  MTSW_R_PVC_UC_LKYV_EN			(1U << 2)
+#define  MTSW_R_PVC_MC_LKYV_EN			(1U << 3)
+#define  MTSW_R_PVC_IPM_LKYV_EN			(1U << 4)
+#define  MTSW_R_PVC_PORT_STAG			(1U << 5)
+#define  MTSW_R_PVC_VLAN_ATTR_MASK		(0x3 << 6)
+#define  MTSW_R_PVC_VLAN_ATTR_USER_PORT		(0x0 << 6)
+#define  MTSW_R_PVC_VLAN_ATTR_STACK_PORT	(0x1 << 6)
+#define  MTSW_R_PVC_VLAN_ATTR_XLAT_PORT		(0x2 << 6)
+#define  MTSW_R_PVC_VLAN_ATTR_TRANS_PORT	(0x3 << 6)
+#define  MTSW_R_PVC_EG_TAG_MASK			(0x7 << 8)
+#define  MTSW_R_PVC_EG_TAG_CONSISTENT		(0x0 << 8)
+#define  MTSW_R_PVC_EG_TAG_UNTAGGED		(0x4 << 8)
+#define  MTSW_R_PVC_EG_TAG_SWAP			(0x5 << 8)
+#define  MTSW_R_PVC_EG_TAG_TAGGED		(0x6 << 8)
+#define  MTSW_R_PVC_EG_TAG_STACK		(0x7 << 8)
+#define  MTSW_R_PVC_PT_OPTION			(1U << 11)
+#define  MTSW_R_PVC_BC_LKYV_EN			(1U << 13)
+#define  MTSW_R_PVC_FORCE_PVID			(1U << 14)
+#define  MTSW_R_PVC_DIS_PVID			(1U << 15)
+#define  MTSW_R_PVC_STAG_VPID_MASK		0xffff
+#define  MTSW_R_PVC_STAG_VPID_SHIFT		16
+
+#define MTSW_R_PPBV1(_p)		(0x2014 + (_p) * 0x100)
+#define  MTSW_R_PPBV1_G0_PORT_VID_SHIFT		0
+
+#define MTSW_R_PPBV1(_p)		(0x2014 + (_p) * 0x100)
+#define  MTSW_R_PPBV1_G0_PORT_VID_SHIFT		0
+#define  MTSW_R_PPBV1_G0_PORT_VID_MASK		0xfff
+#define  MTSW_R_PPBV1_G0_PORT_PRI_SHIFT		13
+#define  MTSW_R_PPBV1_G0_PORT_PRI_MASK		0x7
+#define  MTSW_R_PPBV1_G1_PORT_VID_SHIFT		16
+#define  MTSW_R_PPBV1_G1_PORT_VID_MASK		0xfff
+#define  MTSW_R_PPBV1_G1_PORT_PRI_SHIFT		29
+#define  MTSW_R_PPBV1_G1_PORT_PRI_MASK		0x7
+
+/* MAC */
+#define MTSW_R_PMCR(_p)			(0x3000 + (_p) * 0x100)
+#define  MTSW_R_PMCR_FORCE_LINK_MASK		(0x1 << 0)
+#define  MTSW_R_PMCR_FORCE_LINK_DOWN		(0x0 << 0)
+#define  MTSW_R_PMCR_FORCE_LINK_UP		(0x1 << 0)
+#define  MTSW_R_PMCR_FORCE_DPX_MASK		(0x1 << 1)
+#define  MTSW_R_PMCR_FORCE_DPX_HALF		(0x0 << 1)
+#define  MTSW_R_PMCR_FORCE_DPX_FULL		(0x1 << 1)
+#define  MTSW_R_PMCR_FORCE_SPD_MASK		(0x3 << 2)
+#define  MTSW_R_PMCR_FORCE_SPD_10M		(0x0 << 2)
+#define  MTSW_R_PMCR_FORCE_SPD_100M		(0x1 << 2)
+#define  MTSW_R_PMCR_FORCE_SPD_1000M		(0x2 << 2)
+#define  MTSW_R_PMCR_FORCE_TX_FC		(0x1 << 4)
+#define  MTSW_R_PMCR_FORCE_RX_FC		(0x1 << 5)
+#define  MTSW_R_PMCR_FORCE_EEE100		(0x1 << 6)
+#define  MTSW_R_PMCR_FORCE_EEE1G		(0x1 << 7)
+#define  MTSW_R_PMCR_BACKPR_EN			(0x1 << 8)
+#define  MTSW_R_PMCR_BKOFF_EN			(0x1 << 9)
+#define  MTSW_R_PMCR_MAC_PRE			(0x1 << 11)
+#define  MTSW_R_PMCR_MAC_RX_EN			(0x1 << 13)
+#define  MTSW_R_PMCR_MAC_TX_EN			(0x1 << 14)
+#define  MTSW_R_PMCR_MAC_MODE_MASK		(0x1 << 16)
+#define  MTSW_R_PMCR_MAC_MODE_PHY		(0x0 << 16)
+#define  MTSW_R_PMCR_MAC_MODE_MAC		(0x1 << 16)
+#define  MTSW_R_PMCR_EXT_PHY			(0x1 << 17)
+#define  MTSW_R_PMCR_IPG_CFG_MASK		(0x3 << 18)
+#define  MTSW_R_PMCR_IPG_CFG_NORMAL		(0x0 << 18)
+#define  MTSW_R_PMCR_IPG_CFG_SHORT_RND		(0x1 << 18)
+#define  MTSW_R_PMCR_IPG_CFG_SHRINK		(0x2 << 18)
+#define  MTSW_R_PMCR_FORCE_MODE_EEE1G		(0x1 << 25)
+#define  MTSW_R_PMCR_FORCE_MODE_EEE100		(0x1 << 26)
+#define  MTSW_R_PMCR_FORCE_MODE_TX_FC		(0x1 << 27)
+#define  MTSW_R_PMCR_FORCE_MODE_RX_FC		(0x1 << 28)
+#define  MTSW_R_PMCR_FORCE_MODE_DPX		(0x1 << 29)
+#define  MTSW_R_PMCR_FORCE_MODE_SPD		(0x1 << 30)
+#define  MTSW_R_PMCR_FORCE_MODE_LNK		(0x1 << 31)
+#define MTSW_R_PMEEECR(_p)		(0x3004 + (_p) * 0x100)
+#define MTSW_R_PMSR(_p)			(0x3008 + (_p) * 0x100)
+#define MTSW_R_PINT_EN(_p)		(0x3010 + (_p) * 0x100)
+#define MTSW_R_PINT_STS(_p)		(0x3014 + (_p) * 0x100)
+#define MTSW_R_DBG_CNT(_p)		(0x3018 + (_p) * 0x100)
+#define  MTSW_R_DBG_CNT_DIS_CLR			(1U << 31)
+#define MTSW_R_WOL(_p)			(0x3020 + (_p) * 0x100)
+#define MTSW_R_PFC_STS(_p)		(0x3024 + (_p) * 0x100)
+#define MTSW_R_PFC_RX_PSON_CNT_L(_p)	(0x3030 + (_p) * 0x100)
+#define MTSW_R_PFC_RX_PSON_CNT_H(_p)	(0x3034 + (_p) * 0x100)
+#define MTSW_R_PFC_RX_PSOFF_CNT_L(_p)	(0x3038 + (_p) * 0x100)
+#define MTSW_R_PFC_RX_PSOFF_CNT_H(_p)	(0x303c + (_p) * 0x100)
+#define MTSW_R_PFC_TX_PSON_CNT_L(_p)	(0x3040 + (_p) * 0x100)
+#define MTSW_R_PFC_TX_PSON_CNT_H(_p)	(0x3044 + (_p) * 0x100)
+#define MTSW_R_PFC_TX_PSOFF_CNT_L(_p)	(0x3048 + (_p) * 0x100)
+#define MTSW_R_PFC_TX_PSOFF_CNT_H(_p)	(0x304c + (_p) * 0x100)
+
+#define MTSW_R_PFC_CTRL			0x30b0
+#define MTSW_R_PFC_AUTO_SYNC_DLY_SEL	0x30b4
+#define MTSW_R_SGMII_2P5G_SPD_CTRL	0x30b8
+#define MTSW_R_LPDET_CTRL		0x30c0
+#define MTSW_R_LPDET_SA_MSB		0x30c8
+#define MTSW_R_LPDET_SA_LSB		0x30cc
+#define MTSW_R_LPDET_RXSA_MSB		0x30d0
+#define MTSW_R_LPDET_RXSA_MSB		0x30d0
+#define MTSW_R_GMACCR			0x30e0
+#define  MTSW_R_GMACCR_MAX_RX_PKT_LEN_MASK	(0x3 << 0)
+#define  MTSW_R_GMACCR_MAX_RX_PKT_LEN_1518	(0x0 << 0)
+#define  MTSW_R_GMACCR_MAX_RX_PKT_LEN_1536	(0x1 << 0)
+#define  MTSW_R_GMACCR_MAX_RX_PKT_LEN_1552	(0x2 << 0)
+#define  MTSW_R_GMACCR_MAX_RX_PKT_LEN_JUMBO	(0x3 << 0)
+#define  MTSW_R_GMACCR_MAX_RX_JUMBO_MASK	(0xf << 2)
+#define  MTSW_R_GMACCR_MAX_RX_JUMBO(_k)		((_k) << 2)
+#define  MTSW_R_GMACCR_MTCC_LMT_MASK		(0xf << 9)
+#define  MTSW_R_GMACCR_MTCC_LMT_DISABLE		(0x0 << 9)
+#define  MTSW_R_GMACCR_MTCC_LMT(_l)		((_l) << 9)
+#define  MTSW_R_GMACCR_PRMBL_LMT_EN		(1U << 17)
+#define  MTSW_R_GMACCR_RXCRC_EN			(1U << 18)
+#define  MTSW_R_GMACCR_TXCRC_EN			(1U << 19)
+#define MTSW_R_SMACCR0			0x30e4
+#define MTSW_R_SMACCR1			0x30e8
+#define MTSW_R_CKGCR			0x30f0
+#define MTSW_R_GPINT_EN			0x30f4
+#define MTSW_R_GPINT_STS		0x30f8
+
+#define MTSW_R_MIB_BASE(_p)		(0x4000 + (_p) * 0x100)
+#define MTSW_R_AECNT(_ae)		(0x4f00 + (_ae) * 4) /* 0 .. 7 */
+#define MTSW_R_MIBCCR			0x4fe0 /* MIB Counter Control */
+#define  MTSW_R_MIBCCR_TX_OCT_CNT_BAD		(1U << 4)
+#define  MTSW_R_MIBCCR_TX_OCT_CNT_GOOD		(1U << 5)
+#define  MTSW_R_MIBCCR_RX_OCT_CNT_BAD		(1U << 6)
+#define  MTSW_R_MIBCCR_RX_OCT_CNT_GOOD		(1U << 7)
+#define  MTSW_R_MIBCCR_MIB_ENABLE		(1U << 31)
+#define MTSW_R_AECCR			0x4fe0 /* ARL Event Counter Control */
+
+#define MTSW_R_SYS_CTRL			0x7000 /* System Control */
+#define  MTSW_R_SYS_CTRL_SW_REG_RST		(1U << 0)
+#define  MTSW_R_SYS_CTRL_SW_SYS_RST		(1U << 1)
+#define  MTSW_R_SYS_CTRL_SW_PHY_RST		(1U << 2)
+#define  MTSW_R_SYS_CTRL_MBIST_EN		(1U << 4) /* "No used" */
+#define  MTSW_R_SYS_CTRL_MBIST_CMP		(1U << 5)
+#define  MTSW_R_SYS_CTRL_FL_BIST_STS		(1U << 6)
+#define  MTSW_R_SYS_CTRL_PL_BIST_STS		(1U << 7)
+#define  MTSW_R_SYS_CTRL_PB_BIST_STS		(1U << 8)
+#define  MTSW_R_SYS_CTRL_MIB_BIST_STS		(1U << 9)
+#define  MTSW_R_SYS_CTRL_VLN_BIST_STS		(1U << 10)
+#define  MTSW_R_SYS_CTRL_ADDR_BIST_STS		(1U << 11)
+#define  MTSW_R_SYS_CTRL_CTRL_BIST_STS		(1U << 12)
+#define  MTSW_R_SYS_CTRL_MASK_BIST_STS		(1U << 14)
+#define  MTSW_R_SYS_CTRL_COL_BIST_STS		(1U << 16)
+#define  MTSW_R_SYS_CTRL_VLAN_TAB_INIT		(1U << 20)
+#define  MTSW_R_SYS_CTRL_MAC_TAB_INIT		(1U << 21)
+#define  MTSW_R_SYS_CTRL_ACL_TAB_INIT		(1U << 22)
+
+#define MTSW_R_SYS_INT_EN		0x7008 /* System Interrupt Enable */
+#define MTSW_R_SYS_INT_STS		0x700c /* System Interrupt Status */
+#define MTSW_R_PMDC_CFG			0x7014 /* PMDC Control Register */
+#define MTSW_R_PHY_POLL			0x7018 /* PHY Polling + SMI Master */
+#define MTSW_R_PHY_IAC			0x701c /* PHY Indirect Access Control */
+#define  MTSW_R_PHY_IAC_MDIO_DATA_SHIFT		0
+#define  MTSW_R_PHY_IAC_MDIO_DATA_MASK		0xffff
+#define  MTSW_R_PHY_IAC_MDIO_ST_C45		(0x0 << 16)
+#define  MTSW_R_PHY_IAC_MDIO_ST_C22		(0x1 << 16)
+#define  MTSW_R_PHY_IAC_MDIO_CMD_C45_ADDR	(0x0 << 18)
+#define  MTSW_R_PHY_IAC_MDIO_CMD_WRITE		(0x1 << 18)
+#define  MTSW_R_PHY_IAC_MDIO_CMD_READ		(0x2 << 18)
+#define  MTSW_R_PHY_IAC_MDIO_CMD_READ_C45	(0x3 << 18)
+#define  MTSW_R_PHY_IAC_MDIO_PHY_ADDR(_a)	((_a) << 20)
+#define  MTSW_R_PHY_IAC_MDIO_REG_ADDR(_r)	((_r) << 25)
+#define  MTSW_R_PHY_IAC_PHY_ACS_ST		(1U << 31)
+#define MTSW_R_PSR_P3_P0		0x7020 /* PHY Status Reg for P3-P0 */
+#define MTSW_R_PSR_P6_P4		0x7024 /* PHY Status Reg for P6-P4 */
+#define MTSW_R_PAUSE_CAP_P6_P0		0x7028 /* MAC Pause TX/RX Cap */
+#define MTSW_R_EEPR_IND			0x7120 /* EEPROM Indirect Access */
+#define MTSW_R_EEPR_STS			0x7124 /* EEPROM Indirect Status */
+#define MTSW_R_EEPR_IND_ADDR		0x7128 /* EEPROM Indirect Addres */
+
+#define MTSW_R_CLKGEN_CTRL		0x7500
+#define MTSW_R_CLKGEN_CTRL_EN			(1 << 0)
+#define MTSW_R_CLKGEN_CTRL_MODE_MASK		(0x3 << 1)
+#define MTSW_R_CLKGEN_CTRL_MODE_RGMII		(0x0 << 1)
+#define MTSW_R_CLKGEN_CTRL_MODE_MII		(0x1 << 1)
+#define MTSW_R_CLKGEN_CTRL_MODE_REV_MII		(0x2 << 1)
+#define MTSW_R_CLKGEN_CTRL_NO_REVERSE		(1 << 4)
+#define MTSW_R_CLKGEN_CTRL_NO_DELAY		(1 << 5)
+#define MTSW_R_CLKGEN_CTRL_SKEW_IN_MASK		(0x3 << 6)
+#define MTSW_R_CLKGEN_CTRL_SKEW_IN_NO_CHANGE	(0x0 << 6)
+#define MTSW_R_CLKGEN_CTRL_SKEW_IN_100PPS	(0x1 << 6)
+#define MTSW_R_CLKGEN_CTRL_SKEW_IN_200PPS	(0x2 << 6)
+#define MTSW_R_CLKGEN_CTRL_SKEW_IN_REVERSE	(0x3 << 6)
+#define MTSW_R_CLKGEN_CTRL_SKEW_OUT_MASK	(0x3 << 8)
+#define MTSW_R_CLKGEN_CTRL_SKEW_OUT_NO_CHANGE	(0x0 << 8)
+#define MTSW_R_CLKGEN_CTRL_SKEW_OUT_100PPS	(0x1 << 8)
+#define MTSW_R_CLKGEN_CTRL_SKEW_OUT_200PPS	(0x2 << 8)
+#define MTSW_R_CLKGEN_CTRL_SKEW_OUT_REVERSE	(0x3 << 8)
+
+/* TOP */
+#define MTSW_R_STRAP			0x7800 /* Strap Status */
+#define  MTSW_R_STRAP_TM_DIS			(1U << 0)
+#define  MTSW_R_STRAP_EEP_MODE			(1U << 1)
+#define  MTSW_R_STRAP_PON_LT			(1U << 2)
+#define  MTSW_R_STRAP_PLL_SW			(1U << 3)
+#define  MTSW_R_STRAP_EEE_DIS			(1U << 4)
+#define  MTSW_R_STRAP_EEP_DIS			(1U << 5)
+#define  MTSW_R_STRAP_PHY_EN			(1U << 6)
+#define  MTSW_R_STRAP_XTAL			(1U << 7)
+#define  MTSW_R_STRAP_XTAL25			(1U << 7)
+#define  MTSW_R_STRAP_XTAL40			(0U << 7)
+#define MTSW_R_SWSTRAP			0x7804 /* Software Strap Status */
+#define  MTSW_R_SWSTRAP_TM_DIS			(1U << 0)
+#define  MTSW_R_SWSTRAP_EEP_MODE		(1U << 1)
+#define  MTSW_R_SWSTRAP_EEP_MODE_LE16K		(0U << 1)
+#define  MTSW_R_SWSTRAP_EEP_MODE_GT16K		(1U << 1)
+#define  MTSW_R_SWSTRAP_PON_LT			(1U << 2)
+#define  MTSW_R_SWSTRAP_PLL_SW			(1U << 3)
+#define  MTSW_R_SWSTRAP_EEE_DIS			(1U << 4)
+#define  MTSW_R_SWSTRAP_EEP_DIS			(1U << 5)
+#define  MTSW_R_SWSTRAP_PHY_EN			(1U << 6)
+#define  MTSW_R_SWSTRAP_XTAL			(1U << 7)
+#define  MTSW_R_SWSTRAP_XTAL40			(0U << 7)
+#define  MTSW_R_SWSTRAP_XTAL25			(1U << 7)
+#define  MTSW_R_SWSTRAP_CHG_STRAP		(1U << 8)
+#define MTSW_R_SIG_SR			0x780c
+#define  MTSW_R_SIG_SR_PAD_MCM_SMI_EN		(1U << 0)
+#define  MTSW_R_SIG_SR_PAD_DUAL_SGMII_EN	(1U << 1)
+#define MTSW_R_LED_SRC			0x7818 /* LED Source Selection */
+#define MTSW_R_CREV			0x781c /* Chip Revision */
+#define  MTSW_R_CREV_NAME_SHIFT			16
+#define  MTSW_R_CREV_NAME_MASK			0xffff
+#define  MTSW_R_CREV_NAME_7531			0x7531
+#define  MTSW_R_CREV_REV_SHIFT			0
+#define  MTSW_R_CREV_REV_MASK			0xf
+#define MTSW_R_PLLGP_EN			0x7820
+#define  MTSW_R_PLLGP_EN_SW_CLKSW		(1 << 0)
+#define  MTSW_R_PLLGP_EN_SW_PLLGP		(1 << 1)
+#define  MTSW_R_PLLGP_EN_COREPLL_EN		(1 << 2)
+#define MTSW_R_PLLGP_CR0		0x7830
+#define  MTSW_R_PLLGP_CR0_SDM_PCW_CHG		(1 << 0)
+#define  MTSW_R_PLLGP_CR0_SDM_PCW_SHIFT		1
+#define  MTSW_R_PLLGP_CR0_SDM_PCW_MASK		0x1fffff
+#define  MTSW_R_PLLGP_CR0_EN			(1 << 22)
+#define  MTSW_R_PLLGP_CR0_POS_DIV_SHIFT		27
+#define  MTSW_R_PLLGP_CR0_POS_DIV_MASK		0x7
+
+#define MTSW_R_ANA_PLLGP_CR2		0x78b0
+#define MTSW_R_ANA_PLLGP_CR5		0x78bc
+
+/*
+ * VLAN Table
+ */
+#define MTSW_VT_VALID				(1U << 0)
+#define MTSW_VT_FID_MASK			(0x7 << 1)
+#define MTSW_VT_FID_DEFAULT			(0x0 << 1)
+#define MTSW_VT_FID_BRIDGED			(0x1 << 1)
+#define MTSW_VT_S_TAG1_SHIFT			4
+#define MTSW_VT_PORT_MEM_SHIFT			16
+#define MTSW_VT_PORT_MEM_MASK			0xff
+#define MTSW_VT_PORT_MEM_ALL			0xff
+#define MTSW_VT_USER_PRI_SHIFT			24
+#define MTSW_VT_USER_PRI_MASK			0x7
+#define MTSW_VT_COPY_PRI			(1U << 27)
+#define MTSW_VT_VTAG_EN				(1U << 28)
+#define MTSW_VT_EG_CON				(1U << 29)
+#define MTSW_VT_IVL_MAC				(1U << 30)
+#define MTSW_VT_PORT_STAG			(1U << 31)
+
+/*
+ * phy modes. note tricky bits.
+ */
+#define MTSW_PHY_UNKNOWN	(1U << 0)
+#define MTSW_PHY_RGMII		(1U << 1)
+#define MTSW_PHY_RGMII_TXID	(MTSW_PHY_RGMII | (1U << 2))
+#define MTSW_PHY_RGMII_RXID	(MTSW_PHY_RGMII | (1U << 3))
+#define MTSW_PHY_RGMII_ID	(MTSW_PHY_RGMII_TXID | MTSW_PHY_RGMII_RXID)
+#define MTSW_PHY_SGMII		(1U << 5)
+
+struct mtsw_softc;
+struct mtsport_softc;
+
+struct mtsw_cpu_port {
+	int			 p_port;
+	struct ifnet		*p_ifp0;
+	struct mtsw_softc	*p_sc;
+
+	int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
+	void (*p_input)(struct ifnet *, struct mbuf *);
+	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
+	    struct rtentry *);
+
+	TAILQ_ENTRY(mtsw_cpu_port)
+				 p_entry;
+};
+
+TAILQ_HEAD(mtsw_cpu_ports, mtsw_cpu_port);
+
+struct mtsw_softc {
+	struct device		 sc_dev;
+	struct mii_bus		*sc_md;
+	unsigned int		 sc_addr;
+	int			 sc_node;
+	struct mutex		 sc_reg_mtx;
+	uint32_t		 sc_reg_page;
+
+	struct mutex		 sc_mii_mtx;
+
+	unsigned int		 sc_flags;
+#define MTSW_F_DUAL_SGMII		(1 << 0)
+	unsigned int		 sc_rev;
+
+	struct mtsport_softc	*sc_ports[MTSW_PORT_COUNT];
+	struct mtsw_cpu_ports	 sc_cpu_ports;
+	unsigned int		 sc_cpu_pmask;
+
+	struct rwlock		 sc_kstat_lock;
+};
+#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname)
+
+struct mtsw_attach_args {
+	char			*maa_name;
+	int			 maa_node;
+	int			 maa_port;
+};
+
+static int		 mtsw_match(struct device *, void *, void *);
+static void		 mtsw_attach(struct device *, struct device *, void *);
+static void		 mtsw_pll_init(struct mtsw_softc *, uint32_t);
+static void		 mtsw_attached(struct mtsw_softc *);
+static void		 mtsw_attach_cpu(struct mtsw_softc *,
+			     const struct mtsw_attach_args *, uint32_t);
+static int		 mtsw_print(void *, const char *);
+
+static uint32_t		 mtsw_rd(struct mtsw_softc *, uint16_t);
+static void		 mtsw_wr(struct mtsw_softc *, uint16_t, uint32_t);
+
+static void		 mtsw_r_set(struct mtsw_softc *sc, uint16_t, uint32_t);
+static void		 mtsw_r_clr(struct mtsw_softc *sc, uint16_t, uint32_t);
+
+static int		 mtsw_mii_rd(struct mtsw_softc *, int, int);
+static void		 mtsw_mii_wr(struct mtsw_softc *, int, int, int);
+
+static int		 mtsw_at_control(struct mtsw_softc *, uint32_t);
+static int		 mtsw_vt_control(struct mtsw_softc *,
+			     uint32_t, uint32_t);
+
+static int		 mtsw_p_ioctl(struct ifnet *, u_long, caddr_t);
+static void		 mtsw_p_input(struct ifnet *, struct mbuf *);
+static int		 mtsw_p_output(struct ifnet *, struct mbuf *,
+			     struct sockaddr *, struct rtentry *);
+
+static inline void	 mtsport_input(struct mtsport_softc *, struct mbuf *);
+
+struct mtsw_defer {
+	struct task		 d_task;
+	struct mtsw_softc	*d_sc;
+};
+
+static void		 mtsw_defer_task(void *);
+
+#if NKSTAT > 0
+static void		 mtsw_kstat_attach_mib(struct mtsw_softc *sc,
+			     int, const char *);
+#endif
+
+const struct cfattach mtsw_ca = {
+	sizeof (struct mtsw_softc), mtsw_match, mtsw_attach
+};
+
+struct cfdriver mtsw_cd = {
+	NULL, "mtsw", DV_DULL
+};
+
+static int
+mtsw_match(struct device *parent, void *match, void *aux)
+{
+	struct mdio_attach_args *maa = aux;
+
+	if (!OF_is_compatible(maa->maa_node, "mediatek,mt7531"))
+		return (0);
+
+	if (maa->maa_addr & ~MTSW_PHY_ADDR_MASK)
+		return (0);
+
+	return (1);
+}
+
+static void
+mtsw_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct mtsw_softc *sc = (struct mtsw_softc *)self;
+	struct mdio_attach_args *maa = aux;
+	uint32_t r, name;
+	int port;
+	struct mtsw_defer *d;
+
+	mtx_init(&sc->sc_reg_mtx, IPL_NET);
+	sc->sc_reg_page = ~0U;
+	mtx_init(&sc->sc_mii_mtx, IPL_NET);
+	rw_init(&sc->sc_kstat_lock, "mtswkslk");
+	TAILQ_INIT(&sc->sc_cpu_ports);
+
+	sc->sc_md = maa->maa_bus;
+	sc->sc_addr = maa->maa_addr;
+	sc->sc_node = maa->maa_node;
+
+	r = mtsw_rd(sc, MTSW_R_CREV);
+	name = (r >> MTSW_R_CREV_NAME_SHIFT) & MTSW_R_CREV_NAME_MASK;
+	if (name != MTSW_R_CREV_NAME_7531) {
+		printf(": unexpected chip revision name %x\n", name);
+		return;
+	}
+
+	sc->sc_rev = (r >> MTSW_R_CREV_REV_SHIFT) & MTSW_R_CREV_REV_MASK;
+	printf(": MT7531 rev %u\n", sc->sc_rev);
+
+	/*
+	 * doco says "all MACs must be forced to link-down" before
+	 * setting reset bits below.
+	 */
+	for (port = MTSW_PORT_PHY_MIN; port <= MTSW_PORT_PHY_MAX; port++) {
+		r = mtsw_mii_rd(sc, port, MII_BMCR);
+		SET(r, BMCR_PDOWN);
+		mtsw_mii_wr(sc, port, MII_BMCR, r);
+	}
+	for (port = MTSW_PORT_MIN; port <= MTSW_PORT_MAX; port++)
+		mtsw_wr(sc, MTSW_R_PMCR(port), MTSW_R_PMCR_FORCE_MODE_LNK);
+
+	/* reset to default values */
+	mtsw_wr(sc, MTSW_R_SYS_CTRL, MTSW_R_SYS_CTRL_SW_REG_RST |
+	    MTSW_R_SYS_CTRL_SW_SYS_RST | MTSW_R_SYS_CTRL_SW_PHY_RST);
+
+	r = mtsw_rd(sc, MTSW_R_SIG_SR);
+	if (ISSET(r, MTSW_R_SIG_SR_PAD_DUAL_SGMII_EN))
+		SET(sc->sc_flags, MTSW_F_DUAL_SGMII);
+	else
+		mtsw_pll_init(sc, r);
+
+	for (port = MTSW_PORT_MIN; port <= MTSW_PORT_MAX; port++) {
+		/* disable forwarding */
+		r = mtsw_rd(sc, MTSW_R_PCR(port));
+		CLR(r, MTSW_R_PCR_PORT_MATRIX_MASK <<
+		    MTSW_R_PCR_PORT_MATRIX_SHIFT);
+		CLR(r, MTSW_R_PCR_PORT_VLAN_MASK);
+		SET(r, MTSW_R_PCR_PORT_VLAN_MATRIX);
+		mtsw_wr(sc, MTSW_R_PCR(port), 0 /* r */);
+
+		r = mtsw_rd(sc, MTSW_R_PSC(port));
+
+		r = mtsw_rd(sc, MTSW_R_PVC(port));
+		r = 0x81000000;
+		mtsw_wr(sc, MTSW_R_PVC(port), r);
+
+//		/* disable learning */
+//		mtsw_r_set(sc, MTSW_R_PSC(port), MTSW_R_PSC_SA_DIS);
+//		mtsw_r_set(sc, MTSW_R_DBG_CNT(port), MTSW_R_DBG_CNT_DIS_CLR);
+
+//		/* (clear) set pvid to 0 */
+//		mtsw_r_clr(sc, MTSW_R_PPBV1(port),
+//		    MTSW_R_PPBV1_G0_PORT_VID_MASK <<
+//		    MTSW_R_PPBV1_G0_PORT_VID_SHIFT);
+	}
+
+	/* enable mib counters */
+	mtsw_wr(sc, MTSW_R_MIBCCR, MTSW_R_MIBCCR_MIB_ENABLE |
+	    MTSW_R_MIBCCR_TX_OCT_CNT_GOOD | MTSW_R_MIBCCR_TX_OCT_CNT_BAD |
+	    MTSW_R_MIBCCR_RX_OCT_CNT_GOOD | MTSW_R_MIBCCR_RX_OCT_CNT_BAD);
+
+	d = malloc(sizeof(*d), M_TEMP, M_WAITOK);
+	task_set(&d->d_task, mtsw_defer_task, d);
+	d->d_sc = sc;
+
+	config_pending_incr();
+	task_add(systq, &d->d_task);
+}
+
+static void
+mtsw_pll_init(struct mtsw_softc *sc, uint32_t sig_sr)
+{
+	uint32_t xtal, div;
+	uint32_t r;
+
+	if (sc->sc_rev > 0) {
+		xtal = ISSET(sig_sr, MTSW_R_SIG_SR_PAD_MCM_SMI_EN) ?
+		    MTSW_R_SWSTRAP_XTAL40 : MTSW_R_SWSTRAP_XTAL25;
+	} else
+		xtal = mtsw_rd(sc, MTSW_R_STRAP) & MTSW_R_SWSTRAP_XTAL;
+
+	div = (xtal == MTSW_R_SWSTRAP_XTAL25) ? 0x140000 : 0x190000;
+
+	/* 1. disable corepll */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_EN);
+	CLR(r, MTSW_R_PLLGP_EN_COREPLL_EN);
+	mtsw_wr(sc, MTSW_R_PLLGP_EN, r);
+
+	/* 2. switch to xtal output */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_EN);
+	SET(r, MTSW_R_PLLGP_EN_SW_CLKSW);
+	mtsw_wr(sc, MTSW_R_PLLGP_EN, r);
+
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	CLR(r, MTSW_R_PLLGP_CR0_EN);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+
+	/* 3. disable pllgp and enable program pllgp */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_EN);
+	SET(r, MTSW_R_PLLGP_EN_SW_PLLGP);
+	mtsw_wr(sc, MTSW_R_PLLGP_EN, r);
+
+	/* 4. program corepll output frequency to 500MHz */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	CLR(r,
+	    MTSW_R_PLLGP_CR0_POS_DIV_MASK << MTSW_R_PLLGP_CR0_POS_DIV_SHIFT);
+	SET(r, 2 << MTSW_R_PLLGP_CR0_POS_DIV_SHIFT);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+	delay(25);
+
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	CLR(r,
+	    MTSW_R_PLLGP_CR0_SDM_PCW_MASK << MTSW_R_PLLGP_CR0_SDM_PCW_SHIFT);
+	SET(r, div << MTSW_R_PLLGP_CR0_SDM_PCW_SHIFT);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+
+	/* update ratio */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	SET(r, MTSW_R_PLLGP_CR0_SDM_PCW_CHG);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+	delay(10);
+
+	/* 5. clear update ratio */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	CLR(r, MTSW_R_PLLGP_CR0_SDM_PCW_CHG);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+
+	/* enable 250SSC clock for rgmii */
+	mtsw_wr(sc, MTSW_R_ANA_PLLGP_CR2, 0x4f40000);
+	/* enable 325M clock for sgmii */
+	mtsw_wr(sc, MTSW_R_ANA_PLLGP_CR5, 0xad0000);
+
+	/* 6. enable pll */
+	r = mtsw_rd(sc, MTSW_R_PLLGP_CR0);
+	SET(r, MTSW_R_PLLGP_CR0_EN);
+	mtsw_wr(sc, MTSW_R_PLLGP_CR0, r);
+
+	r = mtsw_rd(sc, MTSW_R_PLLGP_EN);
+	SET(r, MTSW_R_PLLGP_EN_COREPLL_EN);
+	mtsw_wr(sc, MTSW_R_PLLGP_EN, r);
+
+	delay(25);
+}
+
+static void
+mtsw_defer_task(void *arg)
+{
+	struct mtsw_defer *d = arg;
+	struct mtsw_softc *sc = d->d_sc;
+
+	free(d, M_TEMP, sizeof(*d));
+
+	mtsw_attached(sc);
+
+	config_pending_decr();
+}
+
+struct mtsw_phy_map_entry {
+	const char	*name;
+	unsigned int	 phy_mode;
+};
+
+static const struct mtsw_phy_map_entry mtsw_phy_map[] = {
+	{ "rgmii",	MTSW_PHY_RGMII },
+	{ "rgmii-txid",	MTSW_PHY_RGMII_TXID },
+	{ "rgmii-rxid",	MTSW_PHY_RGMII_RXID },
+	{ "rgmii-id",	MTSW_PHY_RGMII_ID },
+	{ "sgmii",	MTSW_PHY_SGMII },
+};
+
+static unsigned int
+mtsw_phy_mode(struct mtsw_softc *sc, int node)
+{
+	char phy_mode[32];
+	size_t i;
+
+	if (OF_getpropstr(node, "phy-mode", phy_mode, sizeof(phy_mode)) <= 0)
+		return (0);
+
+	for (i = 0; i < nitems(mtsw_phy_map); i++) {
+		const struct mtsw_phy_map_entry *e = &mtsw_phy_map[i];
+		if (strcmp(phy_mode, e->name) == 0)
+			return (e->phy_mode);
+	}
+
+	return (MTSW_PHY_UNKNOWN);
+}
+
+static void
+mtsw_rgmii(struct mtsw_softc *sc, unsigned int phy_mode)
+{
+	uint32_t r;
+
+	r = mtsw_rd(sc, MTSW_R_CLKGEN_CTRL);
+	SET(r, MTSW_R_CLKGEN_CTRL_EN);
+
+	CLR(r, MTSW_R_CLKGEN_CTRL_MODE_MASK);
+	SET(r, MTSW_R_CLKGEN_CTRL_MODE_RGMII);
+
+	CLR(r, MTSW_R_CLKGEN_CTRL_SKEW_IN_MASK);
+	SET(r, MTSW_R_CLKGEN_CTRL_SKEW_IN_NO_CHANGE);
+	CLR(r, MTSW_R_CLKGEN_CTRL_SKEW_OUT_MASK);
+	SET(r, MTSW_R_CLKGEN_CTRL_SKEW_OUT_NO_CHANGE);
+
+	/* this seems round the wrong way */
+	if (ISSET(phy_mode, MTSW_PHY_RGMII_TXID))
+		CLR(r, MTSW_R_CLKGEN_CTRL_NO_DELAY);
+	else
+		SET(r, MTSW_R_CLKGEN_CTRL_NO_DELAY);
+
+	/* this seems round the wrong way */
+	if (ISSET(phy_mode, MTSW_PHY_RGMII_RXID))
+		CLR(r, MTSW_R_CLKGEN_CTRL_NO_REVERSE);
+	else
+		SET(r, MTSW_R_CLKGEN_CTRL_NO_REVERSE);
+
+	printf("%s: phy_mode %x CLKGEN_CTRL %08x\n", DEVNAME(sc), phy_mode, r);
+
+	mtsw_wr(sc, MTSW_R_CLKGEN_CTRL, r);
+}
+
+static void
+mtsw_attached(struct mtsw_softc *sc)
+{
+	int pnode, node, port;
+	uint32_t pmask = 0;
+	uint32_t vt;
+	struct mtsw_cpu_port *p;
+	uint32_t r;
+	int rv;
+
+	pnode = OF_getnodebyname(sc->sc_node, "ports");
+	if (pnode == 0) {
+		printf("%s: no ports property\n", DEVNAME(sc));
+		return;
+	};
+
+	for (node = OF_child(pnode); node != 0; node = OF_peer(node)) {
+		struct mtsw_attach_args maa;
+		char name[32];
+		char status[16];
+		uint32_t phandle;
+		struct device *child;
+		unsigned int phy_mode;
+
+		port = OF_getpropint(node, "reg", -1);
+		if (port < MTSW_PORT_MIN || port > MTSW_PORT_MAX)
+			continue;
+
+		if (OF_getpropstr(node, "status", status, sizeof(status)) > 0 &&
+		    strcmp(status, "disabled") == 0)
+			continue;
+
+		if (ISSET(pmask, 1 << port)) {
+			printf("%s: port %u is already used\n",
+			    DEVNAME(sc), port);
+			continue;
+		}
+		SET(pmask, 1 << port);
+
+		phy_mode = mtsw_phy_mode(sc, node);
+
+		switch (port) {
+		case 5:
+			if (ISSET(phy_mode, MTSW_PHY_RGMII)) {
+				mtsw_rgmii(sc, phy_mode);
+				break;
+			}
+			/* FALLTHROUGH */
+		case 6:
+			if (!ISSET(phy_mode, MTSW_PHY_SGMII)) {
+				printf("%s: unsupported phy-mode on port %d\n",
+				    DEVNAME(sc), port);
+				break;
+			}
+			break;
+		default:
+#if 0
+			if (phy_mode != 0) {
+				printf("%s: phy-mode specified on port %d\n",
+				    DEVNAME(sc), port);
+			}
+#endif
+			break;
+		}
+
+		mtsw_r_clr(sc, MTSW_R_PMCR(port),
+		    MTSW_R_PMCR_FORCE_LINK_MASK |
+		    MTSW_R_PMCR_FORCE_DPX_MASK |
+		    MTSW_R_PMCR_FORCE_SPD_MASK |
+		    MTSW_R_PMCR_FORCE_TX_FC |
+		    MTSW_R_PMCR_FORCE_RX_FC |
+		    MTSW_R_PMCR_FORCE_EEE100 |
+		    MTSW_R_PMCR_FORCE_EEE1G |
+		    MTSW_R_PMCR_MAC_RX_EN | MTSW_R_PMCR_MAC_TX_EN);
+
+		OF_getpropstr(node, "name", name, sizeof(name));
+		memset(&maa, 0, sizeof(maa));
+		maa.maa_name = name;
+		maa.maa_node = node;
+		maa.maa_port = port;
+
+		phandle = OF_getpropint(node, "ethernet", 0);
+		if (phandle != 0) {
+			mtsw_attach_cpu(sc, &maa, phandle);
+			continue;
+		}
+
+		child = config_found(&sc->sc_dev, &maa, mtsw_print);
+		if (child == NULL)
+			continue;
+
+		sc->sc_ports[port] = (struct mtsport_softc *)child;
+
+#if NKSTAT > 0
+		mtsw_kstat_attach_mib(sc, port, child->dv_xname);
+#endif
+	}
+
+	/* we know where the CPU ports are now */
+	r = mtsw_rd(sc, 0x4);
+	CLR(r, 0xff);
+	SET(r, sc->sc_cpu_pmask);
+	mtsw_wr(sc, 0x4, r);
+
+	/* add all these ports to vlan 0 */
+	vt = MTSW_VT_VALID | MTSW_VT_FID_BRIDGED |
+	    MTSW_VT_EG_CON | MTSW_VT_IVL_MAC;
+	SET(vt, pmask << MTSW_VT_PORT_MEM_SHIFT);
+	mtsw_wr(sc, MTSW_R_VAWD1, vt);
+	mtsw_wr(sc, MTSW_R_VAWD2, 0);
+	rv = mtsw_vt_control(sc, MTSW_R_VTCR_FUNC_WRITE_VID, 0);
+	if (rv != 0) {
+		printf("%s: vlan 0 table entry add failed (%d)\n",
+		    DEVNAME(sc), rv);
+	}
+
+	rv = mtsw_at_control(sc, MTSW_R_ATC_AC_CMD_CLEAN);
+	if (rv != 0) {
+		printf("%s: address table clean failed (%d)\n",
+		    DEVNAME(sc), rv);
+	}
+
+	CLR(pmask, sc->sc_cpu_pmask);
+
+	TAILQ_FOREACH(p, &sc->sc_cpu_ports, p_entry) {
+		r = mtsw_rd(sc, MTSW_R_PCR(p->p_port));
+		CLR(r, MTSW_R_PCR_PORT_MATRIX_MASK <<
+		    MTSW_R_PCR_PORT_MATRIX_SHIFT);
+		SET(r, pmask << MTSW_R_PCR_PORT_MATRIX_SHIFT);
+		mtsw_wr(sc, MTSW_R_PCR(p->p_port), r);
+
+		/* enable the magic headers */
+		mtsw_wr(sc, MTSW_R_PVC(p->p_port), MTSW_R_PVC_PORT_STAG);
+
+		mtsw_r_set(sc, MTSW_R_PMCR(p->p_port),
+		    MTSW_R_PMCR_MAC_RX_EN | MTSW_R_PMCR_MAC_TX_EN |
+		    MTSW_R_PMCR_FORCE_MODE_LNK | MTSW_R_PMCR_FORCE_LINK_UP |
+		    MTSW_R_PMCR_FORCE_MODE_SPD | MTSW_R_PMCR_FORCE_SPD_1000M |
+		    MTSW_R_PMCR_FORCE_MODE_DPX | MTSW_R_PMCR_FORCE_DPX_FULL);
+	}
+}
+
+static void
+mtsw_attach_cpu(struct mtsw_softc *sc, const struct mtsw_attach_args *maa,
+    uint32_t phandle)
+{
+	struct mtsw_cpu_port *p;
+	struct ifnet *ifp0;
+	struct arpcom *ac0;
+
+	ifp0 = if_byphandle(phandle);
+	if (ifp0 == NULL) {
+		printf("%s: cannot find cpu interface on port %u\n",
+		    DEVNAME(sc), maa->maa_port);
+		return;
+	}
+
+	if (ifp0->if_type != IFT_ETHER) {
+		printf("%s: unsupported type of cpu interface on port %u\n",
+		    DEVNAME(sc), maa->maa_port);
+		return;
+	}
+
+	printf("%s: %s at port %u\n", DEVNAME(sc), ifp0->if_xname,
+	    maa->maa_port);
+
+	NET_LOCK();
+	ac0 = (struct arpcom *)ifp0;
+	if (ac0->ac_trunkport != NULL) {
+		printf("%s: cpu interface %s is busy\n",
+		    DEVNAME(sc), ifp0->if_xname);
+		NET_UNLOCK();
+		return;
+	}
+
+	p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK|M_ZERO);
+
+	p->p_port = maa->maa_port;
+	p->p_ifp0 = ifp0;
+	p->p_sc = sc;
+
+	p->p_ioctl = ifp0->if_ioctl;
+	p->p_input = ifp0->if_input;
+	p->p_output = ifp0->if_output;
+
+	if (ifpromisc(ifp0, 1) != 0)
+		printf("%s: %s promisc error\n", DEVNAME(sc), ifp0->if_xname);
+
+	ac0->ac_trunkport = p;
+	/* membar_producer? */
+	ifp0->if_ioctl = mtsw_p_ioctl;
+	ifp0->if_input = mtsw_p_input;
+	ifp0->if_output = mtsw_p_output;
+	NET_UNLOCK();
+
+	TAILQ_INSERT_TAIL(&sc->sc_cpu_ports, p, p_entry);
+	SET(sc->sc_cpu_pmask, 1 << maa->maa_port);
+
+#if NKSTAT > 0
+	mtsw_kstat_attach_mib(sc, maa->maa_port, ifp0->if_xname);
+#endif
+}
+
+static int
+mtsw_print(void *aux, const char *pnp)
+{
+	struct mtsw_attach_args *maa = aux;
+
+	if (pnp != NULL)
+		printf("\"%s\" at %s", maa->maa_name, pnp);
+
+	printf(" port %d", maa->maa_port);
+
+	return (UNCONF);
+}
+
+static uint32_t
+mtsw_rd(struct mtsw_softc *sc, uint16_t reg)
+{
+	struct mii_bus *md = sc->sc_md;
+	uint16_t page, r;
+	uint32_t hi, lo;
+
+	page = (reg >> MTSW_MDIO_PAGE_SHIFT) & MTSW_MDIO_PAGE_MASK;
+	r = (reg >> MTSW_MDIO_REG_SHIFT) & MTSW_MDIO_REG_MASK;
+
+	mtx_enter(&sc->sc_reg_mtx);
+	if (sc->sc_reg_page != page) {
+		/* move the chip to the right page */
+		md->md_writereg(md->md_cookie, sc->sc_addr,
+		    MTSW_MDIO_PAGE_REG, page);
+		sc->sc_reg_page = page;
+	}
+
+	lo = md->md_readreg(md->md_cookie, sc->sc_addr, r) & 0xffff;
+	hi = md->md_readreg(md->md_cookie, sc->sc_addr, 0x10) & 0xffff;
+	mtx_leave(&sc->sc_reg_mtx);
+
+	return ((hi << 16) | lo);
+}
+
+static void
+mtsw_wr(struct mtsw_softc *sc, uint16_t reg, uint32_t val)
+{
+	struct mii_bus *md = sc->sc_md;
+	uint16_t page, r;
+	uint32_t hi, lo;
+
+	page = (reg >> MTSW_MDIO_PAGE_SHIFT) & MTSW_MDIO_PAGE_MASK;
+	r = (reg >> MTSW_MDIO_REG_SHIFT) & MTSW_MDIO_REG_MASK;
+
+	hi = val >> 16;
+	lo = val & 0xffff;
+
+	mtx_enter(&sc->sc_reg_mtx);
+	if (sc->sc_reg_page != page) {
+		/* move the chip to the right page */
+		md->md_writereg(md->md_cookie, sc->sc_addr,
+		    MTSW_MDIO_PAGE_REG, page);
+		sc->sc_reg_page = page;
+	}
+
+	md->md_writereg(md->md_cookie, sc->sc_addr, r, lo);
+	md->md_writereg(md->md_cookie, sc->sc_addr, 0x10, hi);
+	mtx_leave(&sc->sc_reg_mtx);
+}
+
+static void
+mtsw_r_set(struct mtsw_softc *sc, uint16_t reg, uint32_t bits)
+{
+	uint32_t r;
+
+	r = mtsw_rd(sc, reg);
+	SET(r, bits);
+	mtsw_wr(sc, reg, r);
+}
+
+static void
+mtsw_r_clr(struct mtsw_softc *sc, uint16_t reg, uint32_t bits)
+{
+	uint32_t r;
+
+	r = mtsw_rd(sc, reg);
+	CLR(r, bits);
+	mtsw_wr(sc, reg, r);
+}
+
+static uint32_t
+mtsw_mii_piac(struct mtsw_softc *sc)
+{
+	uint32_t r;
+	int n;
+
+	for (n = 0; n < 5000; n++) {
+		r = mtsw_rd(sc, MTSW_R_PHY_IAC);
+		if (!ISSET(r, MTSW_R_PHY_IAC_PHY_ACS_ST))
+			break;
+
+		delay(20);
+	}
+
+	return (r);
+}
+
+static int
+mtsw_mii_rd(struct mtsw_softc *sc, int phy, int reg)
+{
+	int rv = -1;
+	uint32_t r;
+
+	mtx_enter(&sc->sc_mii_mtx);
+	r = mtsw_mii_piac(sc);
+	if (ISSET(r, MTSW_R_PHY_IAC_PHY_ACS_ST))
+		goto leave;
+
+	mtsw_wr(sc, MTSW_R_PHY_IAC, MTSW_R_PHY_IAC_MDIO_ST_C22 |
+	    MTSW_R_PHY_IAC_MDIO_CMD_READ |
+	    MTSW_R_PHY_IAC_MDIO_PHY_ADDR(phy) |
+	    MTSW_R_PHY_IAC_MDIO_REG_ADDR(reg) |
+	    MTSW_R_PHY_IAC_PHY_ACS_ST);
+
+	r = mtsw_mii_piac(sc);
+	if (ISSET(r, MTSW_R_PHY_IAC_PHY_ACS_ST))
+		goto leave;
+
+	rv = (r >> MTSW_R_PHY_IAC_MDIO_DATA_SHIFT) &
+	    MTSW_R_PHY_IAC_MDIO_DATA_MASK;
+leave:
+	mtx_leave(&sc->sc_mii_mtx);
+
+	return (rv);
+}
+
+static void
+mtsw_mii_wr(struct mtsw_softc *sc, int phy, int reg, int val)
+{
+	uint32_t r;
+
+	mtx_enter(&sc->sc_mii_mtx);
+	r = mtsw_mii_piac(sc);
+	if (ISSET(r, MTSW_R_PHY_IAC_PHY_ACS_ST))
+		goto leave;
+
+	r = (val & MTSW_R_PHY_IAC_MDIO_DATA_MASK) <<
+	    MTSW_R_PHY_IAC_MDIO_DATA_SHIFT;
+
+	mtsw_wr(sc, MTSW_R_PHY_IAC, r | MTSW_R_PHY_IAC_MDIO_ST_C22 |
+	    MTSW_R_PHY_IAC_MDIO_CMD_WRITE |
+	    MTSW_R_PHY_IAC_MDIO_REG_ADDR(reg) |
+	    MTSW_R_PHY_IAC_MDIO_PHY_ADDR(phy) |
+	    MTSW_R_PHY_IAC_PHY_ACS_ST);
+
+	(void)mtsw_mii_piac(sc); /* XXX do we have to wait for this? */
+leave:
+	mtx_leave(&sc->sc_mii_mtx);
+}
+
+static int
+mtsw_at_control(struct mtsw_softc *sc, uint32_t cmd)
+{
+	unsigned int t;
+
+	SET(cmd, MTSW_R_ATC_BUSY | MTSW_R_ATC_AC_MAT_ALL_MAC);
+	mtsw_wr(sc, MTSW_R_ATC, cmd);
+
+	for (t = 0; t < 1000; t++) {
+		uint32_t r = mtsw_rd(sc, MTSW_R_ATC);
+		if (!ISSET(r, MTSW_R_ATC_BUSY))
+			return (0);
+
+		delay(200);
+	}
+
+	return (ETIMEDOUT);
+}
+
+static int
+mtsw_vt_control(struct mtsw_softc *sc, uint32_t cmd, uint32_t vlan)
+{
+	unsigned int t;
+
+	SET(cmd, vlan << MTSW_R_VTCR_VID_SHIFT);
+	SET(cmd, MTSW_R_VTCR_BUSY);
+
+	mtsw_wr(sc, MTSW_R_VTCR, cmd);
+
+	for (t = 0; t < 1000; t++) {
+		uint32_t r = mtsw_rd(sc, MTSW_R_VTCR);
+		if (!ISSET(r, MTSW_R_VTCR_BUSY)) {
+			if (ISSET(r, MTSW_R_VTCR_IDX_INVLD))
+				return (EINVAL);
+
+			return (0);
+		}
+
+		delay(200);
+	}
+
+	return (ETIMEDOUT);
+}
+
+static int
+mtsw_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mtsw_cpu_port *p = ac0->ac_trunkport;
+	int error = 0;
+
+	switch (cmd) {
+	case SIOCGTRUNKPORT: {
+		struct trunk_reqport *rp = (struct trunk_reqport *)data;
+		struct mtsw_softc *sc = p->p_sc;
+
+		if (strncmp(rp->rp_ifname, rp->rp_portname,
+		    sizeof(rp->rp_ifname)) != 0)
+			return (EINVAL);
+
+		(void)strlcpy(rp->rp_ifname, DEVNAME(sc),
+		    sizeof(rp->rp_ifname));
+		break;
+	}
+
+	case SIOCSIFLLADDR:
+		error = EBUSY;
+		break;
+
+	default:
+		error = (*p->p_ioctl)(ifp0, cmd, data);
+		break;
+	}
+
+	return (error);
+}
+
+static int
+mtsw_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
+    struct rtentry *rt)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mtsw_cpu_port *p = ac0->ac_trunkport;
+
+#if 0
+	/* restrict transmission to bpf only */
+	if (m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL) {
+		m_freem(m);
+		return (EBUSY);
+	}
+#endif
+
+	return ((*p->p_output)(ifp0, m, dst, rt));
+}
+
+static void
+mtsw_p_input(struct ifnet *ifp0, struct mbuf *m)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mtsw_cpu_port *p = ac0->ac_trunkport;
+	struct mtsw_softc *sc = p->p_sc;
+	struct ether_header *eh;
+	int hlen = sizeof(*eh) + sizeof(uint16_t);
+	int diff = hlen - offsetof(struct ether_header, ether_type);
+	uint16_t shim;
+	struct mtsport_softc *psc;
+	int port;
+
+	if (m->m_len < hlen) {
+		m = m_pullup(m, hlen);
+		if (m == NULL) {
+			/* drop++ */
+			return;
+		}
+	}
+	eh = mtod(m, struct ether_header *);
+
+	shim = bemtoh16(&eh->ether_type);
+
+	port = shim & 0x7;
+	if (port >= nitems(sc->sc_ports))
+		goto drop;
+
+	psc = sc->sc_ports[port];
+	if (psc == NULL)
+		goto drop;
+
+	memmove(mtod(m, caddr_t) + diff, mtod(m, caddr_t),
+	    offsetof(struct ether_header, ether_type));
+	m_adj(m, diff);
+
+	mtsport_input(psc, m);
+	return;
+
+drop:
+	m_freem(m);
+}
+
+/*
+ * mtsw port driver
+ */
+
+struct mtsport_softc {
+	struct device		 psc_dev;
+	int			 psc_node;
+	int			 psc_port;
+
+	struct arpcom		 psc_ac;
+#define psc_if			 psc_ac.ac_if
+
+	struct mtsw_softc	*psc_parent;
+	struct mii_data		 psc_mii;
+#define psc_ifmedia		 psc_mii.mii_media
+
+	struct if_device	 psc_ifd;
+};
+#define PDEVNAME(_psc) ((_psc)->psc_dev.dv_xname)
+
+static int		mtsport_match(struct device *, void *, void *);
+static void		mtsport_attach(struct device *, struct device *,
+			    void *);
+
+static void		mtsport_mii_attach(struct mtsport_softc *);
+
+static int		mtsport_ioctl(struct ifnet *, u_long, caddr_t);
+static void		mtsport_start(struct ifqueue *);
+
+static int		mtsport_up(struct mtsport_softc *sc);
+static int		mtsport_down(struct mtsport_softc *sc);
+
+static void		mtsport_mii_attach(struct mtsport_softc *);
+static int		mtsport_miibus_readreg(struct device *, int, int);
+static void		mtsport_miibus_writereg(struct device *, int, int, int);
+static void		mtsport_miibus_statchg(struct device *);
+
+static int		mtsport_media_upd(struct ifnet *);
+static void		mtsport_media_sts(struct ifnet *, struct ifmediareq *);
+
+const struct cfattach mtsport_ca = {
+	sizeof(struct mtsport_softc), mtsport_match, mtsport_attach
+};
+
+struct cfdriver mtsport_cd = {
+	NULL, "mtsport", DV_DULL
+};
+
+static int
+mtsport_match(struct device *parent, void *match, void *aux)
+{
+	return (1);
+}
+
+static void
+mtsport_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct mtsport_softc *psc = (struct mtsport_softc *)self;
+	struct mtsw_attach_args *maa = aux;
+	struct mii_data *mii = &psc->psc_mii;
+	struct ifnet *ifp;
+	uint32_t phandle;
+	uint16_t bmcr;
+
+	psc->psc_node = maa->maa_node;
+	psc->psc_port = maa->maa_port;
+	psc->psc_parent = (struct mtsw_softc *)parent;
+
+	LIST_INIT(&mii->mii_phys);
+
+	ifp = &psc->psc_if;
+	(void)strlcpy(ifp->if_xname, PDEVNAME(psc), sizeof(ifp->if_xname));
+	ifp->if_softc = psc;
+	ifp->if_ioctl = mtsport_ioctl;
+	ifp->if_qstart = mtsport_start;
+	ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
+	ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
+
+	if (OF_getprop(psc->psc_node, "local-mac-address",
+	    &psc->psc_ac.ac_enaddr, sizeof(psc->psc_ac.ac_enaddr)) !=
+	    sizeof(psc->psc_ac.ac_enaddr))
+		ether_fakeaddr(ifp);
+
+	printf(": address %s\n", ether_sprintf(psc->psc_ac.ac_enaddr));
+
+	switch (psc->psc_port) {
+	case 5:
+		phandle = OF_getpropint(psc->psc_node,
+		    "phy-handle", 0);
+		if (phandle != 0)
+			printf("%s: XXX external phy?\n", PDEVNAME(psc));
+		break;
+	case 6:
+		printf("%s: sgmii?\n", PDEVNAME(psc));
+		break;
+	default:
+		/* turn the phy back on since we're going to use it */
+		bmcr = mtsw_mii_rd(psc->psc_parent, psc->psc_port, MII_BMCR);
+		CLR(bmcr, BMCR_PDOWN);
+		mtsw_mii_wr(psc->psc_parent, psc->psc_port, MII_BMCR, bmcr);
+
+		mii->mii_ifp = ifp;
+		mii->mii_readreg = mtsport_miibus_readreg;
+		mii->mii_writereg = mtsport_miibus_writereg;
+		mii->mii_statchg = mtsport_miibus_statchg;
+
+		mtsport_mii_attach(psc);
+	}
+
+	if (LIST_FIRST(&mii->mii_phys) == NULL) {
+		printf("%s: no PHY found!\n", PDEVNAME(psc));
+		ifmedia_add(&psc->psc_ifmedia, IFM_ETHER|IFM_MANUAL,
+		    0, NULL);
+		ifmedia_set(&psc->psc_ifmedia, IFM_ETHER|IFM_MANUAL);
+	} else
+		ifmedia_set(&psc->psc_ifmedia, IFM_ETHER|IFM_AUTO);
+
+	if_counters_alloc(ifp);
+	if_attach(ifp);
+	ether_ifattach(ifp);
+
+	OF_getpropstr(psc->psc_node, "label",
+	    ifp->if_description, sizeof(ifp->if_description));
+
+	psc->psc_ifd.if_node = psc->psc_node;
+	psc->psc_ifd.if_ifp = ifp;
+	if_register(&psc->psc_ifd);
+}
+
+static void
+mtsport_mii_attach(struct mtsport_softc *psc)
+{
+	struct mii_data *mii = &psc->psc_mii;
+
+	ifmedia_init(&psc->psc_ifmedia, 0,
+	    mtsport_media_upd, mtsport_media_sts);
+
+	mii_attach(&psc->psc_dev, mii, 0xffffffff,
+	    psc->psc_port, MII_OFFSET_ANY, 0);
+}
+
+static int
+mtsport_miibus_readreg(struct device *dev, int phy, int reg)
+{
+	struct device *parent = dev->dv_parent;
+	struct mtsw_softc *sc = (struct mtsw_softc *)parent;
+
+	return (mtsw_mii_rd(sc, phy, reg));
+}
+
+static void
+mtsport_miibus_writereg(struct device *dev, int phy, int reg, int val)
+{
+	struct device *parent = dev->dv_parent;
+	struct mtsw_softc *sc = (struct mtsw_softc *)parent;
+
+	return (mtsw_mii_wr(sc, phy, reg, val));
+}
+
+static void
+mtsport_miibus_statchg(struct device *dev)
+{
+	printf("%s: %s[%u]\n", dev->dv_xname, __func__, __LINE__);
+}
+
+static int
+mtsport_media_upd(struct ifnet *ifp)
+{
+	struct mtsport_softc *psc = ifp->if_softc;
+
+	if (LIST_FIRST(&psc->psc_mii.mii_phys))
+		mii_mediachg(&psc->psc_mii);
+
+	return (0);
+}
+
+static void
+mtsport_media_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+	struct mtsport_softc *psc = ifp->if_softc;
+
+	if (LIST_FIRST(&psc->psc_mii.mii_phys)) {
+		mii_pollstat(&psc->psc_mii);
+		ifmr->ifm_active = psc->psc_mii.mii_media_active;
+		ifmr->ifm_status = psc->psc_mii.mii_media_status;
+	}
+} 
+
+static int
+mtsport_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+	struct mtsport_softc *psc = ifp->if_softc;
+	struct ifreq *ifr = (struct ifreq *)data;
+	int error = 0;
+
+	switch (cmd) {
+	case SIOCSIFFLAGS:
+		if (ISSET(ifp->if_flags, IFF_UP)) {
+			if (!ISSET(ifp->if_flags, IFF_RUNNING))
+				error = mtsport_up(psc);
+		} else {
+			if (ISSET(ifp->if_flags, IFF_RUNNING))
+				error = mtsport_down(psc);
+		}
+		break;
+
+	case SIOCSIFMEDIA:
+	case SIOCGIFMEDIA:
+		error = ifmedia_ioctl(ifp, ifr, &psc->psc_ifmedia, cmd);
+		break;
+
+	default:
+		error = ether_ioctl(ifp, &psc->psc_ac, cmd, data);
+		break;
+	}
+
+	if (error == ENETRESET) {
+		/* hardware doesnt need reprogramming */
+		error = 0;
+	}
+
+	return (error);
+}
+
+static int
+mtsport_up(struct mtsport_softc *psc)
+{
+	struct ifnet *ifp = &psc->psc_if;
+	struct mtsw_softc *sc = psc->psc_parent;
+	uint32_t r;
+
+	r = mtsw_rd(sc, MTSW_R_PCR(psc->psc_port));
+	CLR(r, MTSW_R_PCR_PORT_MATRIX_MASK << MTSW_R_PCR_PORT_MATRIX_SHIFT);
+	SET(r, sc->sc_cpu_pmask << MTSW_R_PCR_PORT_MATRIX_SHIFT);
+	mtsw_wr(sc, MTSW_R_PCR(psc->psc_port), r);
+
+	mtsw_r_set(sc, MTSW_R_PMCR(psc->psc_port),
+	    MTSW_R_PMCR_MAC_RX_EN | MTSW_R_PMCR_MAC_TX_EN);
+
+	SET(ifp->if_flags, IFF_RUNNING);
+
+	return (0);
+}
+
+static int
+mtsport_down(struct mtsport_softc *psc)
+{
+	struct ifnet *ifp = &psc->psc_if;
+	struct mtsw_softc *sc = psc->psc_parent;
+	uint32_t r;
+
+	CLR(ifp->if_flags, IFF_RUNNING);
+
+	mtsw_r_clr(sc, MTSW_R_PMCR(psc->psc_port),
+	    MTSW_R_PMCR_MAC_RX_EN | MTSW_R_PMCR_MAC_TX_EN);
+
+	r = mtsw_rd(sc, MTSW_R_PCR(psc->psc_port));
+	CLR(r, MTSW_R_PCR_PORT_MATRIX_MASK << MTSW_R_PCR_PORT_MATRIX_SHIFT);
+	mtsw_wr(sc, MTSW_R_PCR(psc->psc_port), r);
+
+	return (0);
+}
+
+static void
+mtsport_start(struct ifqueue *ifq)
+{
+	struct ifnet *ifp = ifq->ifq_if;
+	struct mtsport_softc *psc = ifp->if_softc;
+	struct mbuf *m;
+	struct ether_header *eh;
+	uint16_t shim;
+	uint16_t *pad;
+	const int hlen = sizeof(*eh) + sizeof(*pad);
+	const int offs = offsetof(struct ether_header, ether_type);
+	const int diff = hlen - offs;
+	int errors = 0;
+
+	struct mtsw_softc *sc = psc->psc_parent;
+	struct mtsw_cpu_port *p = TAILQ_FIRST(&sc->sc_cpu_ports);
+
+	if (p == NULL) {
+		ifq_purge(ifq);
+		return;
+	}
+
+	while ((m = ifq_dequeue(ifq)) != NULL) {
+#if NBPFILTER > 0
+		{
+			caddr_t if_bpf = ifp->if_bpf;
+			if (if_bpf)
+				bpf_mtap_ether(if_bpf, m, BPF_DIRECTION_OUT);
+		}
+#endif
+
+		m = m_prepend(m, diff, M_NOWAIT);
+		if (m == NULL) {
+			errors++;
+			continue;
+		}
+		if (m->m_len < hlen) {
+			m = m_pullup(m, hlen);
+			if (m == NULL) {
+				errors++;
+				continue;
+			}
+		}
+
+		memmove(mtod(m, caddr_t), mtod(m, caddr_t) + diff, offs);
+		eh = mtod(m, struct ether_header *);
+
+		shim = 1 << psc->psc_port;
+		htobem16(&eh->ether_type, shim);
+
+		pad = (uint16_t *)(eh + 1);
+		*pad = htons(0);
+
+		if (if_enqueue(p->p_ifp0, m) != 0)
+			errors++;
+	}
+
+	if (errors)
+		counters_add(ifp->if_counters, ifc_oerrors, errors);
+}
+
+static inline void
+mtsport_input(struct mtsport_softc *psc, struct mbuf *m)
+{
+	if_vinput(&psc->psc_if, m);
+}
+
+#if NKSTAT > 0
+
+struct mtsw_mib_kstat {
+	unsigned int			 mib_addr;
+	enum kstat_kv_type		 mib_type;
+	enum kstat_kv_unit		 mib_unit;
+	const char			*mib_name;
+};
+
+static const struct mtsw_mib_kstat mtsw_mib_kstats[] = {
+	{ 0x00, KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx drops" },
+	{ 0x04, KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx crc" },
+	{ 0x08,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx unicast" },
+	{ 0x0c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx multicast" },
+	{ 0x10,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx broadcast" },
+	{ 0x14,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx collisions" },
+	{ 0x18,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx single colls" },
+	{ 0x1c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx multi colls" },
+	{ 0x20,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx deferred" },
+	{ 0x24,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx late colls" },
+	{ 0x28,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE, "tx excess colls" },
+	{ 0x2c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx pause" },
+	{ 0x30,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 64B" },
+	{ 0x34,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 65B" },
+	{ 0x38,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 128B" },
+	{ 0x3c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 256B" },
+	{ 0x40,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 512B" },
+	{ 0x44,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "tx 1024B" },
+	{ 0x48,	KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES, "tx" },
+
+	{ 0x60,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx drops" },
+	{ 0x64,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx filtering" },
+	{ 0x68,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx unicast" },
+	{ 0x6c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx multicast" },
+	{ 0x70,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx broadcast" },
+	{ 0x74,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx align err" },
+	{ 0x78,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx crc" },
+	{ 0x7c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx undersize" },
+	{ 0x80,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx fragment" },
+	{ 0x84,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx oversize" },
+	{ 0x88,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx jabber" },
+	{ 0x90,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 64B" },
+	{ 0x94,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 65B" },
+	{ 0x98,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 128B" },
+	{ 0x9c,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 256B" },
+	{ 0xa0,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 512B" },
+	{ 0xa4,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx 1024B" },
+	{ 0xa8,	KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES, "rx" },
+
+	{ 0xb0,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx ctrl drop" },
+	{ 0xb4,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx ingress drop" },
+	{ 0xb8,	KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS, "rx arl drop" },
+};
+
+static uint64_t
+mtsw_rd_c64(struct mtsw_softc *sc, uint16_t r)
+{
+	uint32_t lo, hi, ok;
+
+	hi = mtsw_rd(sc, r + 4);
+	for (;;) {
+		lo = mtsw_rd(sc, r);
+		ok = mtsw_rd(sc, r + 4);
+		if (hi == ok)
+			break;
+
+		hi = ok;
+	}
+
+	return ((uint64_t)hi << 32) | (uint64_t)lo;
+}
+
+static int
+mtsw_kstat_read_mib(struct kstat *ks)
+{
+	struct mtsw_softc *sc = ks->ks_softc;
+	struct kstat_kv *kvs = ks->ks_data;
+	int port = (int)(intptr_t)ks->ks_ptr;
+	size_t i;
+	struct timespec now, diff;
+
+	/* avoid hitting the mdio bus too hard */
+	getnanouptime(&now);
+	timespecsub(&now, &ks->ks_updated, &diff);
+	if (timespeccmp(&diff, &ks->ks_interval, <))
+		return (0);
+
+	for (i = 0; i < nitems(mtsw_mib_kstats); i++) {
+		const struct mtsw_mib_kstat *mib = &mtsw_mib_kstats[i];
+		struct kstat_kv *kv = &kvs[i];
+		uint16_t r = MTSW_R_MIB_BASE(port) + mib->mib_addr;
+
+		if (mib->mib_type == KSTAT_KV_T_COUNTER32) 
+			kstat_kv_u32(kv) = mtsw_rd(sc, r);
+		else
+			kstat_kv_u64(kv) = mtsw_rd_c64(sc, r);
+	}
+
+	ks->ks_updated = now;
+	return (0);
+}
+
+static void
+mtsw_kstat_attach_mib(struct mtsw_softc *sc, int port, const char *ifname)
+{
+	static const struct timespec ival = { 0, 500000000 };
+	struct kstat *ks;
+	struct kstat_kv *kvs;
+	size_t i;
+
+	ks = kstat_create(ifname, 0, "mtsw-mib", 0, KSTAT_T_KV, 0);
+	if (ks == NULL)
+		return;
+
+	kvs = mallocarray(nitems(mtsw_mib_kstats), sizeof(*kvs), M_DEVBUF,
+	    M_WAITOK|M_ZERO);
+
+	for (i = 0; i < nitems(mtsw_mib_kstats); i++) {
+		const struct mtsw_mib_kstat *mib = &mtsw_mib_kstats[i];
+
+		kstat_kv_unit_init(&kvs[i], mib->mib_name,
+		    mib->mib_type, mib->mib_unit);
+	}
+
+	kstat_set_wlock(ks, &sc->sc_kstat_lock);
+	ks->ks_softc = sc;
+	ks->ks_ptr = (void *)(intptr_t)port;
+	ks->ks_data = kvs;
+	ks->ks_datalen = nitems(mtsw_mib_kstats) * sizeof(*kvs);
+	ks->ks_read = mtsw_kstat_read_mib;
+	ks->ks_interval = ival;
+
+	kstat_install(ks);
+}
+
+#endif /* NKSTAT > 0 */
Index: fdt/mvmdio.c
===================================================================
RCS file: /cvs/src/sys/dev/fdt/mvmdio.c,v
retrieving revision 1.4
diff -u -p -r1.4 mvmdio.c
--- fdt/mvmdio.c	24 Oct 2021 17:52:26 -0000	1.4
+++ fdt/mvmdio.c	24 Apr 2023 02:08:20 -0000
@@ -36,12 +36,6 @@
 #include <machine/bus.h>
 #include <machine/fdt.h>
 
-#ifdef __armv7__
-#include <arm/simplebus/simplebusvar.h>
-#else
-#include <arm64/dev/simplebusvar.h>
-#endif
-
 #include <dev/ofw/openfirm.h>
 #include <dev/ofw/ofw_clock.h>
 #include <dev/ofw/ofw_pinctrl.h>
@@ -49,6 +43,7 @@
 #include <dev/ofw/fdt.h>
 
 #include <dev/fdt/if_mvnetareg.h>
+#include <dev/fdt/mdiovar.h>
 
 #include <net/if.h>
 
@@ -58,7 +53,7 @@
 	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
 
 struct mvmdio_softc {
-	struct simplebus_softc sc_sbus;
+	struct device sc_dev;
 
 	bus_space_tag_t sc_iot;
 	bus_space_handle_t sc_ioh;
@@ -118,7 +113,7 @@ mvmdio_attach(struct device *parent, str
 	sc->sc_mii.md_writereg = mvmdio_smi_writereg;
 	mii_register(&sc->sc_mii);
 
-	simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa);
+	mdio_attach(self, &sc->sc_mii, faa->fa_node);
 }
 
 int
@@ -136,7 +131,7 @@ mvmdio_smi_readreg(struct device *dev, i
 			break;
 	}
 	if (i == MVNETA_PHY_TIMEOUT) {
-		printf("%s: SMI busy timeout\n", sc->sc_sbus.sc_dev.dv_xname);
+		printf("%s: SMI busy timeout\n", sc->sc_dev.dv_xname);
 		mtx_leave(&sc->sc_mtx);
 		return -1;
 	}
@@ -174,7 +169,7 @@ mvmdio_smi_writereg(struct device *dev, 
 			break;
 	}
 	if (i == MVNETA_PHY_TIMEOUT) {
-		printf("%s: SMI busy timeout\n", sc->sc_sbus.sc_dev.dv_xname);
+		printf("%s: SMI busy timeout\n", sc->sc_dev.dv_xname);
 		mtx_leave(&sc->sc_mtx);
 		return;
 	}
@@ -192,5 +187,5 @@ mvmdio_smi_writereg(struct device *dev, 
 	mtx_leave(&sc->sc_mtx);
 
 	if (i == MVNETA_PHY_TIMEOUT)
-		printf("%s: phy write timed out\n", sc->sc_sbus.sc_dev.dv_xname);
+		printf("%s: phy write timed out\n", sc->sc_dev.dv_xname);
 }
Index: fdt/mvsw.c
===================================================================
RCS file: /cvs/src/sys/dev/fdt/mvsw.c,v
retrieving revision 1.5
diff -u -p -r1.5 mvsw.c
--- fdt/mvsw.c	6 Apr 2022 18:59:28 -0000	1.5
+++ fdt/mvsw.c	24 Apr 2023 02:08:21 -0000
@@ -15,10 +15,19 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "bpfilter.h"
+
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
 #include <sys/timeout.h>
+#include <sys/percpu.h>
 
 #include <machine/bus.h>
 #include <machine/fdt.h>
@@ -26,8 +35,33 @@
 #include <dev/ofw/openfirm.h>
 #include <dev/ofw/ofw_misc.h>
 #include <dev/ofw/fdt.h>
+#include <dev/fdt/mdiovar.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
 
 #include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+
+#include <netinet6/in6_var.h>
+#include <netinet/ip6.h>
+
+#include <crypto/siphash.h> /* if_trunk.h uses siphash bits */
+#include <net/if_trunk.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#define MVSW_MAX_PORTS			11
+
+#define ETHERTYPE_MVSW_ETAG		ETHERTYPE_802_EX1
+#define ETHERTYPE_MVSW_DEFAULT		0x9000 /* who cares? */
 
 /* Registers */
 
@@ -49,34 +83,303 @@
 #define MVSW_SMI_TIMEOUT	1600
 
 /* Switch registers */
-#define MVSW_PORT(x)			(0x10 + (x))
-#define MVSW_G2				0x1c
+#define MVSW_PORT(x)			(0x10 + (x))	/* Port */
+#define MVSW_G1				0x1b		/* Global1 */
+#define MVSW_G2				0x1c		/* Global2 */
 
+/*
+ * Port registers */
 #define MVSW_PORT_SWITCHID		0x03
 #define  MVSW_PORT_SWITCHID_PROD_MASK	0xfff0
 #define  MVSW_PORT_SWITCHID_PROD_88E6141 0x3400
 #define  MVSW_PORT_SWITCHID_PROD_88E6341 0x3410
 #define  MVSW_PORT_SWITCHID_REV_MASK	0x000f
-#define MVSW_PORT_CTRL			0x04
-#define  MVSW_PORT_CTRL_STATE_MASK	0x0003
-#define  MVSW_PORT_CTRL_STATE_FORWARD	0x0003
+#define MVSW_PORT_CTRL0			0x04
+#define  MVSW_PORT_CTRL0_STATE_MASK			(0x3 << 0)
+#define  MVSW_PORT_CTRL0_STATE_DISABLED			 (0x0 << 0)
+#define  MVSW_PORT_CTRL0_STATE_BLOCKING			 (0x1 << 0)
+#define  MVSW_PORT_CTRL0_STATE_LEARNING			 (0x2 << 0)
+#define  MVSW_PORT_CTRL0_STATE_FORWARD			 (0x3 << 0)
+#define  MVSW_PORT_CTRL0_EGRESS_FLOOD_MCAST		(0x1 << 2)
+#define  MVSW_PORT_CTRL0_EGRESS_FLOOD_UCAST		(0x1 << 3)
+#define  MVSW_PORT_CTRL0_TAG_IF_BOTH			(0x1 << 6)
+#define  MVSW_PORT_CTRL0_VLAN_TUNNEL			(0x1 << 7)
+#define  MVSW_PORT_CTRL0_FRAME_MODE_MASK		(0x3 << 8)
+#define  MVSW_PORT_CTRL0_FRAME_MODE_NORMAL		 (0x0 << 8)
+#define  MVSW_PORT_CTRL0_FRAME_MODE_TAG			 (0x1 << 8)
+#define  MVSW_PORT_CTRL0_FRAME_MODE_PROVIDER		 (0x2 << 8)
+#define  MVSW_PORT_CTRL0_FRAME_MODE_ETAG		 (0x3 << 8)
+#define  MVSW_PORT_CTRL0_IGMP_MLD_SNOOP			(0x1 << 10)
+#define  MVSW_PORT_CTRL0_HEADER				(0x1 << 11)
+#define  MVSW_PORT_CTRL0_EGRESS_MODE_MASK		(0x3 << 12)
+#define  MVSW_PORT_CTRL0_EGRESS_MODE_UNMODIFIED		 (0x0 << 12)
+#define  MVSW_PORT_CTRL0_EGRESS_MODE_UNTAGGED		 (0x1 << 12)
+#define  MVSW_PORT_CTRL0_EGRESS_MODE_TAGGED		 (0x2 << 12)
+#define  MVSW_PORT_CTRL0_EGRESS_MODE_ETAG		 (0x3 << 12)
+#define  MVSW_PORT_CTRL0_SAFILTER_MASK			(0x3 << 14)
+#define  MVSW_PORT_CTRL0_SAFILTER_DROP_ON_LOCK		 (0x1 << 14)
+#define  MVSW_PORT_CTRL0_SAFILTER_DROP_ON_UNLOCK	 (0x2 << 14)
+#define  MVSW_PORT_CTRL0_SAFILTER_DROP_TO_CPU		 (0x3 << 14)
+
+#define MVSW_PORT_CTRL1			0x05
+#define  MVSW_PORT_CTRL1_FID_HI_SHIFT			0
+#define  MVSW_PORT_CTRL1_FID_HI_MASK			0xff
+#define  MVSW_PORT_CTRL1_TRUNK_ID_SHIFT			8
+#define  MVSW_PORT_CTRL1_TRUNK_ID_MASK			0x0f
+#define  MVSW_PORT_CTRL1_TRUNK_PORT			(0x1 << 14)
+#define  MVSW_PORT_CTRL1_MESSAGE_PORT			(0x1 << 15)
+
+#define MVSW_PORT_BASED_VLAN		0x06
+#define  MVSW_PORT_BASED_VLAN_FID_LO_SHIFT		0
+#define  MVSW_PORT_BASED_VLAN_FID_LO_MASK		0
+
+/* Default Port VLAN */
+#define MVSW_PORT_DEFAULT_VLAN		0x07
+#define  MVSW_PORT_DEVAULT_VLAN_VID_SHIFT		0
+#define  MVSW_PORT_DEVAULT_VLAN_VID_MASK		0xfff
+
+/* Port Control 2 */
+#define MVSW_PORT_CTRL2			0x08
+#define  MVSW_PORT_CTRL2_JUMBO_MODE_MASK		(0x3 << 12)
+#define  MVSW_PORT_CTRL2_JUMBO_MODE_1522		(0x0 << 12)
+#define  MVSW_PORT_CTRL2_JUMBO_MODE_2048		(0x1 << 12)
+#define  MVSW_PORT_CTRL2_JUMBO_MODE_10240		(0x2 << 12)
+#define  MVSW_PORT_CTRL2_8021Q_MODE_MASK		(0x3 << 10)
+#define  MVSW_PORT_CTRL2_8021Q_MODE_DISABLED		(0x0 << 10)
+#define  MVSW_PORT_CTRL2_8021Q_MODE_FALLBACK		(0x1 << 10)
+#define  MVSW_PORT_CTRL2_8021Q_MODE_CHECK		(0x2 << 10)
+#define  MVSW_PORT_CTRL2_8021Q_MODE_SECURE		(0x3 << 10)
+#define  MVSW_PORT_CTRL2_DISCARD_TAGGED			(0x1 << 9)
+#define  MVSW_PORT_CTRL2_DISCARD_UNTAGGED		(0x1 << 8)
+#define  MVSW_PORT_CTRL2_MAP_DA				(0x1 << 7)
+
+/* Port Association Vector */
+#define MVSW_PORT_ASSOC_VECTOR		0x0b
+#define  MVSW_PORT_ASSOC_VECTOR_HOLD_AT_1		(0x1 << 15)
+#define  MVSW_PORT_ASSOC_VECTOR_INT_AGE_OUT		(0x1 << 14)
+#define  MVSW_PORT_ASSOC_VECTOR_LOCKED_PORT		(0x1 << 13)
+#define  MVSW_PORT_ASSOC_VECTOR_IGNORE_WRONG		(0x1 << 12)
+#define  MVSW_PORT_ASSOC_VECTOR_REFRESH_LOCKED		(0x1 << 11)
+/* i think low bits are a bitmap of relevant ports */
+
+#define MVSW_PORT_ETH_TYPE		0x0f
+
+/*
+ * Global1 registers
+ */
+
+/* ATU FID */
+#define MVSW_G1_ATU_FID			0x01
+
+#define MVSW_G1_VTU_OP			0x05
+#define  MVSW_G1_VTU_OP_BUSY				(0x1 << 15)
+#define  MVSW_G1_VTU_OP_MASK				(0x7 << 12)
+#define  MVSW_G1_VTU_OP_FLUSH_ALL			(0x1 << 12)
+#define  MVSW_G1_VTU_OP_NOOP				(0x2 << 12)
+#define  MVSW_G1_VTU_OP_VTU_LOAD_PURGE			(0x3 << 12)
+#define  MVSW_G1_VTU_OP_VTU_GET_NEXT			(0x4 << 12)
+#define  MVSW_G1_VTU_OP_STU_LOAD_PURGE			(0x5 << 12)
+#define  MVSW_G1_VTU_OP_STU_GET_NEXT			(0x6 << 12)
+#define  MVSW_G1_VTU_OP_GET_CLR_VIOLATION		(0x7 << 12)
+#define  MVSW_G1_VTU_OP_MEMBER_VIOLATION		(0x1 << 6)
+#define  MVSW_G1_VTU_OP_MISS_VIOLATION			(0x1 << 5)
+#define  MVSW_G1_VTU_OP_SPID_MASK			(0xf << 0)
+
+/* ATU Control */
+#define MVSW_G1_ATU_CTRL		0x0a
+#define  MVSW_G1_ATU_CTRL_LEARN2ALL			(0x1 << 3)
+
+/* ATU Operation */
+#define MVSW_G1_ATU_OP			0x0a
+#define  MVSW_G1_ATU_OP_BUSY				(0x1 << 15)
+#define  MVSW_G1_ATU_OP_MASK				(0x7 << 12)
+#define  MVSW_G1_ATU_OP_NOOP				(0x0 << 12)
+#define  MVSW_G1_ATU_OP_FLUSH_MOVE_ALL			(0x1 << 12)
+#define  MVSW_G1_ATU_OP_FLUSH_MOVE_NON_STATIC		(0x2 << 12)
+#define  MVSW_G1_ATU_OP_LOAD_DB				(0x3 << 12)
+#define  MVSW_G1_ATU_OP_GET_NEXT_DB			(0x4 << 12)
+#define  MVSW_G1_ATU_OP_FLUSH_MOVE_ALL_DB		(0x5 << 12)
+#define  MVSW_G1_ATU_OP_FLUSH_MOVE_NON_STATIC_DB	(0x6 << 12)
+#define  MVSW_G1_ATU_OP_GET_CLR_VIOLATION		(0x7 << 12)
+#define  MVSW_G1_ATU_OP_AGE_OUT_VIOLATION		(0x1 << 7)
+#define  MVSW_G1_ATU_OP_MEMBER_VIOLATION		(0x1 << 6)
+#define  MVSW_G1_ATU_OP_MISS_VIOLATION			(0x1 << 5)
+#define  MVSW_G1_ATU_OP_FULL_VIOLATION			(0x1 << 4)
+
+/* ATU Data */
+#define MVSW_G1_ATU_DATA		0x0c
+#define  MVSW_G1_ATU_DATA_TRUNK 			(0x1 << 15)
+#define  MVSW_G1_ATU_DATA_TRUNK_ID_MASK			(0xf << 4)
+#define  MVSW_G1_ATU_DATA_PORT_VECTOR_MASK		(0x3ff << 4)
+#define  MVSW_G1_ATU_DATA_STATE_MASK			(0xf << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_UNUSED		(0x0 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_1_OLDEST		(0x1 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_2		(0x2 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_3		(0x3 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_4		(0x4 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_5		(0x5 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_6		(0x6 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_AGE_7_NEWEST		(0x7 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_POLICY	(0x8 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_POLICY_PO	(0x9 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL	(0xa << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL_PO	(0xb << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT	(0xc << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT_PO	(0xd << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC		(0xe << 0)
+#define  MVSW_G1_ATU_DATA_STATE_UC_STATIC_PO		(0xf << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_UNUSED		(0x0 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_POLICY	(0x4 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL	(0x5 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT	(0x6 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC		(0x7 << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_POLICY_PO	(0xc << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL_PO	(0xd << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT_PO	(0xe << 0)
+#define  MVSW_G1_ATU_DATA_STATE_MC_STATIC_PO		(0xf << 0)
+
+#define MVSW_G1_ATU_MAC_BASE		0x0d
+#define MVSW_G1_ATU_MAC_01		(MVSW_G1_ATU_MAC_BASE + 0)
+#define MVSW_G1_ATU_MAC_23		(MVSW_G1_ATU_MAC_BASE + 1)
+#define MVSW_G1_ATU_MAC_45		(MVSW_G1_ATU_MAC_BASE + 2)
+
+/* Monitor & MGMT Control */
+#define MVSW_G1_MONITOR_MGMT_CTL	0x1a
+#define  MVSW_G1_MONITOR_MGMT_CTL_UPDATE		(0x1 << 15)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_MASK		(0x3f << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_0180C200000XLO	(0x00 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_0180C200000XHI	(0x01 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_0180C200002XLO	(0x02 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_0180C200002XHI	(0x03 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_INGRESS_DEST	(0x20 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_EGRESS_DEST	(0x21 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST		(0x30 << 8)
+#define  MVSW_G1_MONITOR_MGMT_CTL_DATA_SHIFT		0
+#define  MVSW_G1_MONITOR_MGMT_CTL_DATA_MASK		0xff
+#define  MVSW_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST_MGMTPRI	0xe0
+
+/* Control2 */
+#define MVSW_G1_CTRL2			0x1c
+#define  MVSW_G1_CTRL2_DEVICE_NUMBER_SHIFT		0
+#define  MVSW_G1_CTRL2_DEVICE_NUMBER_MASK		0x1f
+#define  MVSW_G1_CTRL2_RMU_MODE_MASK			(0x7 << 8)
+#define  MVSW_G1_CTRL2_RMU_MODE_PORT_0			(0x0 << 8)
+#define  MVSW_G1_CTRL2_RMU_MODE_PORT_1			(0x1 << 8)
+#define  MVSW_G1_CTRL2_RMU_MODE_ALL_DSA			(0x6 << 8)
+#define  MVSW_G1_CTRL2_RMU_MODE_DISABLED		(0x7 << 8)
+
+/*
+ * Global2 registers
+ */
+
+/* Trunk Mask Table */
+#define MVSW_G2_TRUNK_MASK		0x07
+#define  MVSW_G2_TRUNK_MASK_UPDATE			(0x1 << 15)
+#define  MVSW_G2_TRUNK_MASK_SHIFT			12
+#define  MVSW_G2_TRUNK_MASK_COUNT			8 /* 0x0 to 0x7 */
+#define  MVSW_G2_TRUNK_MASK_HASH			(0x1 << 11)
+/* low bits are a bitmap of ports in the trunk i think */
+
+/* Trunk Mapping Table */
+#define MVSW_G2_TRUNK_MAPPING		0x08
+#define  MVSW_G2_TRUNK_MAPPING_UPDATE			(0x1 << 15)
+#define  MVSW_G2_TRUNK_MAPPING_ID_SHIFT			11
+#define  MVSW_G2_TRUNK_MAPPING_ID_COUNT			16 /* 0x0 to 0xf */
+/* low bits are a bitmap of ports in the trunk i think */
+
+/* Ingress Rate Command */
+#define MVSW_G2_IRL_CMD			0x09
+#define  MVSW_G2_IRL_CMD_BUSY				(0x1 << 15)
+#define  MVSW_G2_IRL_CMD_OP_MASK			(0x7 << 12)
+#define  MVSW_G2_IRL_CMD_OP_NOOP			(0x0 << 12)
+#define  MVSW_G2_IRL_CMD_OP_INIT_ALL			(0x1 << 12)
+#define  MVSW_G2_IRL_CMD_OP_INIT_RES			(0x2 << 12)
+#define  MVSW_G2_IRL_CMD_OP_WRITE_REG			(0x3 << 12)
+#define  MVSW_G2_IRL_CMD_OP_READ_REG			(0x4 << 12)
+#define  MVSW_G2_IRL_CMD_PORT_SHIFT			8
+#define  MVSW_G2_IRL_CMD_PORT_MASK			0xf
+#define  MVSW_G2_IRL_CMD_RES_MASK			(0x7 << 5)
+#define  MVSW_G2_IRL_CMD_REG_MASK			(0xf << 0)
+
+/* Ingress Rate Data */
+#define MVSW_G2_IRL_DATA		0x0a
+
 #define MVSW_G2_SMI_PHY_CMD		0x18
 #define MVSW_G2_SMI_PHY_DATA		0x19
 
+/* Misc */
+#define MVSW_G2_MISC			0x1d
+#define  MVSW_G2_MISC_5BIT_PORT				(0x1 << 14)
+
 /* SERDES registers */
 #define MVSW_SERDES(x)			(0x10 + (x))
 #define MVSW_SERDES_BMCR		(0x2000 + MII_BMCR)
 
+struct mvsw_tag {
+	uint16_t		tag0;
+#define MVSW_TAG_MODE_SHIFT		14
+#define MVSW_TAG_MODE_MASK		(0x3 << MVSW_TAG_MODE_SHIFT) 
+#define MVSW_TAG_MODE_TO_CPU		(0x0 << MVSW_TAG_MODE_SHIFT) 
+#define MVSW_TAG_MODE_FROM_CPU		(0x1 << MVSW_TAG_MODE_SHIFT) 
+#define MVSW_TAG_MODE_TO_SNIFFER	(0x2 << MVSW_TAG_MODE_SHIFT) 
+#define MVSW_TAG_MODE_TAG		(0x3 << MVSW_TAG_MODE_SHIFT) 
+
+#define MVSW_TAG_IEEE			(1 << 13)
+
+#define MVSW_TAG_SWITCH_SHIFT		8
+#define MVSW_TAG_SWITCH_MASK		0x1f
+
+#define MVSW_TAG_PORT_SHIFT		3
+#define MVSW_TAG_PORT_MASK		0x1f
+
+	uint16_t		tag1;
+};
+
+struct mvsw_etag {
+	uint16_t		reserved;
+	uint16_t		tag0;
+	uint16_t		tag1;
+};
+
 /* XXX #include <dev/mii/mdio.h> */
 #define MDIO_MMD_PHYXS		4
 
+/*
+ * The driver.
+ */
+
+struct mvsw_port {
+	int			 p_port;
+	struct mvsw_softc	*p_softc;
+	struct ifnet		*p_ifp0;
+
+	int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
+	void (*p_input)(struct ifnet *, struct mbuf *);
+	int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
+	    struct rtentry *);
+
+	TAILQ_ENTRY(mvsw_port)	 p_entry;
+};
+TAILQ_HEAD(mvsw_ports, mvsw_port);
+
+struct mvsport_softc;
+
 struct mvsw_softc {
-	struct device	sc_dev;
+	struct device		 sc_dev;
+
+	int			 sc_node;
+	struct mii_bus		*sc_mdio;
+	int 			 sc_reg;
 
-	struct mii_bus	*sc_mdio;
-	int 		sc_reg;
+	unsigned int		 sc_nports;
+	struct mvsport_softc	*sc_ports[MVSW_MAX_PORTS];
+	struct mvsw_ports	 sc_cpus;
+
+	caddr_t			 sc_bpf;
 };
 
+#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname)
+
 int	mvsw_match(struct device *, void *, void *);
 void	mvsw_attach(struct device *, struct device *, void *);
 
@@ -88,6 +391,26 @@ struct cfdriver mvsw_cd = {
 	NULL, "mvsw", DV_DULL
 };
 
+struct mvsw_defer {
+	struct task		 d_task;
+	struct mvsw_softc	*d_sc;
+};
+
+static void	mvsw_attach_deferred(void *);
+
+static void	mvsw_attach_cpu(struct mvsw_softc *, int, uint32_t);
+static void	mvsw_config_cpu(struct mvsw_softc *, struct mvsw_port *);
+
+static int	mvsw_p_ioctl(struct ifnet *, u_long, caddr_t);
+static void	mvsw_p_input(struct ifnet *, struct mbuf *);
+static int	mvsw_p_output(struct ifnet *, struct mbuf *,
+		    struct sockaddr *, struct rtentry *);
+
+static int	mvsw_print(void *, const char *);
+
+static struct mbuf *
+		mvsport_input(struct mvsport_softc *, struct mbuf *);
+
 int	mvsw_smi_read(struct mvsw_softc *, int, int);
 void	mvsw_smi_write(struct mvsw_softc *, int, int, int);
 int	mvsw_phy_read(struct mvsw_softc *, int, int);
@@ -95,59 +418,181 @@ void	mvsw_phy_write(struct mvsw_softc *,
 int	mvsw_serdes_read(struct mvsw_softc *, int, int, int);
 void	mvsw_serdes_write(struct mvsw_softc *, int, int, int, int);
 
-void	mvsw_port_enable(struct mvsw_softc *, int);
+static int	mvsw_wait(struct mvsw_softc *, int, int, uint16_t, uint16_t,
+		    const char *);
+
+#define mvsw_vtu_wait(_sc) \
+	mvsw_wait((_sc), MVSW_G1, MVSW_G1_VTU_OP, \
+	    MVSW_G1_VTU_OP_BUSY, 0, "mvswvtu")
+
+#define mvsw_atu_wait(_sc) \
+	mvsw_wait((_sc), MVSW_G1, MVSW_G1_ATU_OP, \
+	    MVSW_G1_ATU_OP_BUSY, 0, "mvswatu")
+
+#define mvsw_irl_wait(_sc) \
+	mvsw_wait((_sc), MVSW_G2, MVSW_G2_IRL_CMD, \
+	    MVSW_G2_IRL_CMD_BUSY, 0, "mvswirl")
+
+static int	mvsw_vtu_op(struct mvsw_softc *, uint16_t);
+static int	mvsw_atu_op(struct mvsw_softc *, uint16_t, uint16_t, uint16_t);
+static int	mvsw_irl_op(struct mvsw_softc *, uint16_t);
+
 void	mvsw_phy_enable(struct mvsw_softc *, int);
 void	mvsw_serdes_enable(struct mvsw_softc *, int);
 
 int
 mvsw_match(struct device *parent, void *match, void *aux)
 {
-	struct fdt_attach_args *faa = aux;
+	struct mdio_attach_args *maa = aux;
 
-	return OF_is_compatible(faa->fa_node, "marvell,mv88e6085");
+	return OF_is_compatible(maa->maa_node, "marvell,mv88e6085");
 }
 
 void
 mvsw_attach(struct device *parent, struct device *self, void *aux)
 {
 	struct mvsw_softc *sc = (struct mvsw_softc *)self;
-	struct fdt_attach_args *faa = aux;
-	int ports, port, node;
-	uint32_t phy;
-	uint16_t swid;
-
-	if (faa->fa_nreg < 1) {
-		printf(": no registers\n");
-		return;
-	}
-
-	sc->sc_reg = faa->fa_reg[0].addr;
-	printf(" phy %d", sc->sc_reg);
+	struct mdio_attach_args *maa = aux;
+	uint16_t r;
+	struct mvsw_defer *d;
+
+	TAILQ_INIT(&sc->sc_cpus);
+	sc->sc_nports = nitems(sc->sc_ports);
+
+	sc->sc_node = maa->maa_node;
+	sc->sc_reg = maa->maa_addr;
+	sc->sc_mdio = maa->maa_bus;
 
-	sc->sc_mdio = mii_bynode(OF_parent(faa->fa_node));
-	if (sc->sc_mdio == NULL) {
-		printf(": can't find mdio bus\n");
-		return;
-	}
-
-	swid = mvsw_smi_read(sc, MVSW_PORT(0), MVSW_PORT_SWITCHID);
-	switch (swid & MVSW_PORT_SWITCHID_PROD_MASK) {
+	r = mvsw_smi_read(sc, MVSW_PORT(0), MVSW_PORT_SWITCHID);
+	switch (r & MVSW_PORT_SWITCHID_PROD_MASK) {
 	case MVSW_PORT_SWITCHID_PROD_88E6141:
+		sc->sc_nports = 6;
 		printf(": 88E6141");
 		break;
 	case MVSW_PORT_SWITCHID_PROD_88E6341:
+		sc->sc_nports = 6;
 		printf(": 88E6341");
 		break;
 	default:
 		printf(": unknown product 0x%04x\n",
-		   swid & MVSW_PORT_SWITCHID_PROD_MASK);
+		    r & MVSW_PORT_SWITCHID_PROD_MASK);
 		return;
 	}
-	printf(" rev %d\n", swid & MVSW_PORT_SWITCHID_REV_MASK);
+	printf(" rev %d\n", r & MVSW_PORT_SWITCHID_REV_MASK);
 
-	ports = OF_getnodebyname(faa->fa_node, "ports");
-	if (ports == 0)
+	if (sc->sc_dev.dv_unit & ~MVSW_G1_CTRL2_DEVICE_NUMBER_MASK) {
+		printf("%s: too many switches\n", DEVNAME(sc));
 		return;
+	}
+
+	/*
+	 * wait until the cpu port is (probably) attached to wire things up.
+	 */
+
+	d = malloc(sizeof(*d), M_TEMP, M_WAITOK);
+	task_set(&d->d_task, mvsw_attach_deferred, d);
+	d->d_sc = sc;
+
+        config_pending_incr();
+	task_add(systq, &d->d_task);
+}
+
+static void
+mvsw_attach_deferred(void *arg)
+{
+	struct mvsw_defer *d = arg;
+	struct mvsw_softc *sc = d->d_sc;
+	int ports, port, node, i;
+	uint32_t phy, phandle;
+	uint16_t r;
+	struct mvsw_port *p;
+
+	free(d, M_TEMP, sizeof(*d));
+
+	for (port = 0; port < sc->sc_nports; port++) {
+		/* start with all ports disabled */
+		r = mvsw_smi_read(sc, MVSW_PORT(port), MVSW_PORT_CTRL0);
+		CLR(r, MVSW_PORT_CTRL0_STATE_MASK);
+		SET(r, MVSW_PORT_CTRL0_STATE_DISABLED);
+		mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_CTRL0, r);
+
+		r = mvsw_smi_read(sc, MVSW_PORT(port), MVSW_PORT_CTRL1);
+		CLR(r, MVSW_PORT_CTRL1_MESSAGE_PORT);
+		mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_CTRL1, r);
+
+		mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_DEFAULT_VLAN, 0);
+
+		/* reset ingress rate limiting (IRL) */
+		if (mvsw_irl_op(sc, MVSW_G2_IRL_CMD_OP_INIT_ALL |
+		    (port << MVSW_G2_IRL_CMD_PORT_SHIFT)) == -1) {
+			printf("%s: unable to reset ingress rate limiting "
+			    "on port %u\n", DEVNAME(sc), port);
+			/* we can carry on */
+		}
+	}
+
+	/* flush the vlan translation unit */
+	if (mvsw_vtu_wait(sc) == -1) {
+		printf("%s: VLAN Translation Unit busy\n", DEVNAME(sc));
+		goto done;
+	}
+
+	if (mvsw_vtu_op(sc, MVSW_G1_VTU_OP_FLUSH_ALL) == -1) {
+		printf("%s: VLAN Translation Unit flush timeout\n",
+		    DEVNAME(sc));
+		goto done;
+	}
+
+	/* clear 5 bit port use in port vlan table (PVT) */
+	r = mvsw_smi_read(sc, MVSW_G2, MVSW_G2_MISC);
+	CLR(r, MVSW_G2_MISC_5BIT_PORT);
+	mvsw_smi_write(sc, MVSW_G2, MVSW_G2_MISC, r);
+
+	/* XXX PVT clear/reset/setup? */
+
+	/* flush the address translation unit */
+	if (mvsw_atu_wait(sc) == -1) {
+		printf("%s: Address Translation Unit busy\n", DEVNAME(sc));
+		goto done;
+	}
+
+	if (mvsw_atu_op(sc, 0, MVSW_G1_ATU_OP_FLUSH_MOVE_ALL, 0) == -1) {
+		printf("%s: Address Translation Unit flush timeout\n",
+		    DEVNAME(sc));
+		goto done;
+	}
+
+	/* XXX clear priority overrite table */
+
+	r = mvsw_smi_read(sc, MVSW_G1, MVSW_G1_CTRL2);
+	/* set device number */
+	CLR(r, MVSW_G1_CTRL2_DEVICE_NUMBER_MASK <<
+	    MVSW_G1_CTRL2_DEVICE_NUMBER_SHIFT);
+	SET(r, sc->sc_dev.dv_unit <<
+	    MVSW_G1_CTRL2_DEVICE_NUMBER_SHIFT);
+
+	/* disable remote management */
+	CLR(r, MVSW_G1_CTRL2_RMU_MODE_MASK);
+	SET(r, MVSW_G1_CTRL2_RMU_MODE_DISABLED);
+	mvsw_smi_write(sc, MVSW_G1, MVSW_G1_CTRL2, r);
+
+	/* clear trunk setup */
+	for (i = 0; i < MVSW_G2_TRUNK_MASK_COUNT; i++) {
+//		mvsw_smi_write(sc, MVSW_G2, MVSW_G2_TRUNK_MASK,
+//		    MVSW_G2_TRUNK_MASK_UPDATE |
+//		    (i << MVSW_G2_TRUNK_MASK_SHIFT) | /* clear bitmap */ 0);
+	}
+	for (i = 0; i < MVSW_G2_TRUNK_MAPPING_ID_COUNT; i++) {
+//		mvsw_smi_write(sc, MVSW_G2, MVSW_G2_TRUNK_MAPPING,
+//		    MVSW_G2_TRUNK_MAPPING_UPDATE |
+//		    (i << MVSW_G2_TRUNK_MAPPING_ID_SHIFT) |
+//		    /* clear bitmap */ 0);
+	}
+
+	ports = OF_getnodebyname(sc->sc_node, "ports");
+	if (ports == 0)
+		goto done;
+
 	for (port = OF_child(ports); port; port = OF_peer(port)) {
 		phy = OF_getpropint(port, "phy-handle", 0);
 		node = OF_getnodebyphandle(phy);
@@ -156,8 +601,274 @@ mvsw_attach(struct device *parent, struc
 		else
 			mvsw_serdes_enable(sc, port);
 
-		mvsw_port_enable(sc, port);
+		phandle = OF_getpropint(port, "ethernet", 0);
+		if (phandle != 0)
+			mvsw_attach_cpu(sc, port, phandle);
+		else {
+			uint32_t reg = OF_getpropint(port, "reg",
+			    MVSW_MAX_PORTS);
+			struct device *child;
+
+			if (reg < sc->sc_nports) {
+				child = config_found(&sc->sc_dev, &port,
+				    mvsw_print);
+				sc->sc_ports[reg] =
+				    (struct mvsport_softc *)child;
+			}
+		}
+	}
+
+	p = TAILQ_FIRST(&sc->sc_cpus);
+	if (p == NULL) {
+		printf("%s: no CPU ports found\n", DEVNAME(sc));
+		goto done;
 	}
+
+	mvsw_config_cpu(sc, p);
+
+	r = 0x1 << p->p_port;
+	for (port = 0; port < sc->sc_nports; port++) {
+		if (sc->sc_ports[port] == NULL)
+			continue;
+
+		mvsw_smi_write(sc, MVSW_PORT(port),
+		    MVSW_PORT_BASED_VLAN, r);
+	}
+
+done:
+        config_pending_decr();
+}
+
+static void
+mvsw_attach_cpu(struct mvsw_softc *sc, int node, uint32_t phandle)
+{
+	struct ifnet *ifp0;
+	struct arpcom *ac0;
+	struct mvsw_port *p;
+	int port;
+	uint16_t r;
+
+	port = OF_getpropint(node, "reg", -1);
+	if (port == -1) {
+		printf("%s: can't find cpu interface port number\n",
+		    DEVNAME(sc));
+		return;
+	}
+
+	ifp0 = if_byphandle(phandle);
+	if (ifp0 == NULL) {
+		printf("%s: unable to find cpu interface on port %u\n",
+		    DEVNAME(sc), port);
+		return;
+	}
+
+	if (ifp0->if_type != IFT_ETHER) {
+		printf("%s: unsupported type of cpu interface on port %u\n",
+		    DEVNAME(sc), port);
+		return;
+	}
+
+	printf("%s: %s at port %u\n", DEVNAME(sc), ifp0->if_xname, port);
+
+	NET_LOCK();
+	ac0 = (struct arpcom *)ifp0;
+	if (ac0->ac_trunkport != NULL) {
+		printf("%s: cpu interface %s is busy\n",
+		    DEVNAME(sc), ifp0->if_xname);
+		NET_UNLOCK();
+		return;
+	}
+
+	p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK);
+
+	p->p_softc = sc;
+	p->p_ifp0 = ifp0;
+	p->p_port = port;
+	p->p_ioctl = ifp0->if_ioctl;
+	p->p_input = ifp0->if_input;
+	p->p_output = ifp0->if_output;
+
+	TAILQ_INSERT_TAIL(&sc->sc_cpus, p, p_entry);
+
+	if (ifpromisc(ifp0, 1) != 0)
+		printf("%s: %s promisc error\n", DEVNAME(sc), ifp0->if_xname);
+
+	ac0->ac_trunkport = p;
+	/* membar_producer()? */
+	ifp0->if_ioctl = mvsw_p_ioctl;
+	ifp0->if_input = mvsw_p_input;
+	ifp0->if_output = mvsw_p_output;
+	NET_UNLOCK();
+
+	/* Enable port. */
+	r = mvsw_smi_read(sc, MVSW_PORT(port), MVSW_PORT_CTRL0);
+	CLR(r, MVSW_PORT_CTRL0_STATE_MASK);
+	SET(r, MVSW_PORT_CTRL0_STATE_FORWARD);
+	CLR(r, MVSW_PORT_CTRL0_FRAME_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL0_FRAME_MODE_ETAG);
+	CLR(r, MVSW_PORT_CTRL0_EGRESS_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL0_EGRESS_MODE_ETAG);
+	SET(r, MVSW_PORT_CTRL0_EGRESS_FLOOD_UCAST |
+	    MVSW_PORT_CTRL0_EGRESS_FLOOD_MCAST);
+	mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_CTRL0, r);
+
+	r = mvsw_smi_read(sc, MVSW_PORT(port), MVSW_PORT_CTRL2);
+	CLR(r, MVSW_PORT_CTRL2_MAP_DA);
+	CLR(r, MVSW_PORT_CTRL2_JUMBO_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL2_JUMBO_MODE_10240);
+	CLR(r, MVSW_PORT_CTRL2_8021Q_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL2_8021Q_MODE_DISABLED);
+	CLR(r, MVSW_PORT_CTRL2_DISCARD_TAGGED);
+	CLR(r, MVSW_PORT_CTRL2_DISCARD_UNTAGGED);
+	mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_CTRL2, r);
+
+	mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_ASSOC_VECTOR,
+	    0x1 << port);
+
+	mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_ETH_TYPE,
+	    ETHERTYPE_MVSW_ETAG);
+}
+
+static void
+mvsw_config_cpu(struct mvsw_softc *sc, struct mvsw_port *p)
+{
+	int port = p->p_port;
+	uint16_t r;
+
+	/* tell the switch this is the cpu port */
+	r = MVSW_G1_MONITOR_MGMT_CTL_UPDATE |
+	    MVSW_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST |
+	    (port | MVSW_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST_MGMTPRI);
+	mvsw_smi_write(sc, MVSW_G2, MVSW_G1_MONITOR_MGMT_CTL, r);
+
+	r = MVSW_G1_MONITOR_MGMT_CTL_UPDATE |
+	    MVSW_G1_MONITOR_MGMT_CTL_PTR_INGRESS_DEST |
+	    port;
+	mvsw_smi_write(sc, MVSW_G2, MVSW_G1_MONITOR_MGMT_CTL, r);
+
+	r = MVSW_G1_MONITOR_MGMT_CTL_UPDATE |
+	    MVSW_G1_MONITOR_MGMT_CTL_PTR_EGRESS_DEST |
+	    port;
+	mvsw_smi_write(sc, MVSW_G2, MVSW_G1_MONITOR_MGMT_CTL, r);
+}
+
+static int
+mvsw_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mvsw_port *p = ac0->ac_trunkport;
+	int error = 0;
+
+	switch (cmd) {
+	case SIOCGTRUNKPORT: {
+		struct trunk_reqport *rp = (struct trunk_reqport *)data;
+		struct mvsw_softc *sc = p->p_softc;
+
+		if (strncmp(rp->rp_ifname, rp->rp_portname,
+		    sizeof(rp->rp_ifname)) != 0)
+			return (EINVAL);
+
+		(void)strlcpy(rp->rp_ifname, DEVNAME(sc),
+		    sizeof(rp->rp_ifname));
+		break;
+	}
+
+	case SIOCSIFLLADDR:
+		error = EBUSY;
+		break;
+
+	default:
+		error = (*p->p_ioctl)(ifp0, cmd, data);
+		break;
+	}
+
+	return (error);
+}
+
+static int
+mvsw_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
+    struct rtentry *rt)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mvsw_port *p = ac0->ac_trunkport;
+
+#if 0
+	/* restrict transmission to bpf only */
+	if (m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL) {
+		m_freem(m);
+		return (EBUSY);
+	}
+#endif
+
+	return ((*p->p_output)(ifp0, m, dst, rt));
+}
+
+static void
+mvsw_p_input(struct ifnet *ifp0, struct mbuf *m)
+{
+	struct arpcom *ac0 = (struct arpcom *)ifp0;
+	struct mvsw_port *p = ac0->ac_trunkport;
+	struct mvsw_softc *sc = p->p_softc;
+	struct ether_header *eh;
+	struct mvsw_etag *etag;
+	int hlen = sizeof(*eh) + sizeof(*etag);
+	int diff = hlen - offsetof(struct ether_header, ether_type);
+	uint16_t tag0;
+	struct mvsport_softc *psc;
+
+	eh = mtod(m, struct ether_header *);
+	if (eh->ether_type != htons(ETHERTYPE_MVSW_ETAG))
+		goto drop;
+
+	if (m->m_len < hlen) {
+		m = m_pullup(m, hlen);
+		if (m == NULL) {
+			/* drop++ */
+			return;
+		}
+
+		eh = mtod(m, struct ether_header *);
+	}
+
+	etag = (struct mvsw_etag *)(eh + 1);
+	tag0 = bemtoh16(&etag->tag0);
+
+	int port = (tag0 >> MVSW_TAG_PORT_SHIFT) & MVSW_TAG_PORT_MASK;
+	if (port >= sc->sc_nports)
+		goto drop;
+
+	psc = sc->sc_ports[port];
+	if (psc == NULL)
+		goto drop;
+
+	memmove(mtod(m, caddr_t) + diff, mtod(m, caddr_t),
+	    offsetof(struct ether_header, ether_type));
+	m_adj(m, diff);
+
+	m = mvsport_input(psc, m);
+	if (m == NULL)
+		return;
+
+	ether_input(ifp0, m);
+	return;
+
+drop:
+	m_freem(m);
+}
+
+static int
+mvsw_print(void *aux, const char *pnp)
+{
+	int node = *(int *)aux;
+	int port;
+
+	if (pnp != NULL)
+		printf("\"port\" at %s", pnp); 
+
+	port = OF_getpropint(node, "reg", 0);
+	printf(" port %d", port);
+
+	return (UNCONF);
 }
 
 static inline int
@@ -219,6 +930,53 @@ mvsw_smi_write(struct mvsw_softc *sc, in
 	mvsw_smi_wait(sc);
 }
 
+static int
+mvsw_wait(struct mvsw_softc *sc, int phy, int reg, uint16_t mask, uint16_t v,
+    const char *wmesg)
+{
+	unsigned int i;
+	uint16_t r;
+
+	for (i = 0; i < 16; i++) {
+		r = mvsw_smi_read(sc, phy, reg);
+		if ((r & mask) == v)
+			return (0);
+
+		tsleep_nsec(&sc->sc_mdio, PPAUSE, wmesg, 1500000);
+	}
+
+	return (-1);
+}
+
+static int
+mvsw_vtu_op(struct mvsw_softc *sc, uint16_t op)
+{
+	mvsw_smi_write(sc, MVSW_G1, MVSW_G1_VTU_OP,
+	    MVSW_G1_VTU_OP_BUSY | op);
+
+	return (mvsw_vtu_wait(sc));
+}
+
+static int
+mvsw_atu_op(struct mvsw_softc *sc, uint16_t fid, uint16_t op, uint16_t data)
+{
+	mvsw_smi_write(sc, MVSW_G1, MVSW_G1_ATU_DATA, data);
+	mvsw_smi_write(sc, MVSW_G1, MVSW_G1_ATU_FID, fid);
+	mvsw_smi_write(sc, MVSW_G1, MVSW_G1_VTU_OP,
+	    MVSW_G1_VTU_OP_BUSY | op);
+
+	return (mvsw_atu_wait(sc));
+}
+
+static int
+mvsw_irl_op(struct mvsw_softc *sc, uint16_t op)
+{
+	mvsw_smi_write(sc, MVSW_G2, MVSW_G2_IRL_CMD,
+	    MVSW_G2_IRL_CMD_BUSY | op);
+
+	return (mvsw_irl_wait(sc));
+}
+
 int
 mvsw_phy_wait(struct mvsw_softc *sc)
 {
@@ -308,23 +1066,6 @@ mvsw_serdes_write(struct mvsw_softc *sc,
 }
 
 void
-mvsw_port_enable(struct mvsw_softc *sc, int node)
-{
-	uint16_t val;
-	int port;
-
-	port = OF_getpropint(node, "reg", -1);
-	if (port == -1)
-		return;
-
-	/* Enable port. */
-	val = mvsw_smi_read(sc, MVSW_PORT(port), MVSW_PORT_CTRL);
-	val &= ~MVSW_PORT_CTRL_STATE_MASK;
-	val |= MVSW_PORT_CTRL_STATE_FORWARD;
-	mvsw_smi_write(sc, MVSW_PORT(port), MVSW_PORT_CTRL, val);
-}
-
-void
 mvsw_phy_enable(struct mvsw_softc *sc, int node)
 {
 	uint16_t val;
@@ -357,4 +1098,358 @@ mvsw_serdes_enable(struct mvsw_softc *sc
 	val |= BMCR_AUTOEN;
 	mvsw_serdes_write(sc, MVSW_SERDES(port),
 	    MDIO_MMD_PHYXS, MVSW_SERDES_BMCR, val);
+}
+
+struct mvsport_softc {
+	struct device		 sc_dev;
+	int			 sc_node;
+	int			 sc_port;
+
+	struct arpcom		 sc_ac;
+#define sc_if			 sc_ac.ac_if
+
+	struct mii_bus		*sc_mdio;
+	struct mii_data		 sc_mii;
+#define sc_ifmedia		 sc_mii.mii_media
+	struct mvsw_softc	*sc_parent;
+
+	struct if_device	 sc_ifd;
+};
+
+static int	mvsport_match(struct device *, void *, void *);
+static void	mvsport_attach(struct device *, struct device *, void *);
+
+const struct cfattach mvsport_ca = {
+	sizeof (struct mvsport_softc), mvsport_match, mvsport_attach
+};
+
+struct cfdriver mvsport_cd = {
+	NULL, "mvsport", DV_DULL
+};
+
+static void	mvsport_start(struct ifqueue *);
+static int	mvsport_ioctl(struct ifnet *, u_long, caddr_t);
+
+static int	mvsport_up(struct mvsport_softc *);
+static int	mvsport_down(struct mvsport_softc *);
+
+static int	mvsport_miibus_readreg(struct device *, int, int);
+static void	mvsport_miibus_writereg(struct device *, int, int, int);
+static void	mvsport_miibus_statch(struct device *);
+
+static int	mvsport_media_upd(struct ifnet *);
+static void	mvsport_media_sts(struct ifnet *, struct ifmediareq *);
+
+static uint16_t	mvsport_smi_read(struct mvsport_softc *, int);
+static void	mvsport_smi_write(struct mvsport_softc *, int, uint16_t);
+
+static int
+mvsport_match(struct device *parent, void *match, void *aux)
+{
+	int node = *(int *)aux;
+	char buf[32];
+
+	if (OF_getprop(node, "status", buf, sizeof(buf)) > 0 &&
+	    strcmp(buf, "disabled") == 0)
+		return (0);
+
+	return (1);
+}
+
+static void
+mvsport_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct mvsport_softc *sc = (struct mvsport_softc *)self;
+	int node = *(int *)aux;
+	struct ifnet *ifp;
+	int phyph, phynode;
+	int port;
+	uint16_t r;
+
+	sc->sc_node = node;
+	sc->sc_port = port = OF_getpropint(node, "reg", -1);
+
+	ifp = &sc->sc_if;
+	(void)strlcpy(ifp->if_xname, DEVNAME(sc), sizeof(ifp->if_xname));
+	ifp->if_softc = sc;
+	ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
+	ifp->if_ioctl = mvsport_ioctl;
+	ifp->if_qstart = mvsport_start;
+	ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
+	ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
+
+	OF_getprop(node, "label",
+	    ifp->if_description, sizeof(ifp->if_description));
+
+	if (OF_getprop(node, "local-mac-address", &sc->sc_ac.ac_enaddr,
+	    sizeof(sc->sc_ac.ac_enaddr)) != sizeof(sc->sc_ac.ac_enaddr))
+		ether_fakeaddr(ifp);
+
+	printf(": address %s\n", ether_sprintf(sc->sc_ac.ac_enaddr));
+
+	phyph = OF_getpropint(node, "phy-handle", 0);
+	phynode = OF_getnodebyphandle(phyph);
+	if (phynode != 0) {
+#ifdef notyet
+		int phyloc = OF_getpropint(phynode, "reg", 0);
+#endif
+		struct mii_data *mii = &sc->sc_mii;
+
+		mii->mii_ifp = ifp;
+		mii->mii_readreg = mvsport_miibus_readreg;
+		mii->mii_writereg = mvsport_miibus_writereg;
+		mii->mii_statchg = mvsport_miibus_statch;
+
+		ifmedia_init(&sc->sc_ifmedia, 0,
+		    mvsport_media_upd, mvsport_media_sts);
+
+#ifdef notyet
+		mii_attach(self, mii, 0xffffffff, phyloc, MII_OFFSET_ANY, 0);
+#endif
+		if (LIST_FIRST(&mii->mii_phys) == NULL) {
+#ifdef notyet
+			printf("%s: no PHY found!\n", DEVNAME(sc));
+#endif
+			ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_MANUAL,
+			    0, NULL);
+			ifmedia_set(&sc->sc_ifmedia, IFM_ETHER|IFM_MANUAL);
+		} else
+			ifmedia_set(&sc->sc_ifmedia, IFM_ETHER|IFM_AUTO);
+	}
+
+	if_counters_alloc(ifp);
+ 	if_attach(ifp);
+	ether_ifattach(ifp);
+
+	sc->sc_ifd.if_node = sc->sc_node;
+	sc->sc_ifd.if_ifp = ifp;
+	if_register(&sc->sc_ifd);
+
+	/* Configure port. */
+	r = mvsport_smi_read(sc, MVSW_PORT_CTRL0);
+	CLR(r, MVSW_PORT_CTRL0_STATE_MASK);
+	SET(r, MVSW_PORT_CTRL0_STATE_DISABLED);
+	CLR(r, MVSW_PORT_CTRL0_FRAME_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL0_FRAME_MODE_NORMAL);
+	CLR(r, MVSW_PORT_CTRL0_EGRESS_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL0_EGRESS_MODE_UNMODIFIED);
+	SET(r, MVSW_PORT_CTRL0_EGRESS_FLOOD_UCAST |
+	    MVSW_PORT_CTRL0_EGRESS_FLOOD_MCAST);
+	mvsport_smi_write(sc, MVSW_PORT_CTRL0, r);
+
+	r = mvsport_smi_read(sc, MVSW_PORT_CTRL2);
+	//SET(r, MVSW_PORT_CTRL2_MAP_DA);
+	CLR(r, MVSW_PORT_CTRL2_MAP_DA);
+	CLR(r, MVSW_PORT_CTRL2_JUMBO_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL2_JUMBO_MODE_10240);
+	CLR(r, MVSW_PORT_CTRL2_8021Q_MODE_MASK);
+	SET(r, MVSW_PORT_CTRL2_8021Q_MODE_DISABLED);
+	CLR(r, MVSW_PORT_CTRL2_DISCARD_TAGGED);
+	CLR(r, MVSW_PORT_CTRL2_DISCARD_UNTAGGED);
+	mvsport_smi_write(sc, MVSW_PORT_CTRL2, r);
+
+	mvsport_smi_write(sc, MVSW_PORT_ASSOC_VECTOR, 0);
+
+	mvsport_smi_write(sc, MVSW_PORT_ETH_TYPE, ETHERTYPE_MVSW_DEFAULT);
+}
+
+static uint16_t
+mvsport_smi_read(struct mvsport_softc *sc, int reg)
+{
+	return mvsw_smi_read((struct mvsw_softc *)sc->sc_dev.dv_parent,
+	    MVSW_PORT(sc->sc_port), reg);
+}
+
+static void
+mvsport_smi_write(struct mvsport_softc *sc, int reg, uint16_t r)
+{
+	mvsw_smi_write((struct mvsw_softc *)sc->sc_dev.dv_parent,
+	    MVSW_PORT(sc->sc_port), reg, r);
+}
+
+static int
+mvsport_miibus_readreg(struct device *dev, int phy, int reg)
+{
+	struct device *parent = dev->dv_parent;
+	struct mvsw_softc *sc = (struct mvsw_softc *)parent;
+
+	return (mvsw_phy_read(sc, phy, reg));
+}
+
+static void
+mvsport_miibus_writereg(struct device *dev, int phy, int reg, int val)
+{
+	struct device *parent = dev->dv_parent;
+	struct mvsw_softc *sc = (struct mvsw_softc *)parent;
+
+	return (mvsw_phy_write(sc, phy, reg, val));
+}
+
+static void
+mvsport_miibus_statch(struct device *dev)
+{
+	printf("%s: %s[%u]\n", dev->dv_xname, __func__, __LINE__);
+}
+
+static int
+mvsport_media_upd(struct ifnet *ifp)
+{
+	struct mvsport_softc *sc = ifp->if_softc;
+
+	if (LIST_FIRST(&sc->sc_mii.mii_phys))
+		mii_mediachg(&sc->sc_mii);
+
+	return (0);
+}
+
+static void
+mvsport_media_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+	struct mvsport_softc *sc = ifp->if_softc;
+
+	if (LIST_FIRST(&sc->sc_mii.mii_phys)) {
+		mii_pollstat(&sc->sc_mii);
+		ifmr->ifm_active = sc->sc_mii.mii_media_active;
+		ifmr->ifm_status = sc->sc_mii.mii_media_status;
+	}
+}
+
+static int
+mvsport_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+	struct mvsport_softc *sc = ifp->if_softc;
+	struct ifreq *ifr = (struct ifreq *)data;
+	int error = 0;
+
+	switch (cmd) {
+	case SIOCSIFFLAGS:
+		if (ISSET(ifp->if_flags, IFF_UP)) {
+			if (!ISSET(ifp->if_flags, IFF_RUNNING))
+				error = mvsport_up(sc);
+		} else {
+			if (ISSET(ifp->if_flags, IFF_RUNNING))
+				error = mvsport_down(sc);
+		}
+		break;
+
+	case SIOCSIFMEDIA:
+	case SIOCGIFMEDIA:
+		error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, cmd);
+		break;
+
+	default:
+		error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+		break;
+	}
+
+	if (error == ENETRESET) {
+		/* hardware doesnt need reprogramming */
+		error = 0;
+	}
+
+	return (error);
+}
+
+static int
+mvsport_up(struct mvsport_softc *sc)
+{
+	struct ifnet *ifp = &sc->sc_if;
+	uint16_t r;
+
+	r = mvsport_smi_read(sc, MVSW_PORT_CTRL0);
+	CLR(r, MVSW_PORT_CTRL0_STATE_MASK);
+	SET(r, MVSW_PORT_CTRL0_STATE_FORWARD);
+	mvsport_smi_write(sc, MVSW_PORT_CTRL0, r);
+
+	SET(ifp->if_flags, IFF_RUNNING);
+
+	return (0);
+}
+
+static int
+mvsport_down(struct mvsport_softc *sc)
+{
+	struct ifnet *ifp = &sc->sc_if;
+	uint16_t r;
+
+	CLR(ifp->if_flags, IFF_RUNNING);
+
+	r = mvsport_smi_read(sc, MVSW_PORT_CTRL0);
+	CLR(r, MVSW_PORT_CTRL0_STATE_MASK);
+	SET(r, MVSW_PORT_CTRL0_STATE_DISABLED);
+	mvsport_smi_write(sc, MVSW_PORT_CTRL0, r);
+
+	return (0);
+}
+
+static void
+mvsport_start(struct ifqueue *ifq)
+{
+	struct ifnet *ifp = ifq->ifq_if;
+	struct mvsport_softc *sc = ifp->if_softc;
+	struct mbuf *m;
+	struct ether_header *eh;
+	struct mvsw_etag *etag;
+	const int hlen = sizeof(*eh) + sizeof(*etag);
+	const int offs = offsetof(struct ether_header, ether_type);
+	const int diff = hlen - offs;
+	int errors = 0;
+
+	struct mvsw_softc *ssc = (struct mvsw_softc *)sc->sc_dev.dv_parent;
+	struct mvsw_port *p = TAILQ_FIRST(&ssc->sc_cpus);
+
+	if (p == NULL) {
+		ifq_purge(ifq);
+		return;
+	}
+
+	while ((m = ifq_dequeue(ifq)) != NULL) {
+#if NBPFILTER > 0
+		{
+			caddr_t if_bpf = ifp->if_bpf;
+			if (if_bpf)
+				bpf_mtap_ether(if_bpf, m, BPF_DIRECTION_OUT);
+		}
+#endif
+
+		m = m_prepend(m, diff, M_NOWAIT);
+		if (m == NULL) {
+			errors++;
+			continue;
+		}
+		if (m->m_len < hlen) {
+			m = m_pullup(m, hlen);
+			if (m == NULL) {
+				errors++;
+				continue;
+			}
+		}
+
+		memmove(mtod(m, caddr_t), mtod(m, caddr_t) + diff, offs);
+		eh = mtod(m, struct ether_header *);
+		eh->ether_type = htons(ETHERTYPE_MVSW_ETAG);
+
+		etag = (struct mvsw_etag *)(eh + 1);
+		etag->reserved = htons(0);
+		etag->tag0 = htons(MVSW_TAG_MODE_FROM_CPU |
+		    (ssc->sc_dev.dv_unit << MVSW_TAG_SWITCH_SHIFT) |
+		    (sc->sc_port << MVSW_TAG_PORT_SHIFT));
+		etag->tag1 = htons(0);
+
+		if (if_enqueue(p->p_ifp0, m) != 0)
+			errors++;
+	}
+
+	if (errors)
+		counters_add(ifp->if_counters, ifc_oerrors, errors);
+}
+
+static struct mbuf *
+mvsport_input(struct mvsport_softc *sc, struct mbuf *m)
+{
+	struct ifnet *ifp = &sc->sc_if;
+
+	if_vinput(ifp, m);
+
+	return (NULL);
 }
Index: ic/dwqe.c
===================================================================
RCS file: /cvs/src/sys/dev/ic/dwqe.c,v
retrieving revision 1.8
diff -u -p -r1.8 dwqe.c
--- ic/dwqe.c	24 Apr 2023 01:33:32 -0000	1.8
+++ ic/dwqe.c	24 Apr 2023 02:08:21 -0000
@@ -1,4 +1,4 @@
-/*	$OpenBSD: dwqe.c,v 1.8 2023/04/24 01:33:32 dlg Exp $	*/
+/*	$OpenBSD: dwqe.c,v 1.4 2023/04/07 08:53:03 kettenis Exp $	*/
 /*
  * Copyright (c) 2008, 2019 Mark Kettenis <kettenis@openbsd.org>
  * Copyright (c) 2017, 2022 Patrick Wildt <patrick@blueri.se>
@@ -75,14 +75,11 @@ int	dwqe_media_change(struct ifnet *);
 void	dwqe_media_status(struct ifnet *, struct ifmediareq *);
 
 void	dwqe_mii_attach(struct dwqe_softc *);
-int	dwqe_mii_readreg(struct device *, int, int);
-void	dwqe_mii_writereg(struct device *, int, int, int);
-void	dwqe_mii_statchg(struct device *);
 
 void	dwqe_lladdr_read(struct dwqe_softc *, uint8_t *);
 void	dwqe_lladdr_write(struct dwqe_softc *);
 
-void	dwqe_tick(void *);
+void	dwqe_mii_tick(void *);
 void	dwqe_rxtick(void *);
 
 int	dwqe_intr(void *);
@@ -116,7 +113,7 @@ dwqe_attach(struct dwqe_softc *sc)
 	for (i = 0; i < 4; i++)
 		sc->sc_hw_feature[i] = dwqe_read(sc, GMAC_MAC_HW_FEATURE(i));
 
-	timeout_set(&sc->sc_phy_tick, dwqe_tick, sc);
+	mtx_init(&sc->sc_mii_mtx, IPL_NET);
 	timeout_set(&sc->sc_rxto, dwqe_rxtick, sc);
 
 	ifp = &sc->sc_ac.ac_if;
@@ -213,7 +203,9 @@ dwqe_attach(struct dwqe_softc *sc)
 		dwqe_write(sc, GMAC_SYS_BUS_MODE, mode);
 	}
 
-	if (!sc->sc_fixed_link)
+	if (sc->sc_fixed_link)
+		dwqe_mii_statchg(&sc->sc_dev);
+	else
 		dwqe_mii_attach(sc);
 
 	if_attach(ifp);
@@ -441,8 +442,11 @@ int
 dwqe_mii_readreg(struct device *self, int phy, int reg)
 {
 	struct dwqe_softc *sc = (void *)self;
+	int rv = 0;
 	int n;
 
+	mtx_enter(&sc->sc_mii_mtx);
+
 	dwqe_write(sc, GMAC_MAC_MDIO_ADDR,
 	    sc->sc_clk << GMAC_MAC_MDIO_ADDR_CR_SHIFT |
 	    (phy << GMAC_MAC_MDIO_ADDR_PA_SHIFT) |
@@ -450,14 +454,17 @@ dwqe_mii_readreg(struct device *self, in
 	    GMAC_MAC_MDIO_ADDR_GOC_READ |
 	    GMAC_MAC_MDIO_ADDR_GB);
 
-	for (n = 0; n < 2000; n++) {
+	for (n = 0; n < 10000; n++) {
 		delay(10);
-		if ((dwqe_read(sc, GMAC_MAC_MDIO_ADDR) & GMAC_MAC_MDIO_ADDR_GB) == 0)
-			return dwqe_read(sc, GMAC_MAC_MDIO_DATA);
+		if ((dwqe_read(sc, GMAC_MAC_MDIO_ADDR) & GMAC_MAC_MDIO_ADDR_GB) == 0) {
+			rv = dwqe_read(sc, GMAC_MAC_MDIO_DATA);
+			break;
+		}
 	}
 
-	printf("%s: mii_read timeout\n", sc->sc_dev.dv_xname);
-	return (0);
+	mtx_leave(&sc->sc_mii_mtx);
+
+	return (rv);
 }
 
 void
@@ -466,6 +473,8 @@ dwqe_mii_writereg(struct device *self, i
 	struct dwqe_softc *sc = (void *)self;
 	int n;
 
+	mtx_enter(&sc->sc_mii_mtx);
+
 	dwqe_write(sc, GMAC_MAC_MDIO_DATA, val);
 	dwqe_write(sc, GMAC_MAC_MDIO_ADDR,
 	    sc->sc_clk << GMAC_MAC_MDIO_ADDR_CR_SHIFT |
@@ -474,13 +483,13 @@ dwqe_mii_writereg(struct device *self, i
 	    GMAC_MAC_MDIO_ADDR_GOC_WRITE |
 	    GMAC_MAC_MDIO_ADDR_GB);
 
-	for (n = 0; n < 2000; n++) {
+	for (n = 0; n < 10000; n++) {
 		delay(10);
 		if ((dwqe_read(sc, GMAC_MAC_MDIO_ADDR) & GMAC_MAC_MDIO_ADDR_GB) == 0)
-			return;
+			break;
 	}
 
-	printf("%s: mii_write timeout\n", sc->sc_dev.dv_xname);
+	mtx_leave(&sc->sc_mii_mtx);
 }
 
 void
@@ -521,7 +530,7 @@ dwqe_mii_statchg(struct device *self)
 }
 
 void
-dwqe_tick(void *arg)
+dwqe_mii_tick(void *arg)
 {
 	struct dwqe_softc *sc = arg;
 	int s;
Index: ic/dwqevar.h
===================================================================
RCS file: /cvs/src/sys/dev/ic/dwqevar.h,v
retrieving revision 1.6
diff -u -p -r1.6 dwqevar.h
--- ic/dwqevar.h	24 Apr 2023 01:33:32 -0000	1.6
+++ ic/dwqevar.h	24 Apr 2023 02:08:21 -0000
@@ -1,4 +1,4 @@
-/*	$OpenBSD: dwqevar.h,v 1.6 2023/04/24 01:33:32 dlg Exp $	*/
+/*	$OpenBSD: dwqevar.h,v 1.4 2023/04/07 09:33:51 dlg Exp $	*/
 /*
  * Copyright (c) 2008, 2019 Mark Kettenis <kettenis@openbsd.org>
  * Copyright (c) 2017, 2022 Patrick Wildt <patrick@blueri.se>
@@ -54,11 +54,13 @@ struct dwqe_softc {
 	void			*sc_ih;
 
 	struct if_device	sc_ifd;
+	struct mii_bus		sc_mdio;
 
 	struct arpcom		sc_ac;
 #define sc_lladdr	sc_ac.ac_enaddr
 	struct mii_data		sc_mii;
 #define sc_media	sc_mii.mii_media
+	struct mutex		sc_mii_mtx;
 	int			sc_link;
 	int			sc_phyloc;
 	enum dwqe_phy_mode	sc_phy_mode;
@@ -117,4 +119,8 @@ uint32_t dwqe_read(struct dwqe_softc *, 
 void	dwqe_write(struct dwqe_softc *, bus_addr_t, uint32_t);
 void	dwqe_lladdr_read(struct dwqe_softc *, uint8_t *);
 void	dwqe_lladdr_write(struct dwqe_softc *);
+
+int	dwqe_mii_readreg(struct device *, int, int);
+void	dwqe_mii_writereg(struct device *, int, int, int);
 void	dwqe_mii_statchg(struct device *);
+