diff --git a/Documentation/networking/hanic.rst b/Documentation/networking/hanic.rst
new file mode 100644
index 000000000000..16b04247b0b0
--- /dev/null
+++ b/Documentation/networking/hanic.rst
@@ -0,0 +1,351 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+============
+HANIC DRIVER
+============
+
+Copyright |copy| 2022 Cruise LLC
+
+OVERVIEW
+========
+
+The hanic driver creates a virtual network interface that implements
+the high availability protocol 802.1cb, with vector matching. The
+idea is to create a hanic NIC, then enlist a pair of physical NIC
+devices to act at the redundant ports. The hanic NIC replaces the
+enlisted ports, and handles duplication and R-TAGS (outbound) and
+de-duplication and removing R-TAGS (inbound).
+
+The current implementation assumes that all outbound unicast traffic
+is to be R-TAGed and duplicated, so there is no need to define
+outbound streams explicitly. Arriving traffic that includes R-TAGs
+automatically call themselves out as 802.1cb traffic, so inbound
+streams don't need to be explicitly defined either.
+
+Inbound traffic that does not have an R-TAG is assumed to not be
+redundant, and is simply passed up the network stack.
+
+Broadcast Packets
+=================
+
+Outbound broadcast and multicast traffic is either sent out a single
+(prime or second mode) or duplicated without R-TAGS and sent out all
+the enlisted ports (flood mode). The appropriate mode depends on
+network topology. Broadcast packets are never R-TAGed by the hanic
+driver.
+
+Broadcast packets cannot be R-TAGed by the hanic driver, because there
+is not enough information in the stream identification process to
+properly identify a stream context for the R-TAG sequence
+number.
+
+Consider the example of a source MAC-A and destination MAC-B. MAC-A
+can send unicast packets to MAC-B and also broadcast packets to
+MAC-x. MAC-A identifies the outbound stream (and thus the scope for
+the R-TAG sequence number) by using the target address, so
+MAC-A-to-MAC-x is one stream, and MAC-A-to-MAC-B is a different
+stream.
+
+On the receiving (B) side, the hanic driver uses the source address to
+identify the stream. (The destination address is the host itself.)
+Thus, MAC-A-to-MAC-B and MAC-A-to-MAC-x map to the same
+stream. Now the R-TAGs don't match and the link breaks.
+
+A work-around would be to use both the source and destination MAC
+addresses, but that's beyond the scope of the 802.1cb standard, so
+other devices will not be able to communicate with the hanic
+driver. Therefore, broadcast (and multicast) packets cannot be
+R-TAGed.
+
+Special multicast handling
+==========================
+
+By default, multicast packets (outbound) are treated as broadcast
+packets, and are not R-TAGed. Their disposition follows the broadcast
+packet disposition setting.
+
+However, one can request that multicast packets be R-TAGed and
+replicated. Set the multicast_rtag setting, and multicast packets
+will be handled the same as unicast packets. This should only work
+if by design the network knows that will only be one source nic
+for any packets with a given multicast group. (There may obviously
+still be multiple receivers). If this constraint is true, then the
+objection in the broadcast case that the stream cannot be uniquely
+identified no longer applies, and R-TAGed multicast packets will
+work fine.
+
+If the multicast_rtag flag is set, it is still possible to set some
+multicast groups to avoid tagging by using the vlan and vmac filters
+to set the disposition.
+
+Special ARP handling
+====================
+
+ARP requests are broadcast, so are not R-TAGed. ARP replies, on the
+other hand, are unicast and can theoretically be R-TAGed. However,
+hanic detects outbound ARP replies and handles them based on the
+arp_rtag configuration flag. If this flag is set to 1, then ARP
+response packets are R-TAGed; if this flag is set to 0 (the default)
+they are sent out the primary port (prime mode), secondary port
+(second mode) or all the enlisted ports (flood mode), without R-TAGs.
+
+It is unlikely that the ARP requestor matching an ARP reply is an
+802.1CB protected stream, and even if it is, it should still be able
+to receive an untagged ARP reply packet. That's certainly the case
+with hanic. Thus, the default for arp_rtag is 0, and this is probably
+the best choice if the remotes are all hanic implementations. If the
+network requires all packets to be R-TAGed, set arp_rtag to 1 to force
+arp resposes to be R-TAGed. A hanic implementation will be able to
+understand and work with R-TAGed and not R-TAGed ARP responses, no
+matter how it is configured to handle outbound ARP replies.
+
+Output R-TAG filter
+===================
+
+In a realistic system, some destinations do not support 802.1cb,
+or don't need it. The hanic driver has a means to call out these
+destinations by vlan or by mac and vlan together. For example, if
+all the destinations on vlan=11 (0x000b) should not receive r-TAG'ed
+packets, then that can be arranged like so::
+
+  $ echo 000b: prime > /sys/class/net/\<nic\>/hanic/filters_vlan
+
+If you need to be more selective, then you can use the vmac filter
+to suppress tags::
+
+  $ echo 01:23:45:56:89:ab-000b: prime > /sys/class/net/<nic>/hanic/filter_vmac
+
+This filter does not affect inbound traffic, since the driver can
+already tell if a packet is R-TAG'ed and handle it appropriately.
+
+Creating interfaces
+===================
+
+The way to create interfaces is with a command like this::
+
+  $ echo +hanic0 > /sys/class/net/hanic_interfaces
+
+It is also possible to destroy interfaces thusly::
+
+  $ echo -hanic0 > /sys/class/net/hanic_interfaces
+
+
+Enlisting other ethernet ports to an interface
+==============================================
+
+The standard IP commands can be used to do the enlistment, like so::
+
+  $ ip link set sandlan0a master hanic0
+  $ ip link set sandlan0b master hanic0
+
+These commands enlist the sandlan0a and sandlan0b interfaces to the
+hanic0 interface. Interfaces remain enlisted until they are
+unregistered, or until the hanic interface is destroyed.
+
+The first NIC added to the hanic device is the prime NIC. This is
+the NIC that is used for broadcast and multicast traffic, unless the
+broadcast_port mode is changed to "flood".
+
+The default MAC address for the hanic NIC is that of the prime NIC,
+but the user mode may explicitly set the MAC address. Wherever the MAC
+address comes from, all the NICs are compelled to take on the MAC
+address of the master, so that they receive packets destined for the
+master.
+
+When a NIC is released, it is given the MAC address that it had when it
+was first enlisted, but NICs are not typically released.
+
+
+VLANS
+=====
+
+Hanic understands 802.1Q VLANs. Create hanic protected VLANs by
+starting with a regular hanic NIC (e.g. hanic0) and linking a VLAN
+like so::
+
+  $ ip link add link hanic0 name hanic0.11 type vlan id 11
+
+The hanic0.11 appears to the user like any other NIC, but it is linked
+to the hanic protocol; the hanic module will recognize packets going
+through this VLAN and handle the parts of the protocol that need VLAN
+awareness.
+
+It appears to the user that the VLAN is on top of the hanic driver,
+but the packets out and in will have the 802.1Q tags before the
+802.1cb R-TAGs, as expected. This works out because the Linux vlan
+module reaches around into the header to mark the tags. More
+correctly, one says that hanic0.11 is "linked" to hanic0, and not
+stacked.
+
+The statistics for vlan NICs (e.g. hanic0.11 above) are kept by the
+hanic driver and made available via the underlying interface (hanic0).
+So the hanic interface streams and statistics for the hanic lan and
+all bound vlans are found in the /sys/class/net/hanic0/hanic/ directory.
+
+SYSFS interface
+===============
+
+* /sys/class/net/hanic_interfaces [rw]
+
+Read this file to get the hanic interfaces that exist. Write this file
+to create or destroy hanic interfaces.
+
+* /sys/class/net/<nic>/hanic/broadcast_port [rw]
+
+Read this file to find out how outbound broadcast (and multicast)
+packets are handled. If the value is "flood", then the packets are
+sent out all the NICs. Otherwise, the value is the number for the port
+that is used for broadcast.
+
+* /sys/class/net/<nic>/hanic/arp_rtag [rw] (bool)
+
+Normally, ARP response packets are not R-TAG'ed an are instead
+disposed out the broadcast port. If this flag is set to 1, the ARP
+packet is instead R-TAG'ed. This will create a destination stream if
+needed, R-TAGs will be inserted to the outbound ARP response, and the
+packet will be sent redundantly.
+
+* /sys/class/net/<nic>/hanic/multicast_rtag [rw] (bool)
+
+Normally, multicast packets are not R-TAG'ed and are instead
+disposed out the broadcast port. If this flag is set to 1, multicast
+packets are instead R-TAG'ed. This will create a destination stream if
+needed, R-TAGs will be inserted to the outbound packet, and the
+packet will be sent redundantly.
+
+* /sys/class/net/<nic>/hanic/ports [r]
+
+Read this file to see the NICs bound to the hanic device, one port
+per line.
+
+* /sys/class/net/<nic>/hanic/stream-xx:xx:xx:xx:xx:xx-vvvv [r]
+
+There is a file like this for every active stream, which contains
+802.1cb stream specific statistics. The file name is made up from
+the mac address (6 bytes) and a vlan id (2 bytes). A vlan of 0 means
+no vlan at all.
+
+* /sys/class/net/<nic>/hanic/filter_vlan [rw]
+
+This contains vlan filters. Each line is the format::
+
+  vvvv: mode
+
+where vvvv is the vlan in hex, and mode is the port to use. Outbound
+packets that match this vlan are sent out this port without replication
+and 802.1cb outbound processing is skipped. (This does not affect inbound
+traffic.) The valid modes are:
+
+| Mode   | Meaning                           |
+| +---   | +------                           |
+| none   | Remove from the filter            |
+| <N>    | Send untagged out port <N>        |
+| flood  | Send untagged out all the ports   |
+
+* /sys/class/net/\<nic\>/hanic/filter_vmac [rw]
+
+This contains vlan filters. Each line is the format:
+
+  xx:xx:xx:xx:xx:xx-vvvv: mode
+
+where xx..xx is the target mac address and vvvv is the vlan in
+hex; and mode is the port to use. Outbound packets that match
+this vlan are sent out this port without replication and 802.1cb
+outbound processing is skipped. (This does not affect inbound
+traffic.) The valid modes are:
+
+| Mode   | Meaning                           |
+| +---   | +------                           |
+| none   | Remove from the filter            |
+| <N>    | Send untagged out port <N>        |
+| flood  | Send untagged out all the ports   |
+
+* /sys/class/net/\<nic\>/hanic/jiffies_per_hanic_tick [r]
+
+This is the period, measured in Linux Jiffies, of the 802.1cb timer.
+The TicksPerSecond described in IEEE Std 802.1CB-2017 section 7.4.3.2.5
+is a frequency, and is (HZ / jiffies_per_hanic_tick).
+
+* /sys/class/net/\<nic\>/hanic/ticks_per_reset [rw]
+
+This is the hanic equivalent of frerSeqRcvyResetMSec. It is the number
+of hanic ticks of inactivity before deciding to reset the vector recovery
+algorithm. The frerSeqRcvyResetMSec value can be calculated as::
+
+  frerSeqRcvyResetMSec = (1000.0*jiffies_per_hanic_tick*ticks_per_reset)/HZ
+
+This value can be written to set the set the ticks_per_reset (and thus
+change the frerSeqRcvyResetMSec value) so long as this value is 2 or
+more. This implies that there is a minimum value defined by the HZ of
+the Linux/CPU combination and other compile time constants. For ARM,
+with HZ=250, jiffies_per_hanic_tick==2, so frerSeqRcvyResetMSec>=16.
+(The default is much larger.)
+
+* /sys/class/net/\<nic\>/hanic/test_drop_packet_in
+
+Inject faults by dropping input packets from some port. Write to this
+file the port number and the number of packets to drop. For example,
+to tell port 1 to drop the next 5 input packets::
+
+  % echo 1 5 > test_drop_packet_in
+
+To cancel any further packet drops, replace the number with 0::
+
+  % echo 1 0 > test_drop_packet_in
+
+Read the file to see how many drops remain for all the ports::
+
+  % cat test_prop_packet_in
+  0 0
+  1 5
+  2 0
+  3 0
+
+* /sys/class/net/\<nic\>/hanic/test_drop_packet_out
+
+Inject faults by dropping output packets to some port. Write to this
+file the port number and the number of packets to drop. For example,
+to tell port 1 to drop the next 5 output packets::
+
+  % echo 1 5 > test_drop_packet_out
+
+This fault injection only affects packets that are replicated. So for
+example packets that are routed through one port or the other via
+filters are immune to this fault injection.
+
+To cancel any further packet drops, replace the number with 0::
+
+  % echo 1 0 > test_drop_packet_out
+
+Read the file to see how many drops remain for all the ports::
+
+  % cat test_prop_packet_out
+  * 0
+  0 0
+  1 5
+  2 0
+  3 0
+
+As a special case, one can also request that _all_ replicated packets
+are dropped. This would test the remote's ability to detect or at least
+cope with lost packets. Invoke this feature by setting the ' * ' port to
+drop packets::
+
+  % echo '* 1' > test_drop_packet_out
+  % cat test_prop_packet_out
+  * 1
+  0 0
+  1 0
+  2 0
+  3 0
+
+The R-TAG sequence number for the fully dropped packet will be counted
+as if it was transmitted, so the loss of packets will be reflected in the
+R-TAG value of the packet that does get transmitted.
+
+Notes
+=====
+
+The IPv6 protocol engine has some implicit multicast packets that
+are generated, in the range 33:33:xx:xx:xx:xx. These are not R-TAGed
+outbound, even when multicast_rtag is enabled.
diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst
index 4f2d1f682a18..4c5df4e7da1e 100644
--- a/Documentation/networking/index.rst
+++ b/Documentation/networking/index.rst
@@ -56,6 +56,7 @@ Contents:
    generic_netlink
    gen_stats
    gtp
+   hanic
    ila
    ioam6-sysctl
    ipddp
diff --git a/MAINTAINERS b/MAINTAINERS
index 14ee1c72d01a..b59f8c11cee4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8960,6 +8960,12 @@ Q:	http://patchwork.linuxtv.org/project/linux-media/list/
 T:	git git://linuxtv.org/anttip/media_tree.git
 F:	drivers/media/usb/hackrf/
 
+HANIC NET DEVICE
+M:	Steve Williams <steve.williams@getcruise.com>
+S:	Maintained
+F:	Documentation/networking/hanic.rst
+F:	drivers/net/hanic/
+
 HANTRO VPU CODEC DRIVER
 M:	Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
 M:	Philipp Zabel <p.zabel@pengutronix.de>
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9e63b8c43f3e..8cc990921032 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -631,4 +631,21 @@ config NETDEV_LEGACY_INIT
 	  Drivers that call netdev_boot_setup_check() should select this
 	  symbol, everything else no longer needs it.
 
+config HANIC
+       tristate "Cruise High Availability NIC driver"
+       depends on INET
+       help
+         Support for the HANIC module. This driver creates virtual
+	 devices that enlist physical NIC interfaces and combines
+	 them to implement the IEEE 802.1cb frame replication and
+	 elimination protocol, without any special hardware support.
+
+config HANIC_FAULT_INJECTION
+       bool "Enable fault injection interfaces for the hanic driver"
+       depends on HANIC
+       help
+       Include with the HANIC module support for fault injection.
+       This is a debug aid that allows testers to introduce faults
+       in the form of dropped packets.
+
 endif # NETDEVICES
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 6ce076462dbf..d6b1ec66f3cf 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -89,3 +89,4 @@ thunderbolt-net-y += thunderbolt.o
 obj-$(CONFIG_USB4_NET) += thunderbolt-net.o
 obj-$(CONFIG_NETDEVSIM) += netdevsim/
 obj-$(CONFIG_NET_FAILOVER) += net_failover.o
+obj-$(CONFIG_HANIC) += hanic/
diff --git a/drivers/net/hanic/Makefile b/drivers/net/hanic/Makefile
new file mode 100644
index 000000000000..ba209987c06d
--- /dev/null
+++ b/drivers/net/hanic/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Copyright (c) 2020 Cruise, LLC
+# Auther: steve.williams@getcruise.com
+
+
+obj-$(CONFIG_HANIC) += hanic.o
+
+hanic-objs := hanic_main.o \
+	hanic_dev.o \
+	hanic_filter.o \
+	hanic_netns.o \
+	hanic_protocol.o \
+	hanic_streams.o \
+	hanic_sysfs.o
diff --git a/drivers/net/hanic/hanic_dev.c b/drivers/net/hanic/hanic_dev.c
new file mode 100644
index 000000000000..9e35fed8d5ed
--- /dev/null
+++ b/drivers/net/hanic/hanic_dev.c
@@ -0,0 +1,1006 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+#include "hanic_priv.h"
+
+#include <linux/version.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_vlan.h>
+#include <linux/rwlock.h>
+
+/* The interface for dev_open changed at version 5.0.0.
+ */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
+#define DEV_OPEN(eport) dev_open((eport), 0)
+#else
+#define DEV_OPEN(eport) dev_open((eport))
+#endif
+
+/* The default value for ARP_RTAG can be 0 (do not R-TAG) or
+ * 1 (R-TAG arp responses). The value can be set at run time
+ * per interface through the sysfs API.
+ */
+#define ARP_RTAG_DEFAULT 0
+
+/* The default value for MULTICAST_RTAG can be 0 (do not R-TAG)
+ * or 1 (R-TAG multicast packets). The value can be set at run
+ * time per interface through the sysfs API.
+ */
+#define MULTICAST_RTAG_DEFAULT 0
+
+/* Find the R-TAG in the output stream. Also, while we're at it, find
+ * any VLAN tag (802.1Q) and extract the VLAN.
+ *
+ * R-TAG without 802.1Q VLAN tag:
+ *    +---------+---------+---------+-----------+---- . . .
+ *    | d d d d | d d s s | s s s s | f1 c1 0 0 | x x . . .
+ *    +---------+---------+---------+-----------+---- . . .
+ *
+ * R-TAG with 802.1Q VLAN tag:
+ *    +---------+---------+---------+-----------+-----------+---- . . .
+ *    | d d d d | d d s s | s s s s | 81 00 v v | f1 c1 0 0 | x x . . .
+ *    +---------+---------+---------+-----------+-----------+---- . . .
+ */
+static unsigned char *find_header_rtag(const struct sk_buff *skb,
+				       uint16_t *vlan)
+{
+	unsigned char *dp = skb_mac_header(skb);
+
+	/* R-TAG without VLAN tag. */
+	if (test_net2bytes(dp + 12, ETH_P_RTAG)) {
+		*vlan = 0;
+		return dp + 12;
+	}
+
+	/* R-TAG with VLAN tag. */
+	if (test_net2bytes(dp + 12, ETH_P_8021Q) &&
+	    test_net2bytes(dp + 16, ETH_P_RTAG)) {
+		*vlan = ((dp[14] << 8) + dp[15]) & VLAN_VID_MASK;
+		return dp + 16;
+	}
+
+	/* No R-TAG */
+	*vlan = 0;
+	return 0;
+}
+
+/* This is a table of all the hanic interfaces that exist. */
+static struct list_head hanic_devs_list;
+static rwlock_t hanic_devs_mutex;
+
+/* Add the device to a global table of hanic devices. */
+static void add_dev_to_list(struct net_device *dev)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	write_lock(&hanic_devs_mutex);
+	list_add_tail(&xsp->devs_list, &hanic_devs_list);
+	write_unlock(&hanic_devs_mutex);
+}
+
+static int hanic_open(struct net_device *dev)
+{
+	int idx;
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	xsp->rx_packets = 0;
+	xsp->tx_packets = 0;
+	xsp->rx_bytes = 0;
+	xsp->tx_bytes = 0;
+	xsp->rx_dropped = 0;
+	xsp->tx_dropped = 0;
+
+	netif_start_queue(dev);
+
+	/* Make sure the ports are open as well. */
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		int rc;
+		struct net_device *port = xsp->ports[idx].sl_dev;
+
+		if (port == 0)
+			continue;
+
+		rc = DEV_OPEN(port);
+		if (rc < 0) {
+			netdev_warn(dev,
+				    "Error (errno=%d) bringing up port %s.\n",
+				    -rc, port->name);
+		}
+	}
+
+	return 0;
+}
+
+static int hanic_stop(struct net_device *dev)
+{
+	int idx;
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+
+		if (port == 0)
+			continue;
+		dev_close(port);
+	}
+
+	netif_stop_queue(dev);
+	return 0;
+}
+
+/* Is the packet an ARP protocol packet? Look in the ethertype field
+ * of the buffer for the ethertype. If it's ETH_P_ARP, it's an ARP
+ * packet. Otherwise, it is not.
+ */
+static inline int packet_is_arp(struct sk_buff *skb)
+{
+	const unsigned char *dp = skb_mac_header(skb);
+
+	if (test_net2bytes(dp + 12, ETH_P_ARP))
+		return 1;
+
+	return 0;
+}
+
+/* An outbound packet is not taggable if it is a broadcast packet, or
+ * if it is a multicast packet. Broadcast packets don't suffer R-TAGs
+ * well, and multicast packets mostly behave like broadcast packets.
+ *
+ * However, if the administrator can make some guarantees about
+ * multicast groups, then we can allow multicast packets to be
+ * tagged. That situation is flagged by the multicast_rtag flag.
+ *
+ * Is the packet a broadcast/multicast packet? IEEE 802 MAC
+ * addresses can be quickly tested for this feature by looking at the
+ * LSB of the first octet of the MAC address. If the bit is set, it is
+ * multicast or broadcast.
+ */
+static inline int packet_broadcast(struct sk_buff *skb, struct net_device *dev)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	/* Not broadcast/multicast? */
+	if (likely(is_unicast_ether_addr(skb_mac_header(skb))))
+		return 0;
+
+	/* if multicast_rtag==0, then multicast is treated like broadcast. */
+	if (xsp->multicast_rtag == 0)
+		return 1;
+
+	if (is_broadcast_ether_addr(skb_mac_header(skb)))
+		return 1;
+
+	/* Treat IPv6 multicast addresses as broadcast. */
+	if (test_net2bytes(skb_mac_header(skb), 0x3333))
+		return 1;
+
+	return 0;
+}
+
+static unsigned char *try_insert_rtag(struct sk_buff *skb,
+				      struct net_device *dev, uint16_t *vlan,
+				      hanic_packet_disposition_t *disposition)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_filter_item *filter;
+	unsigned char *rtag;
+	unsigned char *dp;
+	size_t prefix_len;
+
+	/* Don't R-TAG broadcast packets.
+	 * It should be impossible to see broadcast packets, but
+	 * certain error handling paths may allow them through, so
+	 * program defensively.
+	 */
+	if (packet_broadcast(skb, dev)) {
+		*disposition = xsp->broadcast_disp;
+		return NULL;
+	} else if (xsp->arp_rtag == 0 && packet_is_arp(skb)) {
+		*disposition = xsp->broadcast_disp;
+		return NULL;
+	}
+
+	/* Figure out how big the MAC header is, also accounting
+	 * for the VLAN tag, if present. (We also need that vlan
+	 * value.)
+	 */
+	dp = skb_mac_header(skb);
+	prefix_len = 12;
+	*vlan = 0;
+	if (test_net2bytes(dp + prefix_len, ETH_P_8021Q)) {
+		*vlan = get_unaligned_be16(dp + prefix_len + 2) & VLAN_VID_MASK;
+		prefix_len += 4;
+	}
+
+	/* Test the vlan and destination mac address to see if the
+	 * filter is suppressing the R-TAG for this packet. If the
+	 * filter is present, then use the stored disposition, and
+	 * signal to the caller that no R-TAG will happen here.
+	 */
+	*disposition = HANIC_DISP_FLOOD;
+	filter = hanic_test_filter_vlan(xsp, *vlan);
+	if (filter == NULL)
+		filter = hanic_test_filter_vmac(xsp, dp, *vlan);
+
+	if (filter) {
+		*disposition = filter->disposition;
+		return NULL;
+	}
+
+	/* Shift the prefix (the ethernet mac and possible vlan tag)
+	 * forward to make room for the R-TAG, and write a stub R-TAG
+	 * in place. Don't bother with the R-TAG contents, the caller
+	 * will fill that in.
+	 */
+	dp = skb_push(skb, RTAG_HLEN);
+	if (dp == 0) {
+		netdev_warn(dev, "%s: Error inserting prefix bytes.\n",
+			    __func__);
+		return NULL;
+	}
+
+	memmove(dp, dp + RTAG_HLEN, prefix_len);
+	skb->mac_header -= RTAG_HLEN;
+
+	rtag = dp + prefix_len;
+	put_unaligned_be16(ETH_P_RTAG, rtag);
+
+	/* Clean up the skb */
+	skb_reset_mac_header(skb);
+	skb_reset_mac_len(skb);
+
+	return rtag;
+}
+
+/* At this point, the skb is tagged as necessary and ready to flood
+ * out all the network ports.
+ */
+static int dev_queue_flood(struct sk_buff *skb, struct net_device *dev)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int idx;
+
+	/* Special case: If we are injecting flood faults, then this
+	 * packet is meant to be dropped. Free the skb without sending
+	 * any packets at all.
+	 */
+	if (unlikely(test_drop_flood_out(xsp))) {
+		net_info_ratelimited(
+			"%s: FAULT INJECTION: Drop output packet on all ports.\n",
+			dev->name);
+		dev_kfree_skb(skb);
+		return 0;
+	}
+
+	/* Deliver packet clones through all the enlisted ports. */
+	for (idx = 0; idx < xsp->ports_count; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+		struct sk_buff *skb2;
+
+		/* Fault injection: inject output fault. */
+		if (unlikely(test_drop_packet_out(xsp, idx))) {
+			net_info_ratelimited(
+				"%s: FAULT INJECTION: Drop output packet on port %d (%s).\n",
+				dev->name, idx, port->name);
+			continue;
+		}
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (skb2 == 0) {
+			net_warn_ratelimited(
+				"%s: Replication failed on transmit.\n",
+				dev->name);
+			continue;
+		}
+		skb2->dev = port;
+		dev_queue_xmit(skb2);
+	}
+
+	/* No longer need the skb. */
+	dev_kfree_skb(skb);
+
+	return 0;
+}
+
+/* Start transmit -- The skb already contains a fully formatted
+ * packet, we just need to deliver it to the "wire". In this case,
+ * that means handing it over to enlisted ports for transmit. All the
+ * ports get a copy of the buffer to transmit so that they can run in
+ * parallel.
+ *
+ * If the destination is broadcast or multicast, then possibly handle
+ * the packet differently.
+ */
+static int do_hanic_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_stream_item *stream;
+	unsigned char *rtag;
+	uint16_t use_seq_num;
+	uint16_t outbound_vlan;
+	hanic_packet_disposition_t disposition;
+
+	/* If there are no enlisted ports, drop the packet. */
+	if (unlikely(xsp->ports_count <= 0)) {
+		xsp->tx_dropped += 1;
+		net_info_ratelimited(
+			"%s: No enlisted ports to transmit packet.\n",
+			dev->name);
+		return 0;
+	}
+
+	/* Count the packets and data. Don't count the duplicates. */
+	xsp->tx_packets += 1;
+	xsp->tx_bytes += skb->len;
+
+	/* If the packet is broadcast (or multicast) and we are not
+	 * flooding, then use the broadcast_disp to guide our choice
+	 * of an enlisted port. Do this handling early so that we
+	 * do not waste effort checking for tags.
+	 */
+	if (packet_broadcast(skb, dev)) {
+		if (hanic_disp_is_flood(xsp->broadcast_disp))
+			return dev_queue_flood(skb, dev);
+
+		skb->dev = hanic_disp_port(xsp, xsp->broadcast_disp);
+		dev_queue_xmit(skb);
+		return 0;
+	}
+
+	/* Look for an existing R-TAG. If it is not present, check if
+	 * we need to add it. It is possible for the R-TAG to already
+	 * be present if this is a retransmit. (Think TCP.) Also
+	 * figure out the disposition. Normally, these packets will be
+	 * flooded out, but if there is a filter, the try_insert_rtag
+	 * will set a more specific disposition.
+	 */
+	disposition = HANIC_DISP_FLOOD;
+	rtag = find_header_rtag(skb, &outbound_vlan);
+	if (rtag == NULL)
+		rtag = try_insert_rtag(skb, dev, &outbound_vlan, &disposition);
+
+	/* No R-TAG? Then this is a non-replicated packet. Depending
+	 * on the disposition configuration, send this out a single
+	 * port, or flood it to all the enlisted ports.
+	 */
+	if (rtag == NULL) {
+		if (hanic_disp_is_flood(disposition))
+			return dev_queue_flood(skb, dev);
+
+		skb->dev = hanic_disp_port(xsp, disposition);
+		dev_queue_xmit(skb);
+		return 0;
+	}
+
+	/* Do the final fixup of the R-TAG, if it is present. The
+	 * try_insert_rtag function installed the R-TAG, but as a stub
+	 * for the sequence number. Here, we put the proper seqno
+	 * value into the header. This is done here to catch
+	 * retransmissions of packets.
+	 */
+	if (rtag) {
+		unsigned char *dp = skb_mac_header(skb);
+		unsigned long flags;
+
+		spin_lock_irqsave(&xsp->stream_table_lock, flags);
+		stream = hanic_map_stream(xsp, dp, outbound_vlan);
+		if (stream) {
+			use_seq_num = stream->out_seq_num++;
+			stream->stats.tx_send += 1;
+			spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+
+			/* rtag[0]:rtag[1] is ETH_P_RTAG */
+			put_unaligned_be16(0, rtag + 2);
+			put_unaligned_be16(use_seq_num, rtag + 4);
+
+		} else {
+			spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+			net_info_ratelimited("%s: R-TAG on outbound packet "
+					     "with no matching stream.\n",
+					     dev->name);
+		}
+	}
+
+	return dev_queue_flood(skb, dev);
+}
+
+static int hanic_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	int rc;
+
+	read_lock(&hanic_devs_mutex);
+	rc = do_hanic_start_xmit(skb, dev);
+	read_unlock(&hanic_devs_mutex);
+	return rc;
+}
+
+/* Set the mac address of the hanic device. If that works, then set
+ * the mac address of all the enlisted ports as well so that they
+ * follow the master.
+ */
+static int hanic_set_mac_address(struct net_device *dev, void *addr)
+{
+	int idx;
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int rc = eth_mac_addr(dev, addr);
+
+	if (rc < 0)
+		return rc;
+
+	read_lock(&hanic_devs_mutex);
+
+	/* Set the enlisted ports to the masters new address. */
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+
+		if (port == NULL)
+			continue;
+
+		port->netdev_ops->ndo_set_mac_address(port, addr);
+	}
+
+	read_unlock(&hanic_devs_mutex);
+
+	return rc;
+}
+
+/* Enlist a port. This can be called as a Network Device Operation (ndo)
+ * in response to commands like this:
+ *
+ *    ip link set <port> master <dev>
+ */
+static int hanic_add_port(struct net_device *dev, struct net_device *port,
+			  struct netlink_ext_ack *extack)
+{
+	int port_idx;
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	if (dev == port) {
+		NL_SET_ERR_MSG(extack, "Cannot enlist port to itself.");
+		netdev_info(dev, "Cannot enlist port to itself.\n");
+		return -EPERM;
+	}
+
+	if (port->flags & IFF_UP) {
+		NL_SET_ERR_MSG(extack, "Cannot enlist port while it is up.");
+		netdev_info(dev, "Cannot enlist %s while it is up.\n",
+			    port->name);
+		return -EPERM;
+	}
+
+	if (port->flags & IFF_SLAVE) {
+		NL_SET_ERR_MSG(extack, "Interface is already enlisted.");
+		netdev_info(dev, "Interface %s is already enlisted.\n",
+			    port->name);
+		return -EPERM;
+	}
+
+	/*
+	 * This prevents enslaving other masters, which could lead to
+	 * locking messes.
+	 */
+	if (port->flags & IFF_MASTER) {
+		NL_SET_ERR_MSG(extack, "Interface is already a master.");
+		netdev_info(dev, "Interface %s is a master.\n", port->name);
+		return -EPERM;
+	}
+
+	if (port->type != ARPHRD_ETHER) {
+		NL_SET_ERR_MSG(extack, "Sorry, only Ethernet ports supported.");
+		netdev_info(dev, "Sorry, expecting %s to be Ethernet.\n",
+			    port->name);
+		return -EINVAL;
+	}
+
+	if (netdev_is_rx_handler_busy(port)) {
+		NL_SET_ERR_MSG(extack,
+			       "Interface is busy and cannot be enlisted.");
+		netdev_info(dev,
+			    "Interface %s is busy and cannot be enlisted.\n",
+			    port->name);
+		return -EBUSY;
+	}
+
+	if (port->netdev_ops->ndo_set_mac_address == 0) {
+		NL_SET_ERR_MSG(
+			extack,
+			"Sorry, enlisted port must support setting MAC address.\n");
+		netdev_info(
+			dev,
+			"Sorry, enlisted port %s doesn't support set_mac_address.\n",
+			port->name);
+		return -EINVAL;
+	}
+
+	if (unlikely(dev->addr_len != port->addr_len)) {
+		NL_SET_ERR_MSG(extack, "Strange addr_len?\n");
+		netdev_info(dev, "Port %s address format mismatch\n",
+			    port->name);
+		return -EINVAL;
+	}
+
+	write_lock(&hanic_devs_mutex);
+
+	if (xsp->ports_count >= HANIC_PORT_MAX) {
+		write_unlock(&hanic_devs_mutex);
+		NL_SET_ERR_MSG(extack, "Too many enlisted ports");
+		netdev_info(dev, "Too many enlisted ports.\n");
+		return -EINVAL;
+	}
+
+	port_idx = xsp->ports_count++;
+
+	port->flags |= IFF_SLAVE;
+
+	/* Save the port identity for when it is released. */
+	xsp->ports[port_idx].sl_dev = port;
+	xsp->ports[port_idx].ha_dev = dev;
+	memcpy(xsp->ports[port_idx].sl_save_addr, port->dev_addr,
+	       port->addr_len);
+	init_port_fault_injection(xsp, port_idx);
+
+	if (port_idx == 0) {
+		struct sockaddr use_addr;
+
+		/* First enlisted port, so take on that port's identity. */
+		use_addr.sa_family = AF_UNSPEC;
+		memcpy(use_addr.sa_data, port->dev_addr, port->addr_len);
+		eth_mac_addr(dev, &use_addr);
+
+		/* Take on this port's hw features. */
+		dev->features = port->features;
+		dev->hw_features = port->hw_features;
+		dev->max_mtu = port->max_mtu - RTAG_HLEN;
+		dev->min_mtu = port->min_mtu;
+		dev->mtu = dev->max_mtu;
+
+	} else {
+		int rc;
+		struct sockaddr use_addr;
+
+		/* Newly enlisted ports take on the identity of the master. */
+
+		use_addr.sa_family = AF_UNSPEC;
+		memcpy(use_addr.sa_data, dev->dev_addr, dev->addr_len);
+
+		rc = port->netdev_ops->ndo_set_mac_address(port, &use_addr);
+		if (rc < 0) {
+			netdev_info(dev, "Error (rc=%d) setting %s MAC.\n", rc,
+				    port->name);
+		}
+
+		/* New port constrains master's features. */
+		dev->hw_features &= port->hw_features;
+		if (dev->max_mtu > (port->max_mtu - RTAG_HLEN))
+			dev->max_mtu = port->max_mtu - RTAG_HLEN;
+		if (dev->min_mtu < port->min_mtu)
+			dev->min_mtu = port->min_mtu;
+		if (dev->mtu > dev->max_mtu)
+			dev->mtu = dev->max_mtu;
+	}
+
+	hanic_add_protocol_to_port(xsp, port_idx);
+
+	write_unlock(&hanic_devs_mutex);
+
+	/* If I (the master) am UP, then open the enlisted port. */
+	if (dev->flags & IFF_UP) {
+		int rc;
+
+		rc = DEV_OPEN(port);
+		if (rc < 0) {
+			netdev_warn(dev,
+				    "Error (errno=%d) bringing up port %s.\n",
+				    -rc, port->name);
+		}
+	}
+
+	return 0;
+}
+
+/* Delete an enlisted port.
+ * This unbinds the device from the hanic device.
+ * This might be called as an NDO operation, or directly by cleanup
+ * functions internal to the driver. When a port is released, give
+ * its identity back.
+ *
+ * ** MUST be locked by caller **
+ */
+static int do_hanic_del_port(struct net_device *dev, struct net_device *port)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct sockaddr use_addr;
+	int port_idx;
+
+	for (port_idx = 0; port_idx < xsp->ports_count; port_idx += 1) {
+		if (xsp->ports[port_idx].sl_dev == port)
+			break;
+	}
+
+	if (port_idx >= xsp->ports_count) {
+		netdev_info(dev, "Interface %s is not enlisted to me.\n",
+			    port->name);
+		return -ENODEV;
+	}
+
+	/* If the port is UP when I remove it, then bring it down. */
+	if ((port->flags & IFF_UP) == IFF_UP)
+		dev_close(port);
+
+	hanic_remove_protocol_from_port(xsp, port_idx);
+
+	port->flags &= ~IFF_SLAVE;
+	xsp->ports[port_idx].sl_dev = 0;
+	xsp->ports[port_idx].ha_dev = 0;
+
+	/* Give the released port its old identity back. */
+	use_addr.sa_family = AF_UNSPEC;
+	memcpy(use_addr.sa_data, xsp->ports[port_idx].sl_save_addr,
+	       dev->addr_len);
+	port->netdev_ops->ndo_set_mac_address(port, &use_addr);
+
+	/* Decrement port count and shift the top of the
+	 * table down to fill the hole.
+	 */
+	xsp->ports_count -= 1;
+	if (xsp->ports_count > port_idx)
+		xsp->ports[port_idx] = xsp->ports[xsp->ports_count];
+
+	xsp->ports[xsp->ports_count].sl_dev = 0;
+	xsp->ports[xsp->ports_count].ha_dev = 0;
+
+	/* If this was the prime port, then find a new prime port. */
+	if (port_idx == 0) {
+		if (xsp->ports_count == 0) {
+			netdev_info(dev, "Ran out of enlisted ports.\n");
+		} else {
+			netdev_info(dev, "Switch prime port from %s to %s.\n",
+				    port->name, xsp->ports[0].sl_dev->name);
+		}
+	}
+
+	return 0;
+}
+
+/* ** Must NOT be locked by caller ** */
+static int hanic_del_port(struct net_device *dev, struct net_device *port)
+{
+	int rc;
+
+	write_lock(&hanic_devs_mutex);
+	rc = do_hanic_del_port(dev, port);
+	write_unlock(&hanic_devs_mutex);
+	return rc;
+}
+
+static void hanic_get_stats64(struct net_device *dev,
+			      struct rtnl_link_stats64 *storage)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	storage->rx_packets = xsp->rx_packets;
+	storage->tx_packets = xsp->tx_packets;
+	storage->rx_bytes = xsp->rx_bytes;
+	storage->tx_bytes = xsp->tx_bytes;
+	storage->rx_dropped = xsp->rx_dropped;
+	storage->tx_dropped = xsp->tx_dropped;
+}
+
+static const struct net_device_ops hanic_netdev_ops = {
+	.ndo_open = hanic_open,
+	.ndo_stop = hanic_stop,
+	.ndo_start_xmit = hanic_start_xmit,
+	.ndo_set_mac_address = hanic_set_mac_address,
+	.ndo_add_slave = hanic_add_port,
+	.ndo_del_slave = hanic_del_port,
+	.ndo_get_stats64 = hanic_get_stats64
+};
+
+static const struct header_ops hanic_header_ops = {
+	.create = eth_header,
+	.parse = eth_header_parse,
+};
+
+/* The kernel calls this to initialize each device. This is a basic
+ * initialization, where I set up the common properties of the
+ * device. Think of it as the constructor.
+ */
+static void hanic_init(struct net_device *dev)
+{
+	struct hanic_priv *xsp;
+	struct sockaddr use_addr;
+	int idx;
+
+	/* Basic setup. This device is kinda like ethernet. */
+	ether_setup(dev);
+
+	dev->flags |= IFF_MASTER;
+
+	dev->netdev_ops = &hanic_netdev_ops;
+	dev->header_ops = &hanic_header_ops;
+	/* Reserve space for an R-TAG. */
+	dev->hard_header_len += 8;
+
+	/* Initialize MAC address for this device. */
+	use_addr.sa_family = AF_PACKET;
+	use_addr.sa_data[0] = 0x22;
+	use_addr.sa_data[1] = 0x01;
+	use_addr.sa_data[2] = 0x02;
+	use_addr.sa_data[3] = 0x03;
+	use_addr.sa_data[4] = 0x04;
+	use_addr.sa_data[5] = 0x05;
+	eth_mac_addr(dev, &use_addr);
+
+	dev->min_mtu = 0;
+	dev->max_mtu = 0;
+	dev->mtu = 0;
+
+	/* Initialize some key parts of the priv structure. */
+	xsp = netdev_priv(dev);
+	xsp->dev = dev;
+	INIT_LIST_HEAD(&xsp->devs_list);
+	xsp->ports_count = 0;
+	xsp->broadcast_disp = HANIC_DISP_FLOOD;
+	xsp->arp_rtag = ARP_RTAG_DEFAULT;
+	xsp->multicast_rtag = MULTICAST_RTAG_DEFAULT;
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		xsp->ports[idx].sl_dev = 0;
+		xsp->ports[idx].ha_dev = 0;
+	}
+
+	hash_init(xsp->filter_by_vlan_table);
+	hash_init(xsp->filter_by_vmac_table);
+	hash_init(xsp->stream_table);
+	spin_lock_init(&xsp->stream_table_lock);
+	xsp->ticks_per_reset = TICKS_PER_RESET;
+	init_hanic_fault_injection(xsp);
+
+	/* Attach the sysfs attributes to this device. */
+	hanic_device_sysfs(xsp);
+}
+
+/* Create a hanic interface device from the name pattern. */
+int hanic_dev_alloc(const char *name_pattern)
+{
+	int rc;
+	struct net_device *dev;
+
+	dev = alloc_netdev(sizeof(struct hanic_priv), name_pattern,
+			   NET_NAME_ENUM, hanic_init);
+	if (dev == 0) {
+		pr_warn("hanic: Unable to allocate hanic device.\n");
+		return -ENOMEM;
+	}
+
+	/* Ready. Register the device. */
+	rc = register_netdev(dev);
+	if (rc < 0) {
+		free_netdev(dev);
+		return rc;
+	}
+
+	add_dev_to_list(dev);
+
+	return 0;
+}
+
+/* This hanic device is interested in things that happen to enlisted
+ * ports, so this function is registered with the netdev notifier to
+ * be notified when things happen. Filter out the events that are not
+ * interesting.
+ */
+int hanic_dev_netdevice_event(struct net_device *edev, unsigned long event)
+{
+	struct hanic_priv *xsp = 0;
+	int port_idx = -1;
+	struct list_head *cur = 0;
+
+	/* I'm only interested in things that happen to IFF_SLAVE. */
+	if ((edev->flags & IFF_SLAVE) == 0)
+		return NOTIFY_OK;
+
+	/* We might be manipulating the enlisted ports of a device, so
+	 * make sure we are write-locked.
+	 */
+	write_lock(&hanic_devs_mutex);
+
+	/* OK, the event device is somebody's IFF_SIAVE. Is it one of mine? */
+	list_for_each (cur, &hanic_devs_list) {
+		int idx;
+
+		xsp = list_entry(cur, struct hanic_priv, devs_list);
+
+		for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+			if (xsp->ports[idx].sl_dev == edev) {
+				port_idx = idx;
+				break;
+			}
+		}
+
+		if (port_idx >= 0)
+			break;
+	}
+
+	/* Nope, not one of my enlisted ports. */
+	if (port_idx < 0) {
+		write_unlock(&hanic_devs_mutex);
+		return NOTIFY_OK;
+	}
+
+	switch (event) {
+	case NETDEV_UNREGISTER:
+		netdev_info(xsp->dev, "Releasing %s, which is unregistering.\n",
+			    xsp->ports[port_idx].sl_dev->name);
+		do_hanic_del_port(xsp->dev, xsp->ports[port_idx].sl_dev);
+		break;
+	default:
+		break;
+	}
+
+	write_unlock(&hanic_devs_mutex);
+	return NOTIFY_OK;
+}
+
+/* Perform cleanup on the hanic device to make it ready to be
+ * unregistered and freed. Note this includes removing in from
+ * the hanic_devs_list. That means it must be locked.
+ */
+static void do_cleanup_dev(struct net_device *dev)
+{
+	int idx;
+	struct hanic_priv *xsp = netdev_priv(dev);
+
+	/* delete the enlisted ports. */
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		if (xsp->ports[idx].sl_dev)
+			do_hanic_del_port(dev, xsp->ports[idx].sl_dev);
+	}
+
+	/* Clean up the filters tables. */
+	hanic_filters_clear(xsp);
+	/* Clean up the stream table */
+	hanic_map_stream_clear(xsp);
+
+	list_del(&xsp->devs_list);
+}
+
+/* Given the name of a hanic interface, delete that interface. Find it
+ * in the device table, clean it up, and free it.
+ */
+static int do_dev_free_byname(const char *name)
+{
+	struct list_head *cur;
+	struct net_device *dev;
+	struct hanic_priv *xsp;
+
+	for (cur = hanic_devs_list.next; cur != &hanic_devs_list;
+	     cur = cur->next) {
+		xsp = list_entry(cur, struct hanic_priv, devs_list);
+		dev = xsp->dev;
+		if (strcmp(dev->name, name) == 0)
+			break;
+	}
+	if (cur == &hanic_devs_list)
+		return -ENODEV;
+
+	do_cleanup_dev(dev);
+	unregister_netdev(dev);
+	free_netdev(dev);
+	return 0;
+}
+
+/* Wrap the dev_free_byname in the approprate locks. We need to hold
+ * the hanic_devs_mutex because we are manipulating the devs list, and
+ * we need the rtnl_lock() because we will be manipulating kernel
+ * network devices.
+ */
+int hanic_dev_free_byname(const char *name)
+{
+	int rc;
+
+	rtnl_lock();
+	write_lock(&hanic_devs_mutex);
+	rc = do_dev_free_byname(name);
+	write_unlock(&hanic_devs_mutex);
+	rtnl_unlock();
+	return rc;
+}
+
+/* Initialize the device tables and any global variables related to
+ * hanic. This is called by the module init. No actual devices are
+ * created here, that happens later when the user requests that they
+ * be created.
+ */
+int hanic_dev_init(void)
+{
+	rwlock_init(&hanic_devs_mutex);
+	INIT_LIST_HEAD(&hanic_devs_list);
+	return 0;
+}
+
+/* Release all the hanic devices. This is called when the module is
+ * being unloaded.
+ */
+void hanic_dev_release_all(void)
+{
+	struct hanic_priv *xsp;
+	struct net_device *dev;
+
+	/* Scan the entire table of existing interfaces, looking for
+	 * devices to release.
+	 */
+	write_lock(&hanic_devs_mutex);
+	while ((xsp = list_first_entry_or_null(&hanic_devs_list,
+					       struct hanic_priv, devs_list))) {
+		dev = xsp->dev;
+		/* Amongst other things, this removes dev from the
+		 * hanic_devs_list.
+		 */
+		rtnl_lock();
+		do_cleanup_dev(dev);
+		rtnl_unlock();
+		unregister_netdev(dev);
+		free_netdev(dev);
+	}
+	write_unlock(&hanic_devs_mutex);
+}
+
+ssize_t hanic_dev_show_interfaces(char *buf, size_t len)
+{
+	struct list_head *cur;
+	struct hanic_priv *xsp;
+	struct net_device *dev;
+	ssize_t res = 0;
+	int rc;
+
+	read_lock(&hanic_devs_mutex);
+	list_for_each (cur, &hanic_devs_list) {
+		xsp = list_entry(cur, struct hanic_priv, devs_list);
+		dev = xsp->dev;
+
+		rc = snprintf(buf, len, "%s\n", dev->name);
+		if (rc >= len) {
+			buf += len;
+			res += len;
+			len = 0;
+			break;
+		} else {
+			buf += rc;
+			res += rc;
+			len -= rc;
+		}
+	}
+	read_unlock(&hanic_devs_mutex);
+
+	return res;
+}
+
+/* This function is called at regular intervals to check for protocol
+ * timeouts. Scan the streams of all the devices.
+ */
+void hanic_dev_protocol_tick(void)
+{
+	struct list_head *cur;
+	struct hanic_priv *xsp;
+	struct hanic_stream_item *stream;
+	unsigned long flags;
+	int idx;
+
+	read_lock_irqsave(&hanic_devs_mutex, flags);
+	list_for_each (cur, &hanic_devs_list) {
+		xsp = list_entry(cur, struct hanic_priv, devs_list);
+		/* Scan the streams in the NIC. */
+		spin_lock(&xsp->stream_table_lock);
+		hash_for_each (xsp->stream_table, idx, stream, stream_table) {
+			hanic_stream_protocol_tick(xsp, stream);
+		}
+		spin_unlock(&xsp->stream_table_lock);
+	}
+	read_unlock_irqrestore(&hanic_devs_mutex, flags);
+}
diff --git a/drivers/net/hanic/hanic_filter.c b/drivers/net/hanic/hanic_filter.c
new file mode 100644
index 000000000000..cc6bf25ab8b6
--- /dev/null
+++ b/drivers/net/hanic/hanic_filter.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+/* Each hanic device keeps a map of filters that prevent matching
+ * streams from being replicatedl
+ */
+#include "hanic_priv.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+
+/* keep a kmem_cache of the filter items. This is not for speed, since
+ * allocation and release of these items is rare and happens in human
+ * time. This is mostly about convenience.
+ */
+static struct kmem_cache *hanic_filter_pool;
+
+static inline struct hanic_filter_item *alloc_filter_item(gfp_t flags)
+{
+	return kmem_cache_alloc(hanic_filter_pool, flags);
+}
+
+static inline void free_filter_item(struct hanic_filter_item *item)
+{
+	kmem_cache_free(hanic_filter_pool, item);
+}
+
+/* The _test_ functions. */
+struct hanic_filter_item *hanic_test_filter_vlan(struct hanic_priv *xsp,
+						 uint16_t vlan)
+{
+	struct hanic_filter_item *cur;
+
+	hash_for_each_possible (xsp->filter_by_vlan_table, cur, filter_table,
+				vlan) {
+		if (vlan == cur->vlan)
+			return cur;
+	}
+
+	return NULL;
+}
+
+struct hanic_filter_item *hanic_test_filter_vmac(struct hanic_priv *xsp,
+						 const uint8_t *mac,
+						 uint16_t vlan)
+{
+	const uint64_t key = stream_item_key(mac, vlan);
+	struct hanic_filter_item *cur;
+
+	hash_for_each_possible (xsp->filter_by_vmac_table, cur, filter_table,
+				key) {
+		if (key == stream_item_key(cur->mac, cur->vlan))
+			return cur;
+	}
+
+	return NULL;
+}
+
+/* The _set_ functions. */
+struct hanic_filter_item *hanic_set_filter_vlan(struct hanic_priv *xsp,
+						uint16_t vlan)
+{
+	struct hanic_filter_item *filt = 0;
+
+	filt = hanic_test_filter_vlan(xsp, vlan);
+	if (filt)
+		return filt;
+
+	filt = alloc_filter_item(GFP_ATOMIC);
+	if (filt == 0)
+		return NULL;
+
+	memset(filt->mac, 0, sizeof(filt->mac));
+	filt->vlan = vlan;
+	filt->disposition = HANIC_DISP_DEFAULT;
+	hash_add(xsp->filter_by_vlan_table, &filt->filter_table, vlan);
+
+	return filt;
+}
+
+struct hanic_filter_item *
+hanic_set_filter_vmac(struct hanic_priv *xsp, const uint8_t *mac, uint16_t vlan)
+{
+	const uint64_t key = stream_item_key(mac, vlan);
+	struct hanic_filter_item *filt = 0;
+
+	filt = hanic_test_filter_vmac(xsp, mac, vlan);
+	if (filt)
+		return filt;
+
+	filt = alloc_filter_item(GFP_ATOMIC);
+	if (filt == 0)
+		return NULL;
+
+	memcpy(filt->mac, mac, sizeof(filt->mac));
+	filt->vlan = vlan;
+	filt->disposition = HANIC_DISP_DEFAULT;
+	hash_add(xsp->filter_by_vmac_table, &filt->filter_table, key);
+
+	return filt;
+}
+
+/* The _clr_ functions. */
+void hanic_clr_filter_vlan(struct hanic_priv *xsp, uint16_t vlan)
+{
+	struct hanic_filter_item *filt = hanic_test_filter_vlan(xsp, vlan);
+
+	if (filt == 0)
+		return;
+
+	hash_del(&filt->filter_table);
+	free_filter_item(filt);
+}
+
+void hanic_clr_filter_vmac(struct hanic_priv *xsp, const uint8_t *mac,
+			   uint16_t vlan)
+{
+	struct hanic_filter_item *filt = hanic_test_filter_vmac(xsp, mac, vlan);
+
+	if (filt == 0)
+		return;
+
+	hash_del(&filt->filter_table);
+	free_filter_item(filt);
+}
+
+/* This function is used to remove all the filter items for the
+ * device. This is used to clean the device out.
+ */
+void hanic_filters_clear(struct hanic_priv *xsp)
+{
+	int idx;
+	struct hanic_filter_item *cur;
+	struct hlist_node *tmp;
+
+	/* Clean up the filter tables */
+	hash_for_each_safe (xsp->filter_by_vlan_table, idx, tmp, cur,
+			    filter_table) {
+		hash_del(&cur->filter_table);
+		free_filter_item(cur);
+	}
+
+	hash_for_each_safe (xsp->filter_by_vmac_table, idx, tmp, cur,
+			    filter_table) {
+		hash_del(&cur->filter_table);
+		free_filter_item(cur);
+	}
+}
+
+int hanic_filters_init(void)
+{
+	hanic_filter_pool = kmem_cache_create(
+		"hanic_filter_item", sizeof(struct hanic_filter_item), 0, 0, 0);
+	if (hanic_filter_pool == 0) {
+		pr_info("hanic_init_module: Unable to allocate hanic filter pool\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void hanic_filters_remove(void)
+{
+	kmem_cache_destroy(hanic_filter_pool);
+}
diff --git a/drivers/net/hanic/hanic_main.c b/drivers/net/hanic/hanic_main.c
new file mode 100644
index 000000000000..fd054a33cb6b
--- /dev/null
+++ b/drivers/net/hanic/hanic_main.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+#include "hanic_priv.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+MODULE_LICENSE("GPL");
+
+static struct delayed_work hanic_protocol_clock;
+
+/* Keep the timer going.
+ * And deliver the tick to the devices.
+ * The dwork pointer should be pointing to hanic_protocol_clock.
+ */
+static void hanic_protocol_tick(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+
+	if (unlikely(dwork != &hanic_protocol_clock))
+		pr_warn("%s: Unexpected work struct? Ignoring.\n", __func__);
+	else
+		schedule_delayed_work(dwork, JIFFIES_PER_HANIC_TICK);
+	hanic_dev_protocol_tick();
+}
+
+static int do_netdevice_event(struct notifier_block *self, unsigned long event,
+			      void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+	return hanic_dev_netdevice_event(dev, event);
+}
+
+static struct notifier_block hanic_netdevice_notifier = {
+	.notifier_call = do_netdevice_event
+};
+
+/* Called by the kernel to initialize the driver. */
+static int hanic_init_module(void)
+{
+	int rc;
+
+	rc = hanic_streams_init();
+	if (rc < 0) {
+		pr_info("%s: streams_init failed.\n", __func__);
+		return rc;
+	}
+
+	rc = hanic_filters_init();
+	if (rc < 0) {
+		pr_info("%s: filters_init failed.\n", __func__);
+		hanic_streams_remove();
+		return rc;
+	}
+
+	rc = hanic_dev_init();
+	if (rc < 0) {
+		pr_info("%s: dev_init failed.\n", __func__);
+		hanic_filters_remove();
+		hanic_streams_remove();
+		return rc;
+	}
+
+	rc = hanic_init_netns();
+	if (rc < 0) {
+		pr_info("%s: init_netns failed.\n", __func__);
+		hanic_filters_remove();
+		hanic_streams_remove();
+		return rc;
+	}
+
+	register_netdevice_notifier(&hanic_netdevice_notifier);
+
+	pr_info("%s: HANIC_TICKS_PER_SECOND=%d, "
+		"TICKS_PER_RESET=%d (JIFFIES_PER_HANIC_TICK=%d, HZ=%d)\n",
+		__func__, HANIC_TICKS_PER_SECOND, TICKS_PER_RESET,
+		JIFFIES_PER_HANIC_TICK, HZ);
+
+	INIT_DELAYED_WORK(&hanic_protocol_clock, hanic_protocol_tick);
+	schedule_delayed_work(&hanic_protocol_clock, JIFFIES_PER_HANIC_TICK);
+
+	return 0;
+}
+
+/* Remove the mess that is (was) the device driver. Leave no trace. */
+static void hanic_remove_module(void)
+{
+	/* Stop the protocol timer clock. */
+	cancel_delayed_work(&hanic_protocol_clock);
+
+	unregister_netdevice_notifier(&hanic_netdevice_notifier);
+	hanic_dev_release_all();
+	hanic_remove_netns();
+	hanic_filters_remove();
+	hanic_streams_remove();
+}
+
+module_init(hanic_init_module);
+module_exit(hanic_remove_module);
diff --git a/drivers/net/hanic/hanic_netns.c b/drivers/net/hanic/hanic_netns.c
new file mode 100644
index 000000000000..03c2fb0646f9
--- /dev/null
+++ b/drivers/net/hanic/hanic_netns.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+/* This handles the netns aspects of the hanic device[s]. */
+
+#include "hanic_priv.h"
+
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+unsigned int hanic_net_id __read_mostly;
+
+/* These are operations that control the hanic presence within net scopes.
+ */
+
+static int __net_init hanic_net_init(struct net *net)
+{
+	int rc;
+	struct hanic_netns *xnp = net_generic(net, hanic_net_id);
+
+	xnp->net = net;
+	rc = hanic_init_sysfs(xnp);
+	return rc;
+}
+
+static void __net_exit hanic_net_exit(struct net *net)
+{
+	struct hanic_netns *xnp = net_generic(net, hanic_net_id);
+	hanic_remove_sysfs(xnp);
+}
+
+static struct pernet_operations hanic_net_ops = {
+	.init = hanic_net_init,
+	.exit = hanic_net_exit,
+	.id = &hanic_net_id,
+	.size = sizeof(struct hanic_netns),
+};
+
+int hanic_init_netns(void)
+{
+	int rc;
+
+	rc = register_pernet_subsys(&hanic_net_ops);
+	if (rc) {
+		pr_info("hanic: Unable to register pernet subsystem.\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+void hanic_remove_netns(void)
+{
+	unregister_pernet_subsys(&hanic_net_ops);
+}
diff --git a/drivers/net/hanic/hanic_priv.h b/drivers/net/hanic/hanic_priv.h
new file mode 100644
index 000000000000..6ef3e2742b2a
--- /dev/null
+++ b/drivers/net/hanic/hanic_priv.h
@@ -0,0 +1,408 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __hanic_priv_H
+#define __hanic_priv_H
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/hashtable.h>
+#include <asm/unaligned.h>
+
+/* Protocol number for the 802.1cb R-TAG */
+#define ETH_P_RTAG 0xf1c1
+
+/* This is the size of the RTAG. */
+#define RTAG_HLEN 6
+
+#define SEQ_HISTORY_LENGTH 256
+
+/* This defines the granularity of the 802.1cb timer. The standard
+ * requires that this be at least 100/sec.
+ */
+#define HANIC_TICKS_PER_SECOND 100
+
+/* Calculate the delay (in jiffies) between HANIC clock ticks:
+ *
+ *  HZ = jiffies/sec
+ *  HTPS = Hanic ticks/sec
+ *
+ *  HZ/HTPS = (jiffies/sec) / (hanic ticks/sec)
+ *	    = (jiffies/sec) * (sec/hanic ticks)
+ *	    = jiffies / hanic tick
+ *
+ * Realistic systems have a HZ values from 100 to maybe 1000, so this
+ * timer is not going to be super accurate. That doesn't matter
+ * much, but we absolutely do NOT want JIFFIES_PER_HANIC_TICK to round
+ * down to zero, so saturate it at 1.
+ */
+#define JIFFIES_PER_HANIC_TICK                                                 \
+	(((HZ) < (HANIC_TICKS_PER_SECOND)) ?                                   \
+		 1 :                                                           \
+		 ((HZ) / (HANIC_TICKS_PER_SECOND)))
+
+/* Calculate the msec/hanic tick (this value is only used to caluclate
+ * the default TICKS_PER_RESET from the compile time default reset time):
+ *
+ * HZ = jiffies/sec
+ * JIFFIES_PER_HANIC_TICK = jiffies/tick
+ * S/tick = JIFFIES_PER_HANIC_TICK / HZ
+ *	  = (jiffies/tick) / (jiffies/sec)
+ *
+ * ms/tick = 1000 * S/tick
+ *	   = 1000 * JIFFIES_PER_HANIC_TICK / HZ
+ */
+#define MSEC_PER_HANIC_TICK (1000 * JIFFIES_PER_HANIC_TICK / HZ)
+
+/* This is the default value for the frerSeqRcvyResetMSec value as
+ * defined in 802.1cb. This value must be at least 2 timer ticks.
+ */
+#define HANIC_SEQ_RCVY_RESET_MSEC 3000
+
+/* This many protocol ticks of idleness triggers a reset. Note that
+ * this value must be >=2 for reasonable driver behavior. This is the
+ * default value for the per-device tick_per_reset, which can be set
+ * on demand.
+ */
+#define TICKS_PER_RESET                                                        \
+	((HANIC_SEQ_RCVY_RESET_MSEC < 2 * MSEC_PER_HANIC_TICK) ?               \
+		 2 :                                                           \
+		 (HANIC_SEQ_RCVY_RESET_MSEC / MSEC_PER_HANIC_TICK))
+
+/* There are up to this many port devices. */
+#define HANIC_PORT_MAX 4
+
+struct hanic_priv;
+struct hanic_stream_item;
+
+/* Hanic-specific parts of the net device structure. */
+
+/* Type and functions for selecting packet disposition. This is used
+ * for selecting where to send broadcast packets, or untagged packets,
+ * etc.
+ *
+ * The packet disposition can be FLOOD, or the number for a port to
+ * use. If flood, the hanic_disp_is_flood() function returns
+ * true. Otherwise, the hanic_disp_port() function returns the
+ * net_device for the output port to use.
+ */
+typedef int hanic_packet_disposition_t;
+
+#define HANIC_DISP_FLOOD (-1)
+#define HANIC_DISP_DEFAULT (0)
+
+/* The hash functions need a 64bit numeric key, so generate one from
+ * the key that we really want to use.
+ *
+ * This function assumes the mac address is ETH_ALEN (6) bytes, and
+ * the vlan id is 2 bytes. Thus we can use the mac and vlan as a 64bit
+ * key. (Note: This is not the hash function. This is an integral key
+ * that can be hashed by the hashtable infrastructure.)
+ */
+static inline uint64_t stream_item_key(const uint8_t mac[ETH_ALEN],
+				       uint16_t vlan)
+{
+	int idx;
+	uint64_t key;
+
+	key = mac[0];
+	for (idx = 1; idx < ETH_ALEN; idx += 1) {
+		key <<= 8;
+		key |= mac[idx];
+	}
+	key <<= 16;
+	key |= vlan;
+	return key;
+}
+
+/* Each remote that we talk to (or that talks to us) has one of these
+ * entries. The entry represents the stream out and the stream in. In
+ * any case, the item contains the persistent variables needed for the
+ * stream.
+ *
+ * Instances of this object should be protected by the
+ * xsp->stream_table_mutex lock.
+ */
+#define HANIC_OOO_MAX_STRIDE 32
+#define STREAM_ATTR_NAME_LEN 32
+struct hanic_stream_item_stats {
+	unsigned long tx_send;
+	unsigned long rx_rogue_packets;
+	unsigned long rx_passed[HANIC_PORT_MAX];
+	unsigned long rx_dropped_duplicates[HANIC_PORT_MAX];
+	unsigned long rx_seq_recovery_resets;
+	unsigned long rx_out_of_order[HANIC_OOO_MAX_STRIDE];
+};
+struct hanic_stream_item {
+	struct hlist_node stream_table;
+	/* The key */
+	uint8_t mac[ETH_ALEN];
+	uint16_t vlan;
+	/* Out stream state. */
+	uint16_t out_seq_num;
+	/* In stream state. */
+	char take_any; /* flag */
+	uint16_t recov_seq_num;
+	char seq_space[SEQ_HISTORY_LENGTH];
+	int seq_space_ptr;
+	unsigned int in_reset_timer;
+	/* 802.1cb Statictics */
+	struct hanic_stream_item_stats stats;
+	/* Attribute handle */
+	char attr_name[STREAM_ATTR_NAME_LEN];
+	struct device_attribute attr;
+};
+
+extern struct hanic_stream_item *
+hanic_map_stream(struct hanic_priv *xsp, const void *mac, uint16_t vlan);
+extern void hanic_map_stream_clear(struct hanic_priv *xsp);
+
+extern void hanic_stream_protocol_tick(struct hanic_priv *xsp,
+				       struct hanic_stream_item *stream);
+extern int hanic_streams_init(void);
+extern void hanic_streams_remove(void);
+
+/* The hanic filter is a way to select the disposition for packets
+ * based on vlan or vlan and mac address. Normally, outbound backets
+ * are R-TAG'ed and processed. But if an item matches a filter_item,
+ * then it is either flooded out or sent out a single port.
+ *
+ * There are 2 filters: the vlan filter and the vmac filter. The
+ * vlan matches packets by vlan only, and the vmac filter matches
+ * packets using vlan and mac. In either case, the _test_ functions
+ * return a filter item if the item matches a filter set, or returns a
+ * null pointer if the key doesn't match.
+ *
+ * The _set_ functions match a filter by key if it exists, or creates
+ * a new filter item if the key doesn't exist. If a _set_ function
+ * returns null, then there is a malloc error.
+ *
+ * The _clr_ functions clear the key from the table.
+ */
+struct hanic_filter_item {
+	struct hlist_node filter_table;
+	/* The key */
+	uint8_t mac[ETH_ALEN];
+	uint16_t vlan;
+	/* How to handle matching items */
+	hanic_packet_disposition_t disposition;
+};
+
+extern struct hanic_filter_item *hanic_test_filter_vlan(struct hanic_priv *xsp,
+							uint16_t vlan);
+extern struct hanic_filter_item *hanic_test_filter_vmac(struct hanic_priv *xsp,
+							const uint8_t *mac,
+							uint16_t vlan);
+extern struct hanic_filter_item *hanic_set_filter_vlan(struct hanic_priv *xsp,
+						       uint16_t vlan);
+extern struct hanic_filter_item *hanic_set_filter_vmac(struct hanic_priv *xsp,
+						       const uint8_t *mac,
+						       uint16_t vlan);
+extern void hanic_clr_filter_vlan(struct hanic_priv *xsp, uint16_t vlan);
+extern void hanic_clr_filter_vmac(struct hanic_priv *xsp, const uint8_t *mac,
+				  uint16_t vlan);
+extern void hanic_filters_clear(struct hanic_priv *xsp);
+extern int hanic_filters_init(void);
+extern void hanic_filters_remove(void);
+
+/* This is information about an enlisted port. There are multiple port
+ * devices bound to each hanic device.
+ */
+struct hanic_port_info {
+	struct net_device *sl_dev;
+	/* Saved address for enlisted port. */
+	unsigned char sl_save_addr[MAX_ADDR_LEN];
+	/* Back pointer */
+	struct net_device *ha_dev;
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+	/* Fault injection/debug state */
+	atomic_t test_drop_packet_in;
+	atomic_t test_drop_packet_out;
+#endif
+};
+
+/* Information about the hanic device. */
+struct hanic_priv {
+	/* Back pointer to the net_device. */
+	struct net_device *dev;
+	/* My position in the devs_list. */
+	struct list_head devs_list;
+	/* Enlisted ports */
+	struct hanic_port_info ports[HANIC_PORT_MAX];
+	int ports_count;
+	hanic_packet_disposition_t broadcast_disp;
+	bool arp_rtag;
+	bool multicast_rtag;
+	/* Filter tables */
+	DECLARE_HASHTABLE(filter_by_vlan_table, 4);
+	DECLARE_HASHTABLE(filter_by_vmac_table, 8);
+	/* table of streams */
+	DECLARE_HASHTABLE(stream_table, 8);
+	spinlock_t stream_table_lock;
+	/* Reset timer to use in each stream. */
+	unsigned int ticks_per_reset;
+	/* Statistics. */
+	unsigned long rx_packets;
+	unsigned long rx_bytes;
+	unsigned long rx_dropped;
+	unsigned long tx_packets;
+	unsigned long tx_bytes;
+	unsigned long tx_dropped;
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+	/* Fault injection/debug state */
+	atomic_t test_drop_flood_out;
+#endif
+};
+
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+static inline void init_hanic_fault_injection(struct hanic_priv *xsp)
+{
+	atomic_set(&xsp->test_drop_flood_out, 0);
+}
+
+static inline void init_port_fault_injection(struct hanic_priv *xsp,
+					     int port_idx)
+{
+	atomic_set(&xsp->ports[port_idx].test_drop_packet_in, 0);
+	atomic_set(&xsp->ports[port_idx].test_drop_packet_out, 0);
+}
+
+static inline int test_drop_flood_out(struct hanic_priv *xsp)
+{
+	int val;
+
+	val = atomic_dec_if_positive(&xsp->test_drop_flood_out);
+	if (unlikely(val > 0))
+		return 1;
+
+	return 0;
+}
+
+static inline int test_drop_packet_out(struct hanic_priv *xsp, int port_idx)
+{
+	int val;
+
+	val = atomic_dec_if_positive(
+		&xsp->ports[port_idx].test_drop_packet_out);
+	if (unlikely(val > 0))
+		return 1;
+
+	return 0;
+}
+
+static inline int test_drop_packet_in(struct hanic_priv *xsp, int port_idx)
+{
+	int val;
+
+	val = atomic_dec_if_positive(&xsp->ports[port_idx].test_drop_packet_in);
+	if (unlikely(val > 0))
+		return 1;
+
+	return 0;
+}
+
+#else
+static inline void init_hanic_fault_injection(struct hanic_priv *xsp)
+{
+}
+
+static inline void init_port_fault_injection(struct hanic_priv *xsp,
+					     int port_idx)
+{
+}
+
+static inline int test_drop_flood_out(struct hanic_priv *xsp)
+{
+	return 0;
+}
+
+static inline int test_drop_packet_out(struct hanic_priv *xsp, int port_idx)
+{
+	return 0;
+}
+
+static inline int test_drop_packet_in(struct hanic_priv *xsp, int port_idx)
+{
+	return 0;
+}
+#endif
+
+/* These functions use a disposition value to select a port to use. If
+ * the disposition is FLOOD, then the hanic_disp_is_flood() function
+ * returns true. Otherwise, the hanic_disp_port can be used to select
+ * the net_device to use.
+ */
+static inline int hanic_disp_is_flood(hanic_packet_disposition_t pd)
+{
+	return pd < 0;
+}
+
+static inline struct net_device *hanic_disp_port(struct hanic_priv *xsp,
+						 hanic_packet_disposition_t pd)
+{
+	if (pd < 0)
+		return xsp->ports[0].sl_dev;
+	if (pd >= HANIC_PORT_MAX)
+		return xsp->ports[0].sl_dev;
+	if (xsp->ports[pd].sl_dev == NULL)
+		return xsp->ports[0].sl_dev;
+
+	return xsp->ports[pd].sl_dev;
+}
+
+/* Per-namespace aspects of the hanic driver. */
+struct hanic_netns {
+	struct net *net;
+	struct class_attribute class_attr_hanic_interfaces;
+};
+
+/* Create device. */
+extern int hanic_dev_init(void);
+extern int hanic_dev_alloc(const char *name_pattern);
+extern int hanic_dev_free_byname(const char *name);
+extern void hanic_dev_release_all(void);
+
+/* protocol handling. */
+extern void hanic_add_protocol_to_port(struct hanic_priv *xsp, int port_idx);
+extern void hanic_remove_protocol_from_port(struct hanic_priv *xsp,
+					    int port_idx);
+
+/* Events about devices in the system. */
+extern int hanic_dev_netdevice_event(struct net_device *dev,
+				     unsigned long event);
+
+/* Ticks of the protocol clock. */
+extern void hanic_dev_protocol_tick(void);
+
+/* sysfs show interfaces. */
+extern ssize_t hanic_dev_show_interfaces(char *buf, size_t len);
+
+/* Namespace specific functions. */
+extern int hanic_init_netns(void);
+extern void hanic_remove_netns(void);
+
+/* sysfs functions */
+extern int hanic_init_sysfs(struct hanic_netns *xnp);
+extern void hanic_remove_sysfs(struct hanic_netns *xnp);
+extern void hanic_device_sysfs(struct hanic_priv *xsp);
+extern int hanic_sysfs_add_attr(struct hanic_priv *xsp, struct attribute *attr);
+extern void hanic_sysfs_rem_attr(struct hanic_priv *xsp,
+				 struct attribute *attr);
+extern ssize_t hanic_stream_show(struct device *d,
+				 struct device_attribute *attr, char *buf);
+
+/* Return TRUE if the 16bit (2 byte) val matches the value pointed
+ * to by ptr. The value in memory is assumed to be big-endian (network
+ * byte order) and is not expected to be aligned.
+ */
+static inline int test_net2bytes(const unsigned char *ptr, uint16_t val)
+{
+	return get_unaligned_be16(ptr) == val;
+}
+
+#endif
diff --git a/drivers/net/hanic/hanic_protocol.c b/drivers/net/hanic/hanic_protocol.c
new file mode 100644
index 000000000000..7ab51e0e8908
--- /dev/null
+++ b/drivers/net/hanic/hanic_protocol.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+#include "hanic_priv.h"
+#include <linux/netdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/printk.h>
+
+/* The seq_space is a bool array from 0 to SEQ_HISTORY_LENGTH-1. Each
+ * entry represents a past packet, with higher indices being farther
+ * into the past.
+ *
+ * The cell at seq_space[seq_space_ptr] is TRUE by design.
+ *
+ * Logically, recov_seq_num points to the cell as seq_space_ptr, so
+ * that cell should be true, unless take_any==0
+ *
+ * NOTE: Entering this function, -SEQ_HISTORY_LENGTH < delta < 0
+ * The algorithm already knows that seq_space[seq_space_ptr] == 1, and
+ * that is the case if delta==0.
+ */
+static int test_and_set_seq_space(struct hanic_stream_item *stream, int delta)
+{
+	int offset = -delta;
+	int ptr = (stream->seq_space_ptr + offset) % SEQ_HISTORY_LENGTH;
+
+	if (stream->seq_space[ptr])
+		return 1;
+
+	stream->seq_space[ptr] = 1;
+	return 0;
+}
+
+/* Shift the sequence space array a positive amount. This is done
+ * in response to a new packet arriving from the future. This shift
+ * moves "now" to the future defined by the arriving packet.
+ *
+ * A positive delta decrements the seq_space_ptr, since per the
+ * test_and_set_seq_space, the "past" is a positive index.
+ *
+ * NOTE: Entering this function, 0 < delta < SEQ_HISTORY_LENGTH
+ */
+static void shift_seq_space(struct hanic_stream_item *stream, int delta)
+{
+	int idx;
+	int new_ptr = stream->seq_space_ptr - delta;
+
+	if (new_ptr < 0)
+		new_ptr += SEQ_HISTORY_LENGTH;
+
+	/* Set time to "now", an note that the "now" packet has
+	 * arrived at least once.
+	 */
+	stream->seq_space_ptr = new_ptr;
+	stream->seq_space[new_ptr] = 1;
+
+	/* Clear cells in the past that we might have skipped. */
+	for (idx = 1; idx < delta; idx += 1) {
+		int ptr = (new_ptr + idx) % SEQ_HISTORY_LENGTH;
+
+		stream->seq_space[ptr] = 0;
+	}
+}
+
+/* Rewrite the MAC header to elide the R-TAG protocol header. This is
+ * done so that the kernel can re-interpret the packet using the
+ * encapsulated protocols.
+ */
+static struct sk_buff *rewrite_rtag_header(struct sk_buff *skb)
+{
+	int mac_len;
+
+	/* Pull the R-TAG data, and the next sequence number,
+	 * out of the payload.
+	 */
+	skb_pull_rcsum(skb, RTAG_HLEN);
+
+	/* Rewrite the R-TAG out of the MAC header. */
+	mac_len = skb->data - skb_mac_header(skb);
+
+	/* Move all the mac header, except the protocol number,
+	 * forward. In the trivial case, mac_len==20, and the
+	 * result works like this:
+	 *
+	 * a0:a1:a2:a3:a4:a5 b0:b1:b2:b3:b4:b5 f1:c1:ss:ss:ss:ss 08:00 ...
+	 * .. .. .. .. .. .. a0:a1:a2:a3:a4:a5 b0:b1:b2:b3:b4:b5 08:00 ...
+	 *
+	 * Notice how the memmove tosses the first ethertype,
+	 * which is the RTAG protocol number, but preserves the
+	 * nested ethertype, which is the payload packet
+	 * protocol (i.e. IP).
+	 */
+	memmove(skb_mac_header(skb) + RTAG_HLEN, skb_mac_header(skb),
+		mac_len - RTAG_HLEN - 2);
+	skb->mac_header += 6;
+
+	/* Clean up the skb */
+	skb_reset_network_header(skb);
+	skb_reset_transport_header(skb);
+	skb_reset_mac_len(skb);
+
+	return skb;
+}
+
+/* The 802.1cb R-TAG protocol uses duplication to guarantee
+ * delivery, so it is the job of this protocol handler to de-duplicate
+ * the stream of packets. To aid in that task, the remote includes in
+ * the duplicated packets an R-TAG that contains a 16bit sequence
+ * number. Two packets that are duplicates will arrive in a similar
+ * time with the same sequence number.
+ *
+ * To detect duplicates, we keep track of which sequence numbers were
+ * recently (within SEQ_HISTORY_LENGTH packets) received. If a sequence
+ * number has been recently received, it is a duplicate and can be
+ * quietly dropped. If the packet has not been received yet, and is
+ * greater then any other packet sequence numbers, then shift the
+ * sliding window of sequence numbers, and this packet becomes the new
+ * definition of "recent".
+ *
+ * If an arriving packet has a sequence number far outside the
+ * expected range, then this is some sort of rogue packet. Drop it.
+ *
+ * This function will return RX_HANDLER_ANOTHER if the packet is to be
+ * accepted (likely including so packet edits) or RX_HANDLER_CONSUMED
+ * if the packet had been dropped.
+ *
+ * If the packet arrives with an 802.1Q VLAN tag, then the caller will
+ * have already stripped it out and interpreted it.
+ */
+static rx_handler_result_t run_rtag_protocol(struct net_device *dev,
+					     struct net_device *port,
+					     int port_idx,
+					     struct sk_buff **pskb)
+{
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct sk_buff *skb = *pskb;
+	uint8_t *rtag;
+	uint16_t vlan_id = 0;
+	uint16_t rtag_seq;
+	uint16_t next_proto;
+	const uint8_t *saddr;
+	struct hanic_stream_item *stream;
+	unsigned long flags;
+
+	/* Grab the source mac address from the ethernet header.
+	 * Or if this is a multicast packet to me, then use that as
+	 * the stream identifier address, since the source address
+	 * must not be conflated with unicast addresses from the same
+	 * source.
+	 */
+	if (unlikely(is_multicast_ether_addr(skb_mac_header(skb))))
+		saddr = skb_mac_header(skb);
+	else
+		saddr = skb_mac_header(skb) + ETH_ALEN;
+
+	/* If this packet is coming in through a VLAN, get the VLAN id
+	 * for matching streams. Note that the kernel network
+	 * infrastructure has already elided (and interpreted) the
+	 * 802.1Q tag, so these functions are the only lasting clue
+	 * that this packet was on a VLAN.
+	 */
+	if (skb_vlan_tag_present(skb))
+		vlan_id = skb_vlan_tag_get_id(skb);
+
+	spin_lock_irqsave(&xsp->stream_table_lock, flags);
+	stream = hanic_map_stream(xsp, saddr, vlan_id);
+	if (unlikely(stream == 0)) {
+		spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+		net_warn_ratelimited(
+			"%s: Failed to locate or allocate stream map.\n",
+			dev->name);
+		return RX_HANDLER_ANOTHER;
+	}
+
+	/* Fault Injection:
+	 * If this packet is to be dropped as some sort of injected
+	 * fault, then drop it without a message. We do this fault
+	 * injection here so that it only effects R-TAG'ed packets.
+	 */
+	if (unlikely(test_drop_packet_in(xsp, port_idx))) {
+		spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+		consume_skb(skb);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	/* The R-TAG protocol header (other then the ethernet
+	 * tag itself) is 4 bytes (octets) that together make up
+	 * the frame replication sequence number:
+	 *
+	 * f1:c1:rr:rr:ss:ss:pp:pp
+	 *
+	 * f1:c1 is the Ethertype
+	 * rr:rr is 16 bits (2 octets) of reserved
+	 * ss:ss is 16 bits (2 octets) of sequence number.
+	 * pp:pp is the next protocol number.
+	 *
+	 * The lower level processing stripped off the f1:c1
+	 * and put that into the protocol member of the skb, so
+	 * the skb->data points to the first octet of rr:rr.
+	 */
+	rtag = skb->data;
+	rtag_seq = (rtag[2] << 8);
+	rtag_seq |= rtag[3];
+	next_proto = (rtag[4] << 8);
+	next_proto |= rtag[5];
+
+	if (stream->take_any) {
+		/* take_any is set. The stream has been reset. */
+		stream->stats.rx_passed[port_idx] += 1;
+		stream->take_any = 0;
+		stream->recov_seq_num = rtag_seq;
+		stream->seq_space[stream->seq_space_ptr] = 1;
+
+	} else {
+		uint16_t tmp = rtag_seq - stream->recov_seq_num;
+		int delta;
+		int sign = (tmp & 0x8000) ? -1 : 1;
+
+		tmp *= sign;
+		delta = sign * tmp;
+
+		if ((delta >= SEQ_HISTORY_LENGTH) ||
+		    (delta <= -SEQ_HISTORY_LENGTH)) {
+			stream->stats.rx_rogue_packets += 1;
+			spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+			consume_skb(skb);
+			net_info_ratelimited("%s[%s]: Rogue packet from "
+					     "%02x:%02x:%02x:%02x:%02x:%02x. "
+					     "(delta=%d)\n",
+					     dev->name, port->name, saddr[0],
+					     saddr[1], saddr[2], saddr[3],
+					     saddr[4], saddr[5], delta);
+			return RX_HANDLER_CONSUMED;
+
+		} else if (delta == 0) {
+			/* Match duplicate. The best kind. */
+			stream->stats.rx_dropped_duplicates[port_idx] += 1;
+			spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+			consume_skb(skb);
+			return RX_HANDLER_CONSUMED;
+
+		} else if ((delta < 0) &&
+			   test_and_set_seq_space(stream, delta)) {
+			/* Duplicate of a packet from the recent past. */
+			stream->stats.rx_dropped_duplicates[port_idx] += 1;
+			spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+			consume_skb(skb);
+			return RX_HANDLER_CONSUMED;
+
+		} else if (delta > 0) {
+			/* Future packet. */
+			stream->stats.rx_passed[port_idx] += 1;
+			stream->recov_seq_num = rtag_seq;
+			shift_seq_space(stream, delta);
+			if (delta > 1) {
+				if (delta > HANIC_OOO_MAX_STRIDE)
+					delta = HANIC_OOO_MAX_STRIDE;
+				delta -= 1;
+				stream->stats.rx_out_of_order[delta] += 1;
+			}
+
+		} else {
+			/* Non-duplicate from recent past. This
+			 * can happen if packets are out of order.
+			 */
+			stream->stats.rx_passed[port_idx] += 1;
+		}
+	}
+
+	/* A packet has been accepted by the protocol,
+	 * so reset the reset timer.
+	 */
+	stream->in_reset_timer = xsp->ticks_per_reset;
+
+	spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+
+	skb->protocol = cpu_to_be16(next_proto);
+	*pskb = rewrite_rtag_header(skb);
+	return RX_HANDLER_ANOTHER;
+}
+
+/* The packet handler is attached to the enlisted device, with the
+ * rx_handler_data set to the hanic_port_info within the master
+ * device. The handler looks at the packet and decides how to process
+ * it. The hanic_port_info contains a back-pointer to the master
+ * device, and this information can be used to calculate the port_idx
+ * of the port within the hanic port set.
+ *
+ * When this handler is called, the ethernet header (dst, src, proto)
+ * are already extracted and interpreted. However, if we detect the
+ * R-TAG protocol, then we will rewrite the header and reinterpret it
+ * so that the payload protocol  is invoked.
+ */
+
+static rx_handler_result_t hanic_packet_handler(struct sk_buff **pskb)
+{
+	struct sk_buff *skb = *pskb;
+	struct hanic_port_info *ports = skb->dev->rx_handler_data;
+	struct net_device *dev = ports->ha_dev;
+	struct net_device *port = skb->dev;
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int port_idx = ports - xsp->ports;
+	uint16_t proto = be16_to_cpu(skb->protocol);
+	rx_handler_result_t res = RX_HANDLER_ANOTHER;
+
+	/* 802.1cb R-TAG is present. Extract it from the packet and
+	 * remake the packet without it. This includes setting the
+	 * payload protocol number.
+	 */
+	if (proto == ETH_P_RTAG) {
+		res = run_rtag_protocol(dev, port, port_idx, pskb);
+		skb = *pskb;
+	}
+
+	/* If one of the protocol handlers consumed the packet, then
+	 * escape now.
+	 */
+	if (res != RX_HANDLER_ANOTHER)
+		return res;
+
+	/* Steal the packet from the enlisted port and claim it for the master. */
+	skb->dev = dev;
+	xsp->rx_packets += 1;
+	xsp->rx_bytes += skb->len;
+	return RX_HANDLER_ANOTHER;
+}
+
+void hanic_add_protocol_to_port(struct hanic_priv *xsp, int port_idx)
+{
+	int rc;
+	struct net_device *port = xsp->ports[port_idx].sl_dev;
+
+	rc = netdev_rx_handler_register(port, hanic_packet_handler,
+					xsp->ports + port_idx);
+	if (rc < 0) {
+		netdev_info(
+			xsp->dev,
+			"%s: Error (errno=%d) registering packet handler for %s.\n",
+			__func__, -rc, port->name);
+	}
+}
+
+void hanic_remove_protocol_from_port(struct hanic_priv *xsp, int port_idx)
+{
+	struct net_device *port = xsp->ports[port_idx].sl_dev;
+	netdev_rx_handler_unregister(port);
+}
diff --git a/drivers/net/hanic/hanic_streams.c b/drivers/net/hanic/hanic_streams.c
new file mode 100644
index 000000000000..7b27991968d9
--- /dev/null
+++ b/drivers/net/hanic/hanic_streams.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+/* Each hanic device keeps a map of streams in order to hold protocol
+ * state. Each stream is identified by the remote MAC address and the
+ * vlan id.
+ */
+#include "hanic_priv.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+
+static void hanic_stream_reset_statistics(struct hanic_stream_item *stream)
+{
+	int idx;
+
+	stream->stats.tx_send = 0;
+	stream->stats.rx_rogue_packets = 0;
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		stream->stats.rx_passed[idx] = 0;
+		stream->stats.rx_dropped_duplicates[idx] = 0;
+	}
+	stream->stats.rx_seq_recovery_resets = 0;
+	for (idx = 0; idx < HANIC_OOO_MAX_STRIDE; idx += 1)
+		stream->stats.rx_out_of_order[idx] = 0;
+}
+
+static void hanic_stream_in_reset(struct hanic_stream_item *stream)
+{
+	int idx;
+
+	stream->take_any = 1;
+	stream->recov_seq_num = 0;
+	stream->in_reset_timer = 0;
+	stream->seq_space_ptr = 0;
+	for (idx = 0; idx < SEQ_HISTORY_LENGTH; idx += 1)
+		stream->seq_space[idx] = 0;
+}
+
+static struct kmem_cache *hanic_stream_pool;
+
+static inline struct hanic_stream_item *alloc_stream_item(gfp_t flags)
+{
+	return kmem_cache_alloc(hanic_stream_pool, flags);
+}
+
+static inline void free_stream_item(struct hanic_stream_item *item)
+{
+	kmem_cache_free(hanic_stream_pool, item);
+}
+
+/* Look for the stream in the stream table. If it is not there, create
+ * it and initialize it.
+ *
+ * NOTE: The caller is responsible for holding the stream_table_lock.
+ */
+struct hanic_stream_item *hanic_map_stream(struct hanic_priv *xsp,
+					   const void *mac, uint16_t vlan)
+{
+	struct hanic_stream_item *stream = 0;
+	struct hanic_stream_item *cur;
+	uint64_t stream_key = stream_item_key(mac, vlan);
+
+	hash_for_each_possible (xsp->stream_table, cur, stream_table,
+				stream_key) {
+		if (stream_key == stream_item_key(cur->mac, cur->vlan)) {
+			stream = cur;
+			break;
+		}
+	}
+
+	if (stream == 0) {
+		stream = alloc_stream_item(GFP_ATOMIC);
+		if (stream == 0)
+			return 0;
+
+		memcpy(stream->mac, mac, ETH_ALEN);
+		stream->vlan = vlan;
+		stream->out_seq_num = 0;
+		hanic_stream_in_reset(stream);
+		hanic_stream_reset_statistics(stream);
+		/* build up attribute. */
+		sysfs_attr_init(&stream->attr);
+		snprintf(stream->attr_name, sizeof(stream->attr_name),
+			 "stream-%02x:%02x:%02x:%02x:%02x:%02x-%04x",
+			 stream->mac[0], stream->mac[1], stream->mac[2],
+			 stream->mac[3], stream->mac[4], stream->mac[5], vlan);
+		stream->attr.attr.name = stream->attr_name;
+		stream->attr.attr.mode = 0444;
+		stream->attr.show = hanic_stream_show;
+		stream->attr.store = 0;
+		/* Add to tables. */
+		hash_add(xsp->stream_table, &stream->stream_table, stream_key);
+		hanic_sysfs_add_attr(xsp, &stream->attr.attr);
+		net_info_ratelimited("%s: Created new %s.\n", xsp->dev->name,
+				     stream->attr_name);
+	}
+
+	return stream;
+}
+
+void hanic_map_stream_clear(struct hanic_priv *xsp)
+{
+	int idx;
+	struct hanic_stream_item *cur;
+	struct hlist_node *tmp;
+
+	/* Clean up the stream table */
+	hash_for_each_safe (xsp->stream_table, idx, tmp, cur, stream_table) {
+		hanic_sysfs_rem_attr(xsp, &cur->attr.attr);
+		hash_del(&cur->stream_table);
+		free_stream_item(cur);
+	}
+}
+
+/* The input streams have a protocol timer. If no packets are received
+ * after some number of protocol ticks, then reset the state
+ * machine. This is done to recover from broken protocol
+ * synchronizations.
+ *
+ * NOTE: The caller already holds the hanic_devs_mutex lock, and the
+ *       xsp->stream_table_lock.
+ */
+void hanic_stream_protocol_tick(struct hanic_priv *xsp,
+				struct hanic_stream_item *stream)
+{
+	if (stream->in_reset_timer == 0)
+		return;
+
+	if (stream->in_reset_timer > 1) {
+		stream->in_reset_timer -= 1;
+		return;
+	}
+
+	hanic_stream_in_reset(stream);
+	stream->stats.rx_seq_recovery_resets += 1;
+}
+
+int hanic_streams_init(void)
+{
+	hanic_stream_pool = kmem_cache_create(
+		"hanic_stream_item", sizeof(struct hanic_stream_item), 0, 0, 0);
+	if (hanic_stream_pool == 0) {
+		pr_info("hanic_init_module: Unable to allocate hanic stream pool\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void hanic_streams_remove(void)
+{
+	kmem_cache_destroy(hanic_stream_pool);
+}
diff --git a/drivers/net/hanic/hanic_sysfs.c b/drivers/net/hanic/hanic_sysfs.c
new file mode 100644
index 000000000000..39ee229cfab6
--- /dev/null
+++ b/drivers/net/hanic/hanic_sysfs.c
@@ -0,0 +1,672 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Cruise LLC
+ * Author: Steve Williams <steve.williams@getcruise.com>
+ */
+
+#include "hanic_priv.h"
+
+#include <linux/netdevice.h>
+#include <linux/sysfs.h>
+
+#define HANIC_DEV_ATTRIBUTES 1023
+
+/* Show a list of all the interfaces that exist. */
+static ssize_t hanic_show_interfaces(struct class *cls,
+				     struct class_attribute *attr, char *buf)
+{
+	return hanic_dev_show_interfaces(buf, PAGE_SIZE);
+}
+
+#define str_of(x) #x
+#define str_of_value(x) str_of(x)
+
+/* Add/delete interfaces as requested by the user. The command format
+ * is super simple:
+ *
+ *   +<name-pattern> to create an interface
+ *   -<name> to delete an interface
+ *
+ * The <name-pattern> make include an %d which will receive an
+ * interface number or some such, but it doesn't have to.
+ */
+static ssize_t hanic_store_interfaces(struct class *cls,
+				      struct class_attribute *attr,
+				      const char *buffer, size_t count)
+{
+	int rc;
+	char command_code;
+	char ifname[IFNAMSIZ + 1];
+
+	rc = sscanf(buffer, "%c%" str_of_value(IFNAMSIZ) "s", &command_code,
+		    ifname);
+	if (rc < 2) {
+		pr_info("hanic: Command format: [+-]<ifname>\n");
+		return -ENODEV;
+	}
+
+	switch (command_code) {
+	case '+':
+		rc = hanic_dev_alloc(ifname);
+		if (rc < 0)
+			return rc;
+		break;
+
+	case '-':
+		rc = hanic_dev_free_byname(ifname);
+		break;
+
+	default:
+		pr_info("hanic: command_code=%c, ifname=%s\n", command_code,
+			ifname);
+		break;
+	}
+
+	return count;
+}
+
+/* class attribute for hanic_interfaces file.  This ends up in /sys/class/net */
+static const struct class_attribute class_attr_hanic_interfaces = {
+	.attr = {
+		.name = "hanic_interfaces",
+		.mode = 0644,
+	},
+	.show = hanic_show_interfaces,
+	.store = hanic_store_interfaces,
+};
+
+int hanic_init_sysfs(struct hanic_netns *xns)
+{
+	int rc;
+
+	xns->class_attr_hanic_interfaces = class_attr_hanic_interfaces;
+	sysfs_attr_init(&xns->class_attr_sandlan_interfaces.attr);
+
+	rc = netdev_class_create_file_ns(&xns->class_attr_hanic_interfaces,
+					 xns->net);
+	if (rc < 0)
+		pr_info("hanic_create_sysfs: Unable to create class file.\n");
+
+	return rc;
+}
+
+void hanic_remove_sysfs(struct hanic_netns *xns)
+{
+	netdev_class_remove_file_ns(&xns->class_attr_hanic_interfaces,
+				    xns->net);
+}
+
+/* Show the ports in the current bond. */
+static ssize_t ports_show(struct device *d, struct device_attribute *attr,
+			  char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int res = 0;
+	int idx;
+
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+
+		if (port == 0)
+			continue;
+		res += sprintf(buf + res, "%d: %s\n", idx, port->name);
+	}
+	return res;
+}
+static DEVICE_ATTR_RO(ports);
+
+/* Show the JIFFIES_PER_HANIC_TICK. This value can be converted to
+ * TicksPerSecond with the formula HZ/JIFFIES_PER_HANIC_TICK.
+ *
+ * The TicksPerSecond value is as defined in:
+ *   IEEE Std 802.1CB-2017
+ *   7.4.3.2.5 TicksPerSecond
+ *
+ * But the JIFFIES_PER_HANIC_TICK is what's actually used internally.
+ */
+static ssize_t jiffies_per_hanic_tick_show(struct device *d,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	ssize_t res = 0;
+
+	res += snprintf(buf, PAGE_SIZE, "%u\n", JIFFIES_PER_HANIC_TICK);
+	return res;
+}
+
+static DEVICE_ATTR_RO(jiffies_per_hanic_tick);
+
+static ssize_t ticks_per_reset_show(struct device *d,
+				    struct device_attribute *attr, char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	ssize_t res = 0;
+
+	res += snprintf(buf, PAGE_SIZE, "%u\n", xsp->ticks_per_reset);
+	return res;
+}
+
+static ssize_t ticks_per_reset_store(struct device *d,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int rc;
+	unsigned int val;
+
+	rc = sscanf(buf, " %u", &val);
+	if (rc < 1)
+		return count;
+
+	if (val < 2)
+		val = 2;
+	xsp->ticks_per_reset = val;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(ticks_per_reset);
+
+static ssize_t broadcast_port_show(struct device *d,
+				   struct device_attribute *attr, char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int res = 0;
+
+	if (hanic_disp_is_flood(xsp->broadcast_disp))
+		res += sprintf(buf + res, "flood\n");
+	else
+		res += sprintf(buf + res, "%d\n", xsp->broadcast_disp);
+
+	return res;
+}
+
+/* Convert a string to a disposition number. This takes the various
+ * forms that a user might enter.
+ *
+ * Note that "prime" and "second" are legacy values there for
+ * compatibility.
+ */
+static hanic_packet_disposition_t string_to_disposition(const char *text)
+{
+	int rc;
+	int res;
+
+	if (strcmp(text, "prime") == 0)
+		return 0;
+
+	if (strcmp(text, "second") == 0)
+		return 1;
+
+	if (strcmp(text, "flood") == 0)
+		return -1;
+
+	rc = kstrtoint(text, 10, &res);
+	if (rc < 0)
+		return 0;
+	else if (res >= HANIC_PORT_MAX)
+		return 0;
+	else
+		return res;
+}
+
+static ssize_t broadcast_port_store(struct device *d,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int rc;
+#define CMD_SIZE 63
+	char command[CMD_SIZE + 1];
+
+	rc = sscanf(buf, " %" str_of_value(CMD_SIZE) "s", command);
+	if (rc < 1)
+		return count;
+
+	xsp->broadcast_disp = string_to_disposition(command);
+	return count;
+}
+
+static DEVICE_ATTR_RW(broadcast_port);
+
+static ssize_t arp_rtag_show(struct device *d, struct device_attribute *attr,
+			     char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int res = 0;
+
+	res += sprintf(buf, "%d\n", xsp->arp_rtag);
+
+	return res;
+}
+
+static ssize_t arp_rtag_store(struct device *d, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int rc;
+	bool val;
+
+	rc = kstrtobool(buf, &val);
+	if (rc)
+		return count;
+
+	xsp->arp_rtag = val;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(arp_rtag);
+
+static ssize_t multicast_rtag_show(struct device *d,
+				   struct device_attribute *attr, char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int res = 0;
+
+	res += sprintf(buf, "%d\n", xsp->multicast_rtag);
+
+	return res;
+}
+
+static ssize_t multicast_rtag_store(struct device *d,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	int rc;
+	bool val;
+
+	rc = kstrtobool(buf, &val);
+	if (rc)
+		return count;
+
+	xsp->multicast_rtag = val;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(multicast_rtag);
+
+static ssize_t filter_vlan_show(struct device *d, struct device_attribute *attr,
+				char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_filter_item *cur;
+	int res = 0;
+	int rem = PAGE_SIZE;
+	int bkt;
+
+	hash_for_each (xsp->filter_by_vlan_table, bkt, cur, filter_table) {
+		int rc;
+
+		if (hanic_disp_is_flood(cur->disposition)) {
+			rc = snprintf(buf, rem, "%04x: flood\n", cur->vlan);
+		} else {
+			rc = snprintf(buf, rem, "%04x: %d\n", cur->vlan,
+				      cur->disposition);
+		}
+		buf += rc;
+		res += rc;
+		rem -= rc;
+		if (rem <= 32)
+			break;
+	}
+
+	return res;
+}
+
+static ssize_t filter_vmac_show(struct device *d, struct device_attribute *attr,
+				char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_filter_item *cur;
+	int res = 0;
+	int rem = PAGE_SIZE;
+	int bkt;
+
+	hash_for_each (xsp->filter_by_vmac_table, bkt, cur, filter_table) {
+		int rc = snprintf(
+			buf, rem,
+			"%02x:%02x:%02x:%02x:%02x:%02x-%04x: ", cur->mac[0],
+			cur->mac[1], cur->mac[2], cur->mac[3], cur->mac[4],
+			cur->mac[5], cur->vlan);
+
+		buf += rc;
+		res += rc;
+		rem -= rc;
+
+		if (hanic_disp_is_flood(cur->disposition))
+			rc = snprintf(buf, rem, "flood\n");
+		else
+			rc = snprintf(buf, rem, "%d\n", cur->disposition);
+
+		buf += rc;
+		res += rc;
+		rem -= rc;
+		if (rem <= 40)
+			break;
+	}
+
+	return res;
+}
+
+static ssize_t filter_vlan_store(struct device *d,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_filter_item *filter;
+	int rc;
+	int vlan;
+#define CMD_SIZE 63
+	char command[CMD_SIZE + 1];
+
+	rc = sscanf(buf, " %x: %" str_of_value(CMD_SIZE) "s", &vlan, command);
+	if (rc < 2)
+		return count;
+
+	if (strcmp(command, "none") == 0) {
+		hanic_clr_filter_vlan(xsp, vlan);
+		return count;
+	}
+
+	filter = hanic_set_filter_vlan(xsp, vlan);
+	filter->disposition = string_to_disposition(command);
+
+	return count;
+}
+
+static ssize_t filter_vmac_store(struct device *d,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_filter_item *filter;
+	int rc;
+	int vlan;
+	int imac[6];
+	uint8_t mac[6];
+#define CMD_SIZE 63
+	char command[CMD_SIZE + 1];
+
+	rc = sscanf(buf, " %x:%x:%x:%x:%x:%x-%x: %" str_of_value(CMD_SIZE) "s",
+		    imac + 0, imac + 1, imac + 2, imac + 3, imac + 4, imac + 5,
+		    &vlan, command);
+	if (rc < 8)
+		return count;
+
+	for (rc = 0; rc < 6; rc += 1)
+		mac[rc] = imac[rc];
+
+	if (strcmp(command, "none") == 0) {
+		hanic_clr_filter_vmac(xsp, mac, vlan);
+		return count;
+	}
+
+	filter = hanic_set_filter_vmac(xsp, mac, vlan);
+	filter->disposition = string_to_disposition(command);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(filter_vlan);
+static DEVICE_ATTR_RW(filter_vmac);
+
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+static ssize_t test_drop_packet_in_show(struct device *d,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	ssize_t res = 0;
+	int idx;
+
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+		int val;
+
+		if (port == NULL)
+			continue;
+
+		val = atomic_read(&xsp->ports[idx].test_drop_packet_in);
+		if (val > 0)
+			val -= 1;
+
+		res += sprintf(buf + res, "%d %d\n", idx, val);
+	}
+
+	return res;
+}
+
+static ssize_t test_drop_packet_in_store(struct device *d,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	unsigned int val1;
+	int val2;
+	int rc;
+
+	rc = sscanf(buf, "%u %d", &val1, &val2);
+	if (rc != 2)
+		return count;
+
+	if (val1 >= HANIC_PORT_MAX)
+		return count;
+
+	/* NOTE: Since this value is tested with an
+	 * atomic_dec_if_positive function, the tester will always see
+	 * one less then this value, so increment it to be sure we get
+	 * the correct count of drops. Also account for that in the
+	 * _show method.
+	 */
+	if (val2 <= 0)
+		val2 = 0;
+	else
+		val2 += 1;
+
+	if (xsp->ports[val1].sl_dev == NULL)
+		return count;
+
+	atomic_set(&xsp->ports[val1].test_drop_packet_in, val2);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(test_drop_packet_in);
+#endif
+
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+static ssize_t test_drop_packet_out_show(struct device *d,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	ssize_t res = 0;
+	int idx;
+	int val;
+
+	val = atomic_read(&xsp->test_drop_flood_out);
+	if (val > 0)
+		val -= 1;
+
+	res += scnprintf(buf + res, PAGE_SIZE - res, "* %d\n", val);
+
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		struct net_device *port = xsp->ports[idx].sl_dev;
+
+		if (port == NULL)
+			continue;
+
+		val = atomic_read(&xsp->ports[idx].test_drop_packet_out);
+		if (val > 0)
+			val -= 1;
+
+		res += scnprintf(buf + res, PAGE_SIZE - res, "%d %d\n", idx,
+				 val);
+	}
+
+	return res;
+}
+
+/* The atomic_dec_if_positive returns true fewer times than the actual
+ * value of the atomic variable, so correct for that fence-post error
+ * by calculating an appropriate set value.
+ */
+static int count_to_use_for_atomic_value(int val)
+{
+	if (val <= 0)
+		return 0;
+	else
+		return val + 1;
+}
+
+static ssize_t test_drop_packet_out_store(struct device *d,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	unsigned int val1;
+	int val2;
+	int rc;
+
+	rc = sscanf(buf, "* %u", &val2);
+	if (rc == 1) {
+		val2 = count_to_use_for_atomic_value(val2);
+		atomic_set(&xsp->test_drop_flood_out, val2);
+		return count;
+	}
+
+	rc = sscanf(buf, "%u %u", &val1, &val2);
+	if (rc != 2)
+		return count;
+
+	if (val1 >= HANIC_PORT_MAX)
+		return count;
+
+	if (xsp->ports[val1].sl_dev == NULL)
+		return count;
+
+	val2 = count_to_use_for_atomic_value(val2);
+	atomic_set(&xsp->ports[val1].test_drop_packet_out, val2);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(test_drop_packet_out);
+#endif
+
+static struct attribute *per_hanic_attrs[HANIC_DEV_ATTRIBUTES + 1] = {
+	&dev_attr_ports.attr,
+	&dev_attr_broadcast_port.attr,
+	&dev_attr_arp_rtag.attr,
+	&dev_attr_multicast_rtag.attr,
+	&dev_attr_jiffies_per_hanic_tick.attr,
+	&dev_attr_ticks_per_reset.attr,
+	&dev_attr_filter_vlan.attr,
+	&dev_attr_filter_vmac.attr,
+#if defined(CONFIG_HANIC_FAULT_INJECTION)
+	&dev_attr_test_drop_packet_in.attr,
+	&dev_attr_test_drop_packet_out.attr,
+#endif
+	NULL
+};
+
+static const struct attribute_group hanic_group = {
+	.name = "hanic",
+	.attrs = per_hanic_attrs,
+};
+
+void hanic_device_sysfs(struct hanic_priv *xsp)
+{
+	xsp->dev->sysfs_groups[0] = &hanic_group;
+}
+
+/* The functions below allow the rest of the driver to add or remove
+ * attribute files to the group for the device. These are used to
+ * create attribute nodes for streams, or other dynamic aspects of
+ * the device operation.
+ */
+int hanic_sysfs_add_attr(struct hanic_priv *xsp, struct attribute *attr)
+{
+	return sysfs_add_file_to_group(&xsp->dev->dev.kobj, attr,
+				       hanic_group.name);
+}
+
+void hanic_sysfs_rem_attr(struct hanic_priv *xsp, struct attribute *attr)
+{
+	sysfs_remove_file_from_group(&xsp->dev->dev.kobj, attr,
+				     hanic_group.name);
+}
+
+/* Show statistics for the stream. Specifically, get the
+ * hanic_stream_item that contains this attribute.
+ *
+ * Note that we want to hold the stream_table_mutex while extracting
+ * the statistics because they may be updated asynchronously. This
+ * also prevents the stream being deleted while getting the stats.
+ */
+ssize_t hanic_stream_show(struct device *d, struct device_attribute *attr,
+			  char *buf)
+{
+	struct net_device *dev = to_net_dev(d);
+	struct hanic_priv *xsp = netdev_priv(dev);
+	struct hanic_stream_item *stream;
+	struct hanic_stream_item_stats stats;
+	int res = 0;
+	int rc, idx;
+	unsigned long flags;
+	const char *tmp;
+
+	/* Get the statistics out of the stream. */
+	spin_lock_irqsave(&xsp->stream_table_lock, flags);
+	stream = container_of(attr, struct hanic_stream_item, attr);
+	stats = stream->stats;
+	spin_unlock_irqrestore(&xsp->stream_table_lock, flags);
+
+	rc = sprintf(buf, "tx_send=%lu rx_rogue=%lu", stats.tx_send,
+		     stats.rx_rogue_packets);
+	res += rc;
+	buf += rc;
+	for (idx = 0; idx < HANIC_PORT_MAX; idx += 1) {
+		rc = sprintf(buf, " rx_cb_pass[%d]=%lu rx_cb_dup[%d]=%lu", idx,
+			     stats.rx_passed[idx], idx,
+			     stats.rx_dropped_duplicates[idx]);
+		res += rc;
+		buf += rc;
+	}
+	rc = sprintf(buf, " rx_seq_recovery_resets=%lu",
+		     stats.rx_seq_recovery_resets);
+	res += rc;
+	buf += rc;
+
+	tmp = " rx_ooo=";
+	for (idx = 0; idx < HANIC_OOO_MAX_STRIDE; idx += 1) {
+		rc = sprintf(buf, "%s%lu", tmp, stats.rx_out_of_order[idx]);
+		tmp = ",";
+		res += rc;
+		buf += rc;
+	}
+
+	rc = sprintf(buf, "\n");
+	res += rc;
+	return res;
+}
