Subject: Re: HW-assisted VLAN handling
To: Quentin Garnier <cube@cubidou.net>
From: Jaromir Dolecek <jdolecek@NetBSD.org>
List: tech-kern
Date: 01/30/2005 17:09:45
Quentin Garnier wrote:
> Here, Jaromir has a device that can select the vlan ids it will receive,
> pretty much like a multicast filter.

Exactly.
 
> I guess an API should be added to have vlan(4) notify the hardware
> layer, again just like multicast filter.  AFAICT, no such exists at the
> moment.

What about following?

The patch:
* adds list of vlans ethernet interface is configured for to ethercom
* adds a ETHERCAP_VLAN_HWFILTER capability, if set the driver is
  notified via ioctl when a vlan is attached or detached

This works fine for my needs.

Jaromir

Index: if_ether.h
===================================================================
RCS file: /cvsroot/src/sys/net/if_ether.h,v
retrieving revision 1.36
diff -u -p -r1.36 if_ether.h
--- if_ether.h	8 Jan 2005 03:18:18 -0000	1.36
+++ if_ether.h	30 Jan 2005 15:47:12 -0000
@@ -92,7 +92,7 @@ struct	ether_header {
 	 (((etype) == ETHERTYPE_VLAN) ? ETHER_VLAN_ENCAP_LEN : 0))
 
 /*
- * Ethernet CRC32 polynomials (big- and little-endian verions).
+ * Ethernet CRC32 polynomials (big- and little-endian versions).
  */
 #define	ETHER_CRC_POLY_LE	0xedb88320
 #define	ETHER_CRC_POLY_BE	0x04c11db6
@@ -161,11 +161,13 @@ struct	ethercom {
 	struct	mowner ec_rx_mowner;		/* mbufs received */
 	struct	mowner ec_tx_mowner;		/* mbufs transmitted */
 #endif
+	LIST_HEAD(, ether_vlan) ec_vlanaddrs;	/* list of ether VLANs */
 };
 
 #define	ETHERCAP_VLAN_MTU	0x00000001	/* VLAN-compatible MTU */
 #define	ETHERCAP_VLAN_HWTAGGING	0x00000002	/* hardware VLAN tag support */
 #define	ETHERCAP_JUMBO_MTU	0x00000004	/* 9000 byte MTU supported */
+#define	ETHERCAP_VLAN_HWFILTER	0x00000008	/* hardware uses VLAN filter */
 
 #ifdef	_KERNEL
 extern const uint8_t etherbroadcastaddr[ETHER_ADDR_LEN];
@@ -242,7 +244,19 @@ struct ether_multistep {
 	ETHER_NEXT_MULTI((step), (enm)); \
 }
 
+/*
+ * Ethernet 802.1Q VLAN structure.
+ */
+struct ether_vlan {
+	u_int16_t		ev_tag;
+	LIST_ENTRY(ether_vlan)	ev_list;
+};
+
 #ifdef _KERNEL
+
+/* Kernel-only ioctls */
+#define	SIOCSETHVLAN		_IOWR('i', 141, uint16_t)
+
 void	ether_ifattach(struct ifnet *, const u_int8_t *);
 void	ether_ifdetach(struct ifnet *);
 
Index: if_ethersubr.c
===================================================================
RCS file: /cvsroot/src/sys/net/if_ethersubr.c,v
retrieving revision 1.118
diff -u -p -r1.118 if_ethersubr.c
--- if_ethersubr.c	8 Jan 2005 03:18:18 -0000	1.118
+++ if_ethersubr.c	30 Jan 2005 15:47:12 -0000
@@ -1520,6 +1520,11 @@ ether_ioctl(struct ifnet *ifp, u_long cm
 		error = ether_delmulti(ifr, ec);
 		break;
 
+	case SIOCSETHVLAN:
+		/* ignored by default */
+		error = 0;
+		break;
+
 	default:
 		error = ENOTTY;
 	}
Index: if_vlan.c
===================================================================
RCS file: /cvsroot/src/sys/net/if_vlan.c,v
retrieving revision 1.42
diff -u -p -r1.42 if_vlan.c
--- if_vlan.c	4 Dec 2004 18:31:43 -0000	1.42
+++ if_vlan.c	30 Jan 2005 15:47:13 -0000
@@ -132,14 +132,14 @@ struct ifvlan {
 	union {
 		struct ethercom ifvu_ec;
 	} ifv_u;
-	struct ifnet *ifv_p;	/* parent interface of this vlan */
+	struct ifnet *ifv_p;		/* parent interface of this vlan */
 	struct ifv_linkmib {
 		const struct vlan_multisw *ifvm_msw;
 		int	ifvm_encaplen;	/* encapsulation length */
 		int	ifvm_mtufudge;	/* MTU fudged by this much */
 		int	ifvm_mintu;	/* min transmission unit */
 		u_int16_t ifvm_proto;	/* encapsulation ethertype */
-		u_int16_t ifvm_tag;	/* tag to apply on packets */
+		struct ether_vlan ifvm_vlan;	/* VLAN context (for parent) */
 	} ifv_mib;
 	LIST_HEAD(__vlan_mchead, vlan_mc_entry) ifv_mc_listhead;
 	LIST_ENTRY(ifvlan) ifv_list;
@@ -156,7 +156,8 @@ struct ifvlan {
 #define	ifv_encaplen	ifv_mib.ifvm_encaplen
 #define	ifv_mtufudge	ifv_mib.ifvm_mtufudge
 #define	ifv_mintu	ifv_mib.ifvm_mintu
-#define	ifv_tag		ifv_mib.ifvm_tag
+#define	ifv_vlan	ifv_mib.ifvm_vlan
+#define	ifv_tag		ifv_vlan.ev_tag
 
 struct vlan_multisw {
 	int	(*vmsw_addmulti)(struct ifvlan *, struct ifreq *);
@@ -176,7 +177,7 @@ const struct vlan_multisw vlan_ether_mul
 
 static int	vlan_clone_create(struct if_clone *, int);
 static int	vlan_clone_destroy(struct ifnet *);
-static int	vlan_config(struct ifvlan *, struct ifnet *);
+static int	vlan_config(struct ifvlan *, struct ifnet *, struct vlanreq *);
 static int	vlan_ioctl(struct ifnet *, u_long, caddr_t);
 static void	vlan_start(struct ifnet *);
 static void	vlan_unconfig(struct ifnet *);
@@ -268,7 +269,7 @@ vlan_clone_destroy(struct ifnet *ifp)
  * Configure a VLAN interface.  Must be called at splnet().
  */
 static int
-vlan_config(struct ifvlan *ifv, struct ifnet *p)
+vlan_config(struct ifvlan *ifv, struct ifnet *p, struct vlanreq *vlr)
 {
 	struct ifnet *ifp = &ifv->ifv_if;
 	int error;
@@ -290,7 +291,7 @@ vlan_config(struct ifvlan *ifv, struct i
 		 * i.e. can Tx/Rx larger than ETHER_MAX_LEN frames,
 		 * enable it.
 		 */
-		if (ec->ec_nvlans++ == 0 &&
+		if (++ec->ec_nvlans == 1 &&
 		    (ec->ec_capabilities & ETHERCAP_VLAN_MTU) != 0) {
 			/*
 			 * Enable Tx/Rx of VLAN-sized frames.
@@ -303,7 +304,7 @@ vlan_config(struct ifvlan *ifv, struct i
 				error = (*p->if_ioctl)(p, SIOCSIFFLAGS,
 				    (caddr_t) &ifr);
 				if (error) {
-					if (ec->ec_nvlans-- == 1)
+					if (--ec->ec_nvlans == 0)
 						ec->ec_capenable &=
 						    ~ETHERCAP_VLAN_MTU;
 					return (error);
@@ -332,6 +333,20 @@ vlan_config(struct ifvlan *ifv, struct i
 			     IFCAP_CSUM_UDPv4|IFCAP_CSUM_TCPv6|
 			     IFCAP_CSUM_UDPv6);
 
+		ifv->ifv_tag = vlr->vlr_tag;
+
+		/* Make our VLAN ID available to parent */
+		LIST_INSERT_HEAD(&((struct ethercom *)p)->ec_vlanaddrs,
+		    &ifv->ifv_vlan, ev_list);
+
+		/* 
+		 * Notify the driver about changed VLAN list.
+		 */
+		if (ec->ec_capabilities & ETHERCAP_VLAN_HWFILTER) {
+			(void) (*p->if_ioctl)(p, SIOCSETHVLAN,
+			    (caddr_t) &vlr->vlr_tag);
+		}
+
 		/*
 		 * We inherit the parent's Ethernet address.
 		 */
@@ -347,8 +362,7 @@ vlan_config(struct ifvlan *ifv, struct i
 	ifv->ifv_p = p;
 	ifv->ifv_if.if_mtu = p->if_mtu - ifv->ifv_mtufudge;
 	ifv->ifv_if.if_flags = p->if_flags &
-	    (IFF_UP | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
-
+	    (IFF_UP | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_RUNNING);
 	/*
 	 * Inherit the if_type from the parent.  This allows us
 	 * to participate in bridges of that type.
@@ -382,7 +396,7 @@ vlan_unconfig(struct ifnet *ifp)
 	    {
 		struct ethercom *ec = (void *) ifv->ifv_p;
 
-		if (ec->ec_nvlans-- == 1) {
+		if (--ec->ec_nvlans == 0) {
 			/*
 			 * Disable Tx/Rx of VLAN-sized frames.
 			 */
@@ -396,6 +410,17 @@ vlan_unconfig(struct ifnet *ifp)
 			}
 		}
 
+		/* Deassociate VLAN from parent */
+		LIST_REMOVE(&ifv->ifv_vlan, ev_list);
+
+		/* 
+		 * Notify the driver about changed VLAN list.
+		 */
+		if (ec->ec_capabilities & ETHERCAP_VLAN_HWFILTER) {
+			(void) (*ifv->ifv_p->if_ioctl)(ifv->ifv_p,
+			    SIOCSETHVLAN, (caddr_t) &ifv->ifv_tag);
+		}
+
 		ether_ifdetach(ifp);
 		vlan_reset_linkname(ifp);
 		break;
@@ -528,10 +553,8 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
 			error = ENOENT;
 			break;
 		}
-		if ((error = vlan_config(ifv, pr)) != 0)
+		if ((error = vlan_config(ifv, pr, &vlr)) != 0)
 			break;
-		ifv->ifv_tag = vlr.vlr_tag;
-		ifp->if_flags |= IFF_RUNNING;
 
 		/* Update promiscuous mode, if necessary. */
 		vlan_set_promisc(ifp);
-- 
Jaromir Dolecek <jdolecek@NetBSD.org>            http://www.NetBSD.cz/
-=- We can walk our road together if our goals are all the same;     -=-
-=- We can run alone and free if we pursue a different aim.          -=-