Netdev List
 help / color / mirror / Atom feed
* Re:
From: Christoph Lameter @ 2010-09-28 15:49 UTC (permalink / raw)
  To: David Stevens
  Cc: Jason Gunthorpe, linux-rdma, netdev, David Miller, Bob Arendt
In-Reply-To: <OF980F8B87.D755A28E-ON882577AB.00779881-882577AB.0077F27A@us.ibm.com>

On Mon, 27 Sep 2010, David Stevens wrote:

>         No. I'm not talking about the force_igmp_tunable here, I'm talking
> about the per-interface robustness and interval settings which come from
> the querier (whatever version you are using).

The igmp subsystem currently does not keep state on the interface layer
about robustness etc. An interval setting is only kept for IGMP v3 and
used only for general query timeouts with igmp V3. The interval is
different one from the one used for the host membership reports.

Looking at the spec I get the impression that these variables seems to be
mainly of interest to router to router communications?




^ permalink raw reply

* Re: [PATCH net-next-2.6 0/5] XFRM,IPv6: Removal of RH2/HAO from IPsec-protected MIPv6 traffic
From: Arnaud Ebalard @ 2010-09-28 15:53 UTC (permalink / raw)
  To: David Miller; +Cc: eric.dumazet, herbert, yoshfuji, netdev
In-Reply-To: <20100927.212541.183056184.davem@davemloft.net>

Hi David,

David Miller <davem@davemloft.net> writes:

> Please resubmit this after you've done some more exhaustive
> build testing:

I spent some time on it today. An updated version follows. 

^ permalink raw reply

* [PATCHv2 net-next-2.6 0/5] XFRM,IPv6: Removal of RH2/HAO from IPsec-protected MIPv6 traffic
From: Arnaud Ebalard @ 2010-09-28 15:53 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev

Hi,

This an updated version of the patches. For reference, introduction of
the feature is here http://thread.gmane.org/gmane.linux.network/172941
Just tell me if you prefer I resend the full intro each time.

Compared to initial version, this v2 is

 - against net-next-2.6
 - fixed and more exhaustively build tested for various kernel
   configuration (w/ and w/o IPv6, IPv4, MIPv6, XFRM*, RO, ...).
   It adds some #ifdef but I did not found good ways to spare those.
 - built and tested on both current linux-2.6 and net-next-2.6 (*)

Comments welcome.

Cheers,

a+

(*): stripped from latest stats patches from Eric (crash at boot) until
     associated fixes get included tomorrow


^ permalink raw reply

* [PATCHv2 net-next-2.6 1/5] XFRM,IPv6: Remove xfrm_spi_hash() dependency on destination address
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>


In the new IPsec architecture [RFC4301], "for an SA used to carry
unicast traffic, the Security Parameters Index (SPI) by itself
suffices to specify an SA".  Section 4.1 of [RFC4301] provides
additional guidance on the topic.

In the old IPsec architecture [RFC2401], a SA "is uniquely identified
by a triple consisting of a Security Parameter Index (SPI), an IP
Destination Address and a security protocol (AH or ESP) identifier".

If an IPsec stack only supports the behavior mandated by the old
IPsec architecture, SAD lookup on inbound packets require the use of
both the SPI and the destination address of the SA.

For inbound IPsec traffic, IRO remapping rules may exist on the MN to
remap the destination address (CoA) into the HoA.  In that case, by
design, the address found in the destination address field of the
packet (CoA) does not match the one in the SA (HoA).

At the moment, Linux XFRM stack includes the address when computing
the hash to perform state lookup by SPI. This patch changes XFRM
state hash computation to prevent destination address to be
used. This will later allow finding states for packets w/ mangled
destination addresses.

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 net/xfrm/xfrm_hash.h  |   21 +--------------------
 net/xfrm/xfrm_state.c |   20 ++++++++------------
 2 files changed, 9 insertions(+), 32 deletions(-)

diff --git a/net/xfrm/xfrm_hash.h b/net/xfrm/xfrm_hash.h
index 8e69533..19eeee7 100644
--- a/net/xfrm/xfrm_hash.h
+++ b/net/xfrm/xfrm_hash.h
@@ -4,16 +4,6 @@
 #include <linux/xfrm.h>
 #include <linux/socket.h>
 
-static inline unsigned int __xfrm4_addr_hash(xfrm_address_t *addr)
-{
-	return ntohl(addr->a4);
-}
-
-static inline unsigned int __xfrm6_addr_hash(xfrm_address_t *addr)
-{
-	return ntohl(addr->a6[2] ^ addr->a6[3]);
-}
-
 static inline unsigned int __xfrm4_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr)
 {
 	u32 sum = (__force u32)daddr->a4 + (__force u32)saddr->a4;
@@ -60,18 +50,9 @@ static inline unsigned __xfrm_src_hash(xfrm_address_t *daddr,
 }
 
 static inline unsigned int
-__xfrm_spi_hash(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family,
-		unsigned int hmask)
+__xfrm_spi_hash(__be32 spi, u8 proto, unsigned int hmask)
 {
 	unsigned int h = (__force u32)spi ^ proto;
-	switch (family) {
-	case AF_INET:
-		h ^= __xfrm4_addr_hash(daddr);
-		break;
-	case AF_INET6:
-		h ^= __xfrm6_addr_hash(daddr);
-		break;
-	}
 	return (h ^ (h >> 10) ^ (h >> 20)) & hmask;
 }
 
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index eb96ce5..b6a4d8d 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -30,7 +30,7 @@
 
 /* Each xfrm_state may be linked to two tables:
 
-   1. Hash table by (spi,daddr,ah/esp) to find SA by SPI. (input,ctl)
+   1. Hash table by (spi,ah/esp) to find SA by SPI. (input,ctl)
    2. Hash table by (daddr,family,reqid) to find what SAs exist for given
       destination/tunnel endpoint. (output)
  */
@@ -67,9 +67,9 @@ static inline unsigned int xfrm_src_hash(struct net *net,
 }
 
 static inline unsigned int
-xfrm_spi_hash(struct net *net, xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family)
+xfrm_spi_hash(struct net *net, __be32 spi, u8 proto)
 {
-	return __xfrm_spi_hash(daddr, spi, proto, family, net->xfrm.state_hmask);
+	return __xfrm_spi_hash(spi, proto, net->xfrm.state_hmask);
 }
 
 static void xfrm_hash_transfer(struct hlist_head *list,
@@ -95,9 +95,7 @@ static void xfrm_hash_transfer(struct hlist_head *list,
 		hlist_add_head(&x->bysrc, nsrctable+h);
 
 		if (x->id.spi) {
-			h = __xfrm_spi_hash(&x->id.daddr, x->id.spi,
-					    x->id.proto, x->props.family,
-					    nhashmask);
+			h = __xfrm_spi_hash(x->id.spi, x->id.proto, nhashmask);
 			hlist_add_head(&x->byspi, nspitable+h);
 		}
 	}
@@ -679,7 +677,7 @@ xfrm_init_tempstate(struct xfrm_state *x, struct flowi *fl,
 
 static struct xfrm_state *__xfrm_state_lookup(struct net *net, u32 mark, xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family)
 {
-	unsigned int h = xfrm_spi_hash(net, daddr, spi, proto, family);
+	unsigned int h = xfrm_spi_hash(net, spi, proto);
 	struct xfrm_state *x;
 	struct hlist_node *entry;
 
@@ -868,7 +866,7 @@ found:
 			h = xfrm_src_hash(net, daddr, saddr, encap_family);
 			hlist_add_head(&x->bysrc, net->xfrm.state_bysrc+h);
 			if (x->id.spi) {
-				h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, encap_family);
+				h = xfrm_spi_hash(net, x->id.spi, x->id.proto);
 				hlist_add_head(&x->byspi, net->xfrm.state_byspi+h);
 			}
 			x->lft.hard_add_expires_seconds = net->xfrm.sysctl_acq_expires;
@@ -942,9 +940,7 @@ static void __xfrm_state_insert(struct xfrm_state *x)
 	hlist_add_head(&x->bysrc, net->xfrm.state_bysrc+h);
 
 	if (x->id.spi) {
-		h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto,
-				  x->props.family);
-
+		h = xfrm_spi_hash(net, x->id.spi, x->id.proto);
 		hlist_add_head(&x->byspi, net->xfrm.state_byspi+h);
 	}
 
@@ -1535,7 +1531,7 @@ int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high)
 	}
 	if (x->id.spi) {
 		spin_lock_bh(&xfrm_state_lock);
-		h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, x->props.family);
+		h = xfrm_spi_hash(net, x->id.spi, x->id.proto);
 		hlist_add_head(&x->byspi, net->xfrm.state_byspi+h);
 		spin_unlock_bh(&xfrm_state_lock);
 
-- 
1.7.1



^ permalink raw reply related

* [PATCHv2 net-next-2.6 2/5] XFRM,IPv6: Introduce receive sockopts to access IRO remapped src/dst addresses
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>


This patch introduces IRO recv sockopts, in order for userland processes
(e.g. UMIP) to access on-wire source or destination addresses found in
incoming (IPsec-protected) packets as they were before remapping by IRO.
The socket options are respectively IPV6_RECVIROSRC and IPV6_RECVIRODST.

Basically, the two recv socket options are similar in their purpose to
their generic RH2/HAO counterparts defined in RFC 3542 (IPV6_RECVIROSRC
<->  IPV6_RECVDSTOPTS, IPV6_RECVIRODST <-> IPV6_RECVRTHDR). They differ
on the following aspects:

 - IRO reporting sockopts only work on incoming IPsec-protected packets
   Userspace will never get IRO remapped address report for common
   (non protected) packets.
 - The receiver gets the original source/desination address (IRO
   remapping) from its IPsec stack.
 - as IRO sockopts only deal with addresses, no specific structure is
   defined, i.e. struct in6_addr is used to pass info.

As we only interact with IPsec protected packets, struct sec_path is
used to carry information (addresses) for incoming packets that have
undergone remapping process.

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 include/linux/in6.h      |    7 +++++++
 include/linux/ipv6.h     |    4 +++-
 include/net/xfrm.h       |    5 +++++
 net/ipv6/datagram.c      |   18 ++++++++++++++++++
 net/ipv6/ipv6_sockglue.c |   26 ++++++++++++++++++++++++++
 5 files changed, 59 insertions(+), 1 deletions(-)

diff --git a/include/linux/in6.h b/include/linux/in6.h
index c4bf46f..52a98ab 100644
--- a/include/linux/in6.h
+++ b/include/linux/in6.h
@@ -283,4 +283,11 @@ struct in6_flowlabel_req {
  * MRT6_PIM			208
  * (reserved)			209
  */
+
+/* IRO (IPsec Route Optimization) sockopts */
+#define IPV6_RECVIROSRC         74
+#define IPV6_IROSRC		75
+#define IPV6_RECVIRODST         76
+#define IPV6_IRODST		77
+
 #endif
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index e62683b..55289ee 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -341,7 +341,9 @@ struct ipv6_pinfo {
 				odstopts:1,
                                 rxflow:1,
 				rxtclass:1,
-				rxpmtu:1;
+				rxpmtu:1,
+				irosrc:1,
+				irodst:1;
 		} bits;
 		__u16		all;
 	} rxopt;
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4f53532..e6a753c 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -909,6 +909,11 @@ struct sec_path {
 	atomic_t		refcnt;
 	int			len;
 	struct xfrm_state	*xvec[XFRM_MAX_DEPTH];
+
+#ifdef CONFIG_XFRM_SUB_POLICY
+	struct in6_addr         irosrc;
+	struct in6_addr         irodst;
+#endif
 };
 
 static inline struct sec_path *
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index ef371aa..2952c9e 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -29,6 +29,7 @@
 #include <net/transp_v6.h>
 #include <net/ip6_route.h>
 #include <net/tcp_states.h>
+#include <net/xfrm.h>
 
 #include <linux/errqueue.h>
 #include <asm/uaccess.h>
@@ -504,6 +505,23 @@ int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
 		put_cmsg(msg, SOL_IPV6, IPV6_HOPOPTS, (ptr[1]+1)<<3, ptr);
 	}
 
+#ifdef CONFIG_XFRM_SUB_POLICY
+	/* If access to IRO-remapped source or destination address has been
+	 * requested and it has indeed been remapped, provide the on-wire
+	 * address to userland */
+	if (skb_sec_path(skb)) {
+		struct sec_path *sp = skb_sec_path(skb);
+
+		if (np->rxopt.bits.irosrc && !ipv6_addr_any(&sp->irosrc))
+			put_cmsg(msg, SOL_IPV6, IPV6_IROSRC,
+				 sizeof(sp->irosrc), &sp->irosrc);
+
+		if (np->rxopt.bits.irodst && !ipv6_addr_any(&sp->irodst))
+			put_cmsg(msg, SOL_IPV6, IPV6_IRODST,
+				 sizeof(sp->irodst), &sp->irodst);
+	}
+#endif
+
 	if (opt->lastopt &&
 	    (np->rxopt.bits.dstopts || np->rxopt.bits.srcrt)) {
 		/*
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index a7f66bc..722a49f 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -302,6 +302,22 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
 		retv = 0;
 		break;
 
+#ifdef CONFIG_XFRM_SUB_POLICY
+	case IPV6_RECVIROSRC:
+		if (optlen < sizeof(int))
+			goto e_inval;
+		np->rxopt.bits.irosrc = valbool;
+		retv = 0;
+		break;
+
+	case IPV6_RECVIRODST:
+		if (optlen < sizeof(int))
+			goto e_inval;
+		np->rxopt.bits.irodst = valbool;
+		retv = 0;
+		break;
+#endif
+
 	case IPV6_2292DSTOPTS:
 		if (optlen < sizeof(int))
 			goto e_inval;
@@ -1056,6 +1072,16 @@ static int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
 		val = np->rxopt.bits.dstopts;
 		break;
 
+#ifdef CONFIG_XFRM_SUB_POLICY
+	case IPV6_RECVIROSRC:
+		val = np->rxopt.bits.irosrc;
+		break;
+
+	case IPV6_RECVIRODST:
+		val = np->rxopt.bits.irodst;
+		break;
+#endif
+
 	case IPV6_2292DSTOPTS:
 		val = np->rxopt.bits.odstopts;
 		break;
-- 
1.7.1



^ permalink raw reply related

* [PATCHv2 net-next-2.6 3/5] XFRM,IPv6: Add IRO src/dst address remapping XFRM types and i/o handlers
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>


Add IRO source and destination remapping XFRM types and associated
input/output handlers. This allows userland to install such states
in order to support remapping of source or destination address
of packet. They basically work like existing RH2 and HAO ones; the
main difference is that output handlers do not expand the packet by
adding an extension header: they simply change the source or
destination in place. Input handlers are almost the same as RH2/HAO
version in their behavior, but they are triggered differently. RH2
and HAO handlers are triggered based on structures found in the
packet. On input, IRO states (and associated handlers) are looked
up when processing an IPsec-protected packet, when there is an
address mismatch.

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 include/net/xfrm.h       |    2 +
 net/ipv6/mip6.c          |  153 ++++++++++++++++++++++++++++++++++++++++++++++
 net/ipv6/xfrm6_mode_ro.c |   11 +++-
 net/xfrm/xfrm_user.c     |    4 +
 4 files changed, 169 insertions(+), 1 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index e6a753c..05b2b1f 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -35,6 +35,8 @@
 #define XFRM_PROTO_IPV6		41
 #define XFRM_PROTO_ROUTING	IPPROTO_ROUTING
 #define XFRM_PROTO_DSTOPTS	IPPROTO_DSTOPTS
+#define XFRM_PROTO_IRO_SRC      127
+#define XFRM_PROTO_IRO_DST      128
 
 #define XFRM_ALIGN8(len)	(((len) + 7) & ~7)
 #define MODULE_ALIAS_XFRM_MODE(family, encap) \
diff --git a/net/ipv6/mip6.c b/net/ipv6/mip6.c
index d6e9599..04b9e1d 100644
--- a/net/ipv6/mip6.c
+++ b/net/ipv6/mip6.c
@@ -477,6 +477,131 @@ static const struct xfrm_type mip6_rthdr_type =
 	.hdr_offset	= mip6_rthdr_offset,
 };
 
+#ifdef CONFIG_XFRM_SUB_POLICY
+/* IRO equivalent of mip6_destopt_input(): handles incoming packet with a
+ * source address different from the one expected in the SA: check that
+ * received source address is indeed the CoA we expected (or any address
+ * if the state references the unspecified address '::') */
+static int mip6_iro_src_input(struct xfrm_state *x, struct sk_buff *skb)
+{
+	struct ipv6hdr *iph = ipv6_hdr(skb);
+	int err = 1;
+
+	spin_lock(&x->lock);
+	if (!ipv6_addr_equal(&iph->saddr, (struct in6_addr *)x->coaddr) &&
+	    !ipv6_addr_any((struct in6_addr *)x->coaddr))
+		err = -ENOENT;
+	spin_unlock(&x->lock);
+
+	return err;
+}
+
+/* IRO equivalent of mip6_destopt_output(): replaces current source address
+ * of outgoing packet by state's CoA. */
+static int mip6_iro_src_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+	struct ipv6hdr *iph = ipv6_hdr(skb);
+
+	spin_lock_bh(&x->lock);
+	memcpy(&iph->saddr, x->coaddr, sizeof(iph->saddr));
+	spin_unlock_bh(&x->lock);
+
+	return 0;
+}
+
+static int mip6_iro_src_reject(struct xfrm_state *x, struct sk_buff *skb, struct flowi *fl)
+{
+	int err = 0;
+
+	/* XXX We may need some reject handler at some point but it is not
+	 * critical yet: see xfrm_secpath_reject() in net/xfrm/xfrm_policy.c
+	 * and aslo what mip6_destopt_reject() implements */
+
+	printk("XXX FIXME: mip6_iro_src_reject() called\n");
+
+	return err;
+}
+
+/* This is the IRO equivalent of mip6_rthdr_input(): handles incoming packet
+ * with a destination address different from the one expected in the SA:
+ * check that received destination address is indeed the CoA we expected
+ * (or any address if the state references the unspecified address '::') */
+static int mip6_iro_dst_input(struct xfrm_state *x, struct sk_buff *skb)
+{
+	struct ipv6hdr *iph = ipv6_hdr(skb);
+	int err = 1;
+
+	spin_lock(&x->lock);
+	if (!ipv6_addr_equal(&iph->daddr, (struct in6_addr *)x->coaddr) &&
+	    !ipv6_addr_any((struct in6_addr *)x->coaddr))
+		err = -ENOENT;
+	spin_unlock(&x->lock);
+
+	return err;
+}
+
+/* IRO equivalent of mip6_rthdr_output(): replaces current destination
+ * address of outgoing packet with state's CoA */
+static int mip6_iro_dst_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+	struct ipv6hdr *iph = ipv6_hdr(skb);
+
+	spin_lock_bh(&x->lock);
+	memcpy(&iph->daddr, x->coaddr, sizeof(iph->daddr));
+	spin_unlock_bh(&x->lock);
+
+	return 0;
+}
+
+/* Common to iro src and dst remapping states. */
+static int mip6_iro_init_state(struct xfrm_state *x)
+{
+	if (x->id.spi) {
+		printk(KERN_INFO "%s: spi is not 0: %u\n", __func__,
+		       x->id.spi);
+		return -EINVAL;
+	}
+	if (x->props.mode != XFRM_MODE_ROUTEOPTIMIZATION) {
+		printk(KERN_INFO "%s: state's mode is not %u: %u\n",
+		       __func__, XFRM_MODE_ROUTEOPTIMIZATION,
+		       x->props.mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Unlike common IPsec protocols, nothing to do when destroying */
+static void mip6_iro_destroy(struct xfrm_state *x)
+{
+}
+
+static const struct xfrm_type mip6_iro_src_type =
+{
+	.description	= "MIP6_IRO_SRC",
+	.owner		= THIS_MODULE,
+	.proto	     	= XFRM_PROTO_IRO_SRC,
+	.flags		= XFRM_TYPE_NON_FRAGMENT | XFRM_TYPE_LOCAL_COADDR,
+	.init_state	= mip6_iro_init_state,
+	.destructor	= mip6_iro_destroy,
+	.input		= mip6_iro_src_input,
+	.output		= mip6_iro_src_output,
+	.reject         = mip6_iro_src_reject,
+};
+
+static const struct xfrm_type mip6_iro_dst_type =
+{
+	.description	= "MIP6_IRO_DST",
+	.owner		= THIS_MODULE,
+	.proto	     	= XFRM_PROTO_IRO_DST,
+	.flags		= XFRM_TYPE_NON_FRAGMENT | XFRM_TYPE_REMOTE_COADDR,
+	.init_state	= mip6_iro_init_state,
+	.destructor	= mip6_iro_destroy,
+	.input		= mip6_iro_dst_input,
+	.output		= mip6_iro_dst_output,
+};
+#endif /* CONFIG_XFRM_SUB_POLICY */
+
 static int __init mip6_init(void)
 {
 	printk(KERN_INFO "Mobile IPv6\n");
@@ -489,6 +614,20 @@ static int __init mip6_init(void)
 		printk(KERN_INFO "%s: can't add xfrm type(rthdr)\n", __func__);
 		goto mip6_rthdr_xfrm_fail;
 	}
+
+#ifdef CONFIG_XFRM_SUB_POLICY
+	if (xfrm_register_type(&mip6_iro_src_type, AF_INET6) < 0) {
+		printk(KERN_INFO "%s: can't add xfrm type(IRO src remap)\n",
+		       __func__);
+		goto mip6_iro_src_remap_xfrm_fail;
+	}
+	if (xfrm_register_type(&mip6_iro_dst_type, AF_INET6) < 0) {
+		printk(KERN_INFO "%s: can't add xfrm type(IRO dst remap)\n",
+		       __func__);
+		goto mip6_iro_dst_remap_xfrm_fail;
+	}
+#endif
+
 	if (rawv6_mh_filter_register(mip6_mh_filter) < 0) {
 		printk(KERN_INFO "%s: can't add rawv6 mh filter\n", __func__);
 		goto mip6_rawv6_mh_fail;
@@ -498,6 +637,12 @@ static int __init mip6_init(void)
 	return 0;
 
  mip6_rawv6_mh_fail:
+#ifdef CONFIG_XFRM_SUB_POLICY
+	xfrm_unregister_type(&mip6_iro_dst_type, AF_INET6);
+ mip6_iro_dst_remap_xfrm_fail:
+	xfrm_unregister_type(&mip6_iro_src_type, AF_INET6);
+ mip6_iro_src_remap_xfrm_fail:
+#endif
 	xfrm_unregister_type(&mip6_rthdr_type, AF_INET6);
  mip6_rthdr_xfrm_fail:
 	xfrm_unregister_type(&mip6_destopt_type, AF_INET6);
@@ -509,6 +654,14 @@ static void __exit mip6_fini(void)
 {
 	if (rawv6_mh_filter_unregister(mip6_mh_filter) < 0)
 		printk(KERN_INFO "%s: can't remove rawv6 mh filter\n", __func__);
+#ifdef CONFIG_XFRM_SUB_POLICY
+	if (xfrm_unregister_type(&mip6_iro_dst_type, AF_INET6) < 0)
+		printk(KERN_INFO "%s: can't remove xfrm type(IRO dst remap)\n",
+		       __func__);
+	if (xfrm_unregister_type(&mip6_iro_src_type, AF_INET6) < 0)
+		printk(KERN_INFO "%s: can't remove xfrm type(IRO src remap)\n",
+		       __func__);
+#endif
 	if (xfrm_unregister_type(&mip6_rthdr_type, AF_INET6) < 0)
 		printk(KERN_INFO "%s: can't remove xfrm type(rthdr)\n", __func__);
 	if (xfrm_unregister_type(&mip6_destopt_type, AF_INET6) < 0)
diff --git a/net/ipv6/xfrm6_mode_ro.c b/net/ipv6/xfrm6_mode_ro.c
index 63d5d49..ea33178 100644
--- a/net/ipv6/xfrm6_mode_ro.c
+++ b/net/ipv6/xfrm6_mode_ro.c
@@ -45,6 +45,15 @@ static int xfrm6_ro_output(struct xfrm_state *x, struct sk_buff *skb)
 	u8 *prevhdr;
 	int hdr_len;
 
+	/* Unlike RH2 (IPPROTO_ROUTING) and HAO in DstOpt
+	 * (IPPROTO_DSTOPTS), IRO remapping states do not
+	 * add extension header to the packet. Source
+	 * and/or destination addresses are simply modified
+	 * in place. */
+	if (x->id.proto == XFRM_PROTO_IRO_SRC ||
+	    x->id.proto == XFRM_PROTO_IRO_DST)
+		goto out;
+
 	iph = ipv6_hdr(skb);
 
 	hdr_len = x->type->hdr_offset(x, skb, &prevhdr);
@@ -54,8 +63,8 @@ static int xfrm6_ro_output(struct xfrm_state *x, struct sk_buff *skb)
 	__skb_pull(skb, hdr_len);
 	memmove(ipv6_hdr(skb), iph, hdr_len);
 
+ out:
 	x->lastused = get_seconds();
-
 	return 0;
 }
 
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 8bae6b2..2aecd40 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -179,6 +179,10 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
 	case IPPROTO_DSTOPTS:
 	case IPPROTO_ROUTING:
+#ifdef CONFIG_XFRM_SUB_POLICY
+	case XFRM_PROTO_IRO_SRC:
+	case XFRM_PROTO_IRO_DST:
+#endif
 		if (attrs[XFRMA_ALG_COMP]	||
 		    attrs[XFRMA_ALG_AUTH]	||
 		    attrs[XFRMA_ALG_AUTH_TRUNC]	||
-- 
1.7.1



^ permalink raw reply related

* [PATCHv2 net-next-2.6 4/5] XFRM,IPv6: Add IRO remapping hook in xfrm_input()
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>


Add a hook in xfrm_input() to allow IRO remapping to occur when
an incoming packet matching an existing SA (based on SPI) with
an unexpected destination or source address is received.
Because IRO does not consume additional bits in a packet (that's
the point), there is no way to demultiplex based on something
like nh or spi. Instead, IRO input handlers (for source and
destination address remapping) are called upon address mismatch
during IPsec processing.
For that to work, we rely on the fact that SPI values generated
locally are no more linked to destination address (first patch
of the set) and we postpone a bit the expected address check in
xfrm_input() (inside xfrm_state_lookup() against daddr param) by
introducing xfrm_input_addr_check() helper.

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 include/net/xfrm.h     |    3 ++
 net/ipv4/xfrm4_input.c |   12 ++++++++
 net/ipv6/xfrm6_input.c |   70 +++++++++++++++++++++++++++++++++++++++++++++++-
 net/xfrm/xfrm_input.c  |   21 +++++++++++++-
 net/xfrm/xfrm_state.c  |    2 +-
 5 files changed, 104 insertions(+), 4 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 05b2b1f..f536b2f 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -623,6 +623,7 @@ struct xfrm_spi_skb_cb {
 		struct inet6_skb_parm h6;
 	} header;
 
+	unsigned int saddroff;
 	unsigned int daddroff;
 	unsigned int family;
 };
@@ -1405,6 +1406,7 @@ extern int xfrm4_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi,
 			   int encap_type);
 extern int xfrm4_transport_finish(struct sk_buff *skb, int async);
 extern int xfrm4_rcv(struct sk_buff *skb);
+extern int xfrm4_input_addr_check(struct sk_buff *skb, struct xfrm_state *x);
 
 static inline int xfrm4_rcv_spi(struct sk_buff *skb, int nexthdr, __be32 spi)
 {
@@ -1423,6 +1425,7 @@ extern int xfrm6_transport_finish(struct sk_buff *skb, int async);
 extern int xfrm6_rcv(struct sk_buff *skb);
 extern int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
 			    xfrm_address_t *saddr, u8 proto);
+extern int xfrm6_input_addr_check(struct sk_buff *skb, struct xfrm_state *x);
 extern int xfrm6_tunnel_register(struct xfrm6_tunnel *handler, unsigned short family);
 extern int xfrm6_tunnel_deregister(struct xfrm6_tunnel *handler, unsigned short family);
 extern __be32 xfrm6_tunnel_alloc_spi(struct net *net, xfrm_address_t *saddr);
diff --git a/net/ipv4/xfrm4_input.c b/net/ipv4/xfrm4_input.c
index 06814b6..82e23ec 100644
--- a/net/ipv4/xfrm4_input.c
+++ b/net/ipv4/xfrm4_input.c
@@ -41,6 +41,7 @@ int xfrm4_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi,
 		    int encap_type)
 {
 	XFRM_SPI_SKB_CB(skb)->family = AF_INET;
+	XFRM_SPI_SKB_CB(skb)->saddroff = offsetof(struct iphdr, saddr);
 	XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct iphdr, daddr);
 	return xfrm_input(skb, nexthdr, spi, encap_type);
 }
@@ -164,3 +165,14 @@ int xfrm4_rcv(struct sk_buff *skb)
 	return xfrm4_rcv_spi(skb, ip_hdr(skb)->protocol, 0);
 }
 EXPORT_SYMBOL(xfrm4_rcv);
+
+int xfrm4_input_addr_check(struct sk_buff *skb, struct xfrm_state *x)
+{
+	xfrm_address_t *daddr;
+
+	daddr = (xfrm_address_t *)(skb_network_header(skb) +
+				   XFRM_SPI_SKB_CB(skb)->daddroff);
+
+	return xfrm_addr_cmp(&x->id.daddr, daddr, AF_INET);
+}
+EXPORT_SYMBOL(xfrm4_input_addr_check);
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index f8c3cf8..754ecf7 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -15,6 +15,7 @@
 #include <linux/netfilter_ipv6.h>
 #include <net/ipv6.h>
 #include <net/xfrm.h>
+#include <net/ip6_route.h> /* XXX for ip6_route_input() */
 
 int xfrm6_extract_input(struct xfrm_state *x, struct sk_buff *skb)
 {
@@ -24,6 +25,7 @@ int xfrm6_extract_input(struct xfrm_state *x, struct sk_buff *skb)
 int xfrm6_rcv_spi(struct sk_buff *skb, int nexthdr, __be32 spi)
 {
 	XFRM_SPI_SKB_CB(skb)->family = AF_INET6;
+	XFRM_SPI_SKB_CB(skb)->saddroff = offsetof(struct ipv6hdr, saddr);
 	XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct ipv6hdr, daddr);
 	return xfrm_input(skb, nexthdr, spi, 0);
 }
@@ -142,5 +144,71 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
 drop:
 	return -1;
 }
-
 EXPORT_SYMBOL(xfrm6_input_addr);
+
+#if defined(CONFIG_XFRM_SUB_POLICY)
+/* Perform check on source and destination addresses and possibly IRO
+ * address remapping upon mismatch and if matching IRO state exists. */
+int xfrm6_input_addr_check(struct sk_buff *skb, struct xfrm_state *x)
+{
+	xfrm_address_t *saddr, *exp_saddr, *daddr, *exp_daddr;
+
+	saddr = (xfrm_address_t *)(skb_network_header(skb) +
+				   XFRM_SPI_SKB_CB(skb)->saddroff);
+	daddr = (xfrm_address_t *)(skb_network_header(skb) +
+				   XFRM_SPI_SKB_CB(skb)->daddroff);
+
+	exp_daddr = &x->id.daddr;
+	if (xfrm_addr_cmp(exp_daddr, daddr, AF_INET6)) {
+		/* Destination address mismatch: check if we have an IRO
+		 * destination remapping state to explain that.
+		 *
+		 * Note: saddr is provided as a hint. If source address
+		 * is also a remapped one, xfrm6_input_addr() will manage
+		 * to find IRO destination remapping state */
+		if (xfrm6_input_addr(skb, exp_daddr, saddr,
+				     XFRM_PROTO_IRO_DST) < 0)
+			return -1;
+
+		/* Copy destination address to sec_path for sock opts and
+		 * replace packet destination address with expected HoA */
+		ipv6_addr_copy(&skb->sp->irodst, (struct in6_addr *)daddr);
+		ipv6_addr_copy((struct in6_addr *)daddr,
+			       (struct in6_addr *)exp_daddr);
+
+		skb_dst_drop(skb);
+		ip6_route_input(skb);
+		if (skb_dst(skb)->error)
+			return -1;
+	}
+
+	exp_saddr = &x->props.saddr;
+	if (xfrm_addr_cmp(exp_saddr, saddr, AF_INET6)) {
+		/* Source address mismatch: check if we have an IRO
+		 * source remapping state to explain that.
+		 *
+		 * Note: unlike for destination addresses above, a
+		 * source mismatch is not considered fatal */
+		if (xfrm6_input_addr(skb, daddr, exp_saddr,
+				     XFRM_PROTO_IRO_SRC) < 0)
+			return 0;
+
+		/* Copy destination address to sec_path for sock opts and
+		 * then replace source address with expected peer's HoA */
+		ipv6_addr_copy(&skb->sp->irosrc, (struct in6_addr *)saddr);
+		ipv6_addr_copy((struct in6_addr *)saddr,
+			       (struct in6_addr *)exp_saddr);
+	}
+
+	return 0;
+}
+#else
+int xfrm6_input_addr_check(struct sk_buff *skb, struct xfrm_state *x)
+{
+	xfrm_address_t *daddr;
+	daddr = (xfrm_address_t *)(skb_network_header(skb) +
+				   XFRM_SPI_SKB_CB(skb)->daddroff);
+	return xfrm_addr_cmp(&x->id.daddr, daddr, AF_INET6);
+}
+#endif
+EXPORT_SYMBOL(xfrm6_input_addr_check);
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index 45f1c98..7927af7 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -102,6 +102,23 @@ int xfrm_prepare_input(struct xfrm_state *x, struct sk_buff *skb)
 }
 EXPORT_SYMBOL(xfrm_prepare_input);
 
+static inline int xfrm_input_addr_check(struct sk_buff *skb, struct xfrm_state *x,
+					unsigned int family)
+{
+	switch (family) {
+#ifdef CONFIG_INET
+	case AF_INET:
+		return xfrm4_input_addr_check(skb, x);
+#endif
+#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
+	case AF_INET6:
+		return xfrm6_input_addr_check(skb, x);
+#endif
+	default:
+		return 0;
+	}
+}
+
 int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 {
 	struct net *net = dev_net(skb->dev);
@@ -152,8 +169,8 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 			goto drop;
 		}
 
-		x = xfrm_state_lookup(net, skb->mark, daddr, spi, nexthdr, family);
-		if (x == NULL) {
+		x = xfrm_state_lookup(net, skb->mark, NULL, spi, nexthdr, family);
+		if (x == NULL || xfrm_input_addr_check(skb, x, family)) {
 			XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);
 			xfrm_audit_state_notfound(skb, family, spi, seq);
 			goto drop;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index b6a4d8d..b8f7c08 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -685,7 +685,7 @@ static struct xfrm_state *__xfrm_state_lookup(struct net *net, u32 mark, xfrm_ad
 		if (x->props.family != family ||
 		    x->id.spi       != spi ||
 		    x->id.proto     != proto ||
-		    xfrm_addr_cmp(&x->id.daddr, daddr, family))
+		    (daddr && xfrm_addr_cmp(&x->id.daddr, daddr, family)))
 			continue;
 
 		if ((mark & x->mark.m) != x->mark.v)
-- 
1.7.1



^ permalink raw reply related

* [PATCHv2 net-next-2.6 5/5] XFRM,IPv6: Add IRO remapping capability via socket ancillary data path
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>


This provides the ability to remap src/dst address using IRO
via ancillary data passed to sockets. This is the IRO equivalent
of what is done for RH2/HAO (i.e. IPV6_RTHDR/IPV6_DSTOPTS).
This is used by UMIP during BA emission when acting as a Home
Agent.

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 include/net/ipv6.h       |    4 ++++
 net/ipv6/datagram.c      |   20 ++++++++++++++++++++
 net/ipv6/exthdrs.c       |   19 +++++++++++++------
 net/ipv6/ip6_flowlabel.c |    7 +++++++
 net/ipv6/ip6_output.c    |   22 ++++++++++++++++++++++
 net/ipv6/raw.c           |    3 ++-
 6 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 4a3cd2c..2ba96d8 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -188,6 +188,10 @@ struct ipv6_txoptions {
 	struct ipv6_rt_hdr	*srcrt;	/* Routing Header */
 	struct ipv6_opt_hdr	*dst1opt;
 
+	/* XXX protect those via some ifdef e.g. CONFIG_XFRM_SUB_POLICY ? */
+	struct in6_addr         *iro_src;
+	struct in6_addr         *iro_dst;
+
 	/* Option buffer, as read by IPV6_PKTOPTIONS, starts here. */
 };
 
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 2952c9e..0ac7adf 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -757,6 +757,26 @@ int datagram_send_ctl(struct net *net,
 			}
 			break;
 
+#ifdef CONFIG_XFRM_SUB_POLICY
+		case IPV6_IROSRC:
+		case IPV6_IRODST:
+			if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct in6_addr))) {
+				err = -EINVAL;
+				goto exit_f;
+			}
+
+			if (!capable(CAP_NET_RAW)) {
+				err = -EPERM;
+				goto exit_f;
+			}
+
+			if (cmsg->cmsg_type == IPV6_IROSRC)
+				opt->iro_src = (struct in6_addr *)CMSG_DATA(cmsg);
+			else
+				opt->iro_dst = (struct in6_addr *)CMSG_DATA(cmsg);
+			break;
+#endif
+
 		case IPV6_2292RTHDR:
 		case IPV6_RTHDR:
 			if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) {
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 262f105..e480b06 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -750,6 +750,10 @@ ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
 			*((char**)&opt2->dst1opt) += dif;
 		if (opt2->srcrt)
 			*((char**)&opt2->srcrt) += dif;
+		if (opt2->iro_src)
+			*((char**)&opt2->iro_src) += dif;
+		if (opt2->iro_dst)
+			*((char**)&opt2->iro_dst) += dif;
 	}
 	return opt2;
 }
@@ -874,24 +878,27 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
 
 /**
  * fl6_update_dst - update flowi destination address with info given
- *                  by srcrt option, if any.
+ *                  by srcrt/iro_dst option, if any.
  *
  * @fl: flowi for which fl6_dst is to be updated
- * @opt: struct ipv6_txoptions in which to look for srcrt opt
+ * @opt: struct ipv6_txoptions in which to look for srcrt/iro_dst opt
  * @orig: copy of original fl6_dst address if modified
  *
- * Returns NULL if no txoptions or no srcrt, otherwise returns orig
- * and initial value of fl->fl6_dst set in orig
+ * Returns NULL if no txoptions or no options to change flowi destination
+ * (srcrt or IRO destination remapping rule), otherwise returns orig and
+ * initial value of fl->fl6_dst set in orig
  */
 struct in6_addr *fl6_update_dst(struct flowi *fl,
 				const struct ipv6_txoptions *opt,
 				struct in6_addr *orig)
 {
-	if (!opt || !opt->srcrt)
+	if (!opt || (!opt->srcrt && !opt->iro_dst))
 		return NULL;
 
 	ipv6_addr_copy(orig, &fl->fl6_dst);
-	ipv6_addr_copy(&fl->fl6_dst, ((struct rt0_hdr *)opt->srcrt)->addr);
+	ipv6_addr_copy(&fl->fl6_dst,
+		       opt->iro_dst ?: ((struct rt0_hdr *)opt->srcrt)->addr);
+
 	return orig;
 }
 
diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c
index 1365468..dbf9c29 100644
--- a/net/ipv6/ip6_flowlabel.c
+++ b/net/ipv6/ip6_flowlabel.c
@@ -280,6 +280,9 @@ struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
 		opt_space->hopopt = fl_opt->hopopt;
 		opt_space->dst0opt = fl_opt->dst0opt;
 		opt_space->srcrt = fl_opt->srcrt;
+		/* XXX protect those via some ifdef - see net/ipv6.h */
+		opt_space->iro_src = fl_opt->iro_src;
+		opt_space->iro_dst = fl_opt->iro_dst;
 		opt_space->opt_nflen = fl_opt->opt_nflen;
 	} else {
 		if (fopt->opt_nflen == 0)
@@ -287,6 +290,9 @@ struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
 		opt_space->hopopt = NULL;
 		opt_space->dst0opt = NULL;
 		opt_space->srcrt = NULL;
+		/* XXX protect those via some ifdef - see net/ipv6.h */
+		opt_space->iro_src = NULL;
+		opt_space->iro_dst = NULL;
 		opt_space->opt_nflen = 0;
 	}
 	opt_space->dst1opt = fopt->dst1opt;
@@ -456,6 +462,7 @@ static int ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
 		return 1;
 	if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
 		return 1;
+
 	return 0;
 }
 
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 99157b4..210f269 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -222,6 +222,8 @@ int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
 			ipv6_push_frag_opts(skb, opt, &proto);
 		if (opt->opt_nflen)
 			ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop);
+		if (opt->iro_dst)
+			first_hop = opt->iro_dst;
 	}
 
 	skb_push(skb, sizeof(struct ipv6hdr));
@@ -1106,6 +1108,12 @@ static inline struct ipv6_rt_hdr *ip6_rthdr_dup(struct ipv6_rt_hdr *src,
 	return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
 }
 
+static inline struct in6_addr *ip6_iro_addr_dup(struct in6_addr *addr,
+						gfp_t gfp)
+{
+	return addr ? kmemdup(addr, sizeof(struct in6_addr), gfp) : NULL;
+}
+
 int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
 	int offset, int len, int odd, struct sk_buff *skb),
 	void *from, int length, int transhdrlen,
@@ -1162,6 +1170,16 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
 			if (opt->srcrt && !np->cork.opt->srcrt)
 				return -ENOBUFS;
 
+			np->cork.opt->iro_src = ip6_iro_addr_dup(opt->iro_src,
+								 sk->sk_allocation);
+			if (opt->iro_src && !np->cork.opt->iro_src)
+				return -ENOBUFS;
+
+			np->cork.opt->iro_dst = ip6_iro_addr_dup(opt->iro_dst,
+								 sk->sk_allocation);
+			if (opt->iro_dst && !np->cork.opt->iro_dst)
+				return -ENOBUFS;
+
 			/* need source address above miyazawa*/
 		}
 		dst_hold(&rt->dst);
@@ -1440,6 +1458,8 @@ static void ip6_cork_release(struct inet_sock *inet, struct ipv6_pinfo *np)
 		kfree(np->cork.opt->dst1opt);
 		kfree(np->cork.opt->hopopt);
 		kfree(np->cork.opt->srcrt);
+		kfree(np->cork.opt->iro_src);
+		kfree(np->cork.opt->iro_dst);
 		kfree(np->cork.opt);
 		np->cork.opt = NULL;
 	}
@@ -1495,6 +1515,8 @@ int ip6_push_pending_frames(struct sock *sk)
 		ipv6_push_frag_opts(skb, opt, &proto);
 	if (opt && opt->opt_nflen)
 		ipv6_push_nfrag_opts(skb, opt, &proto, &final_dst);
+	if (opt && opt->iro_dst)
+		final_dst = opt->iro_dst;
 
 	skb_push(skb, sizeof(struct ipv6hdr));
 	skb_reset_network_header(skb);
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index 45e6efb..1a11bd5 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -828,7 +828,8 @@ static int rawv6_sendmsg(struct kiocb *iocb, struct sock *sk,
 			if (flowlabel == NULL)
 				return -EINVAL;
 		}
-		if (!(opt->opt_nflen|opt->opt_flen))
+		if (!(opt->opt_nflen|opt->opt_flen) &&
+		    (!opt->iro_src && !opt->iro_dst))
 			opt = NULL;
 	}
 	if (opt == NULL)
-- 
1.7.1


^ permalink raw reply related

* [PATCH net-next2.6] net: rename netdev rx_queue to ingress_queue
From: Eric Dumazet @ 2010-09-28 15:58 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, Jarek Poplawski

There is some confusion with rx_queue name after RPS, and net drivers
private rx_queue fields.

I suggest to rename "struct net_device"->rx_queue to ingress_queue.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---

My plan is to make this structure dynamically allocated, in the rare
case ingress is setup, to shrink net_device.

 include/linux/netdevice.h |    2 +-
 net/core/dev.c            |    8 ++++----
 net/sched/sch_api.c       |   14 +++++++-------
 net/sched/sch_generic.c   |    8 ++++----
 4 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 83de0eb..1ae0d65 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -983,7 +983,7 @@ struct net_device {
 	rx_handler_func_t	*rx_handler;
 	void			*rx_handler_data;
 
-	struct netdev_queue	rx_queue; /* use two cache lines */
+	struct netdev_queue	ingress_queue; /* use two cache lines */
 
 /*
  * Cache lines mostly used on transmit path
diff --git a/net/core/dev.c b/net/core/dev.c
index 42b200f..0ed0669 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2674,7 +2674,7 @@ static int ing_filter(struct sk_buff *skb)
 	skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
 	skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
 
-	rxq = &dev->rx_queue;
+	rxq = &dev->ingress_queue;
 
 	q = rxq->qdisc;
 	if (q != &noop_qdisc) {
@@ -2691,7 +2691,7 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb,
 					 struct packet_type **pt_prev,
 					 int *ret, struct net_device *orig_dev)
 {
-	if (skb->dev->rx_queue.qdisc == &noop_qdisc)
+	if (skb->dev->ingress_queue.qdisc == &noop_qdisc)
 		goto out;
 
 	if (*pt_prev) {
@@ -4894,7 +4894,7 @@ static void __netdev_init_queue_locks_one(struct net_device *dev,
 static void netdev_init_queue_locks(struct net_device *dev)
 {
 	netdev_for_each_tx_queue(dev, __netdev_init_queue_locks_one, NULL);
-	__netdev_init_queue_locks_one(dev, &dev->rx_queue, NULL);
+	__netdev_init_queue_locks_one(dev, &dev->ingress_queue, NULL);
 }
 
 unsigned long netdev_fix_features(unsigned long features, const char *name)
@@ -5406,7 +5406,7 @@ static void netdev_init_one_queue(struct net_device *dev,
 
 static void netdev_init_queues(struct net_device *dev)
 {
-	netdev_init_one_queue(dev, &dev->rx_queue, NULL);
+	netdev_init_one_queue(dev, &dev->ingress_queue, NULL);
 	netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
 	spin_lock_init(&dev->tx_global_lock);
 }
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 6fb3d41..b802078 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -240,7 +240,7 @@ struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
 	if (q)
 		goto out;
 
-	q = qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle);
+	q = qdisc_match_from_root(dev->ingress_queue.qdisc_sleeping, handle);
 out:
 	return q;
 }
@@ -701,7 +701,7 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
 		}
 
 		for (i = 0; i < num_q; i++) {
-			struct netdev_queue *dev_queue = &dev->rx_queue;
+			struct netdev_queue *dev_queue = &dev->ingress_queue;
 
 			if (!ingress)
 				dev_queue = netdev_get_tx_queue(dev, i);
@@ -979,7 +979,7 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
 					return -ENOENT;
 				q = qdisc_leaf(p, clid);
 			} else { /* ingress */
-				q = dev->rx_queue.qdisc_sleeping;
+				q = dev->ingress_queue.qdisc_sleeping;
 			}
 		} else {
 			q = dev->qdisc;
@@ -1044,7 +1044,7 @@ replay:
 					return -ENOENT;
 				q = qdisc_leaf(p, clid);
 			} else { /*ingress */
-				q = dev->rx_queue.qdisc_sleeping;
+				q = dev->ingress_queue.qdisc_sleeping;
 			}
 		} else {
 			q = dev->qdisc;
@@ -1124,7 +1124,7 @@ create_n_graft:
 	if (!(n->nlmsg_flags&NLM_F_CREATE))
 		return -ENOENT;
 	if (clid == TC_H_INGRESS)
-		q = qdisc_create(dev, &dev->rx_queue, p,
+		q = qdisc_create(dev, &dev->ingress_queue, p,
 				 tcm->tcm_parent, tcm->tcm_parent,
 				 tca, &err);
 	else {
@@ -1304,7 +1304,7 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
 		if (tc_dump_qdisc_root(dev->qdisc, skb, cb, &q_idx, s_q_idx) < 0)
 			goto done;
 
-		dev_queue = &dev->rx_queue;
+		dev_queue = &dev->ingress_queue;
 		if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
 			goto done;
 
@@ -1595,7 +1595,7 @@ static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
 	if (tc_dump_tclass_root(dev->qdisc, skb, tcm, cb, &t, s_t) < 0)
 		goto done;
 
-	dev_queue = &dev->rx_queue;
+	dev_queue = &dev->ingress_queue;
 	if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
 		goto done;
 
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index 2aeb3a4..545278a 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -753,7 +753,7 @@ void dev_activate(struct net_device *dev)
 
 	need_watchdog = 0;
 	netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);
-	transition_one_qdisc(dev, &dev->rx_queue, NULL);
+	transition_one_qdisc(dev, &dev->ingress_queue, NULL);
 
 	if (need_watchdog) {
 		dev->trans_start = jiffies;
@@ -812,7 +812,7 @@ static bool some_qdisc_is_busy(struct net_device *dev)
 void dev_deactivate(struct net_device *dev)
 {
 	netdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);
-	dev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);
+	dev_deactivate_queue(dev, &dev->ingress_queue, &noop_qdisc);
 
 	dev_watchdog_down(dev);
 
@@ -838,7 +838,7 @@ void dev_init_scheduler(struct net_device *dev)
 {
 	dev->qdisc = &noop_qdisc;
 	netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
-	dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
+	dev_init_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
 
 	setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
 }
@@ -861,7 +861,7 @@ static void shutdown_scheduler_queue(struct net_device *dev,
 void dev_shutdown(struct net_device *dev)
 {
 	netdev_for_each_tx_queue(dev, shutdown_scheduler_queue, &noop_qdisc);
-	shutdown_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
+	shutdown_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
 	qdisc_destroy(dev->qdisc);
 	dev->qdisc = &noop_qdisc;
 



^ permalink raw reply related

* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Eric Dumazet @ 2010-09-28 16:33 UTC (permalink / raw)
  To: nicolas.dichtel; +Cc: netdev, Octavian Purdila
In-Reply-To: <4CA208C9.1020800@6wind.com>

Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> Hi,
> 
> I face a problem when I try to remove an interface, 
> netdev_wait_allrefs() complains about refcount.
> 
> Here is a trivial scenario to reproduce the problem:
> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> # ./a.out tunl1
> # ip tunnel del tunl1
> 
> Note: a.out binary create an IPv4 raw socket, attach it to tunl1 
> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the 
> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header 
> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
> 
> Note2: when a.out is executed, tunl1 has no ip address and is down.
> 

CC Octavian Purdila, the patch author.

I am just wondering why this route is created in the first place.

Maybe a fix would be to forbid this ?

Some machines have a giant route cache, so its very important to avoid
expensive scans.

> Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice: 
> waiting for tunl1 to become free. Usage count = 3" and after some time, 
> interface is removed.
> 
> The problem is that route cache entries are only invalidate on 
> UNREGISTER event, and not removed (introduced by commit 
> e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that 
> rt_check_expire() remove the remaining route cache entries.
> 
> To fix the problem, I propose to remove a part of the previous commit.
> 
> Regards,
> Nicolas
> pièce jointe différences entre fichiers
> (0001-ipv4-remove-all-rt-cache-entries-on-UNREGISTER-even.patch)
> From 3344e2e0431fe803c4dac8757a8746908357d780 Mon Sep 17 00:00:00 2001
> From: Nicolas Dichtel <nicolas.dichtel@6wind.com>
> Date: Tue, 28 Sep 2010 16:38:19 +0200
> Subject: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
> 
> Commit e2ce146848c81af2f6d42e67990191c284bf0c33 (ipv4: factorize cache clearing
> for batched unregister operations) add a new parameter to fib_disable_ip() to
> only invalidate route cache entries on unregister event.
> This is wrong, we should ensure that all cache entries are removed on
> unregister event, else netdev_wait_allrefs() may complain. A cache entry
> can be created between event DOWN and UNREGISTER.
> 
> So, I revert a part of the patch.
> 
> Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
> ---
>  net/ipv4/fib_frontend.c |   10 +++++-----
>  1 files changed, 5 insertions(+), 5 deletions(-)
> 
> diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c
> index 7d02a9f..377e815 100644
> --- a/net/ipv4/fib_frontend.c
> +++ b/net/ipv4/fib_frontend.c
> @@ -917,11 +917,11 @@ static void nl_fib_lookup_exit(struct net *net)
>  	net->ipv4.fibnl = NULL;
>  }
>  
> -static void fib_disable_ip(struct net_device *dev, int force, int delay)
> +static void fib_disable_ip(struct net_device *dev, int force)
>  {
>  	if (fib_sync_down_dev(dev, force))
>  		fib_flush(dev_net(dev));
> -	rt_cache_flush(dev_net(dev), delay);
> +	rt_cache_flush(dev_net(dev), 0);
>  	arp_ifdown(dev);
>  }
>  
> @@ -944,7 +944,7 @@ static int fib_inetaddr_event(struct notifier_block *this, unsigned long event,
>  			/* Last address was deleted from this interface.
>  			   Disable IP.
>  			 */
> -			fib_disable_ip(dev, 1, 0);
> +			fib_disable_ip(dev, 1);
>  		} else {
>  			rt_cache_flush(dev_net(dev), -1);
>  		}
> @@ -959,7 +959,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>  	struct in_device *in_dev = __in_dev_get_rtnl(dev);
>  
>  	if (event == NETDEV_UNREGISTER) {
> -		fib_disable_ip(dev, 2, -1);
> +		fib_disable_ip(dev, 2);
>  		return NOTIFY_DONE;
>  	}
>  
> @@ -977,7 +977,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>  		rt_cache_flush(dev_net(dev), -1);
>  		break;
>  	case NETDEV_DOWN:
> -		fib_disable_ip(dev, 0, 0);
> +		fib_disable_ip(dev, 0);
>  		break;
>  	case NETDEV_CHANGEMTU:
>  	case NETDEV_CHANGE:



^ permalink raw reply

* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Nicolas Dichtel @ 2010-09-28 16:45 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: netdev, Octavian Purdila
In-Reply-To: <1285691629.3154.80.camel@edumazet-laptop>

Eric Dumazet wrote:
> Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
>> Hi,
>>
>> I face a problem when I try to remove an interface, 
>> netdev_wait_allrefs() complains about refcount.
>>
>> Here is a trivial scenario to reproduce the problem:
>> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
>> # ./a.out tunl1
>> # ip tunnel del tunl1
>>
>> Note: a.out binary create an IPv4 raw socket, attach it to tunl1 
>> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the 
>> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header 
>> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
>>
>> Note2: when a.out is executed, tunl1 has no ip address and is down.
>>
> 
> CC Octavian Purdila, the patch author.
> 
> I am just wondering why this route is created in the first place.
At first, I asked myself the same question, but it seems that this is 
allowed to send a packet through this kind of socket, even if interface 
is down. Packet will be destroyed by the noop qdisk.
But I agree that it is strange to perform route lookup and everything to 
   destroy the packet at the end ...
Maybe raw_sendmsg() can delete it directly ;-) ... or maybe 
ip_route_output_flow().

Any suggestions welcome.

Regards,
Nicolas

> 
> Maybe a fix would be to forbid this ?
> 
> Some machines have a giant route cache, so its very important to avoid
> expensive scans.
> 
>> Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice: 
>> waiting for tunl1 to become free. Usage count = 3" and after some time, 
>> interface is removed.
>>
>> The problem is that route cache entries are only invalidate on 
>> UNREGISTER event, and not removed (introduced by commit 
>> e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that 
>> rt_check_expire() remove the remaining route cache entries.
>>
>> To fix the problem, I propose to remove a part of the previous commit.
>>
>> Regards,
>> Nicolas
>> pièce jointe différences entre fichiers
>> (0001-ipv4-remove-all-rt-cache-entries-on-UNREGISTER-even.patch)
>> From 3344e2e0431fe803c4dac8757a8746908357d780 Mon Sep 17 00:00:00 2001
>> From: Nicolas Dichtel <nicolas.dichtel@6wind.com>
>> Date: Tue, 28 Sep 2010 16:38:19 +0200
>> Subject: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
>>
>> Commit e2ce146848c81af2f6d42e67990191c284bf0c33 (ipv4: factorize cache clearing
>> for batched unregister operations) add a new parameter to fib_disable_ip() to
>> only invalidate route cache entries on unregister event.
>> This is wrong, we should ensure that all cache entries are removed on
>> unregister event, else netdev_wait_allrefs() may complain. A cache entry
>> can be created between event DOWN and UNREGISTER.
>>
>> So, I revert a part of the patch.
>>
>> Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
>> ---
>>  net/ipv4/fib_frontend.c |   10 +++++-----
>>  1 files changed, 5 insertions(+), 5 deletions(-)
>>
>> diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c
>> index 7d02a9f..377e815 100644
>> --- a/net/ipv4/fib_frontend.c
>> +++ b/net/ipv4/fib_frontend.c
>> @@ -917,11 +917,11 @@ static void nl_fib_lookup_exit(struct net *net)
>>  	net->ipv4.fibnl = NULL;
>>  }
>>  
>> -static void fib_disable_ip(struct net_device *dev, int force, int delay)
>> +static void fib_disable_ip(struct net_device *dev, int force)
>>  {
>>  	if (fib_sync_down_dev(dev, force))
>>  		fib_flush(dev_net(dev));
>> -	rt_cache_flush(dev_net(dev), delay);
>> +	rt_cache_flush(dev_net(dev), 0);
>>  	arp_ifdown(dev);
>>  }
>>  
>> @@ -944,7 +944,7 @@ static int fib_inetaddr_event(struct notifier_block *this, unsigned long event,
>>  			/* Last address was deleted from this interface.
>>  			   Disable IP.
>>  			 */
>> -			fib_disable_ip(dev, 1, 0);
>> +			fib_disable_ip(dev, 1);
>>  		} else {
>>  			rt_cache_flush(dev_net(dev), -1);
>>  		}
>> @@ -959,7 +959,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>>  	struct in_device *in_dev = __in_dev_get_rtnl(dev);
>>  
>>  	if (event == NETDEV_UNREGISTER) {
>> -		fib_disable_ip(dev, 2, -1);
>> +		fib_disable_ip(dev, 2);
>>  		return NOTIFY_DONE;
>>  	}
>>  
>> @@ -977,7 +977,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>>  		rt_cache_flush(dev_net(dev), -1);
>>  		break;
>>  	case NETDEV_DOWN:
>> -		fib_disable_ip(dev, 0, 0);
>> +		fib_disable_ip(dev, 0);
>>  		break;
>>  	case NETDEV_CHANGEMTU:
>>  	case NETDEV_CHANGE:
> 
> 

^ permalink raw reply

* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Eric Dumazet @ 2010-09-28 16:56 UTC (permalink / raw)
  To: nicolas.dichtel; +Cc: netdev, Octavian Purdila
In-Reply-To: <4CA21BC6.5070300@6wind.com>

Le mardi 28 septembre 2010 à 18:45 +0200, Nicolas Dichtel a écrit :
> Eric Dumazet wrote:
> > Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> >> Hi,
> >>
> >> I face a problem when I try to remove an interface, 
> >> netdev_wait_allrefs() complains about refcount.
> >>
> >> Here is a trivial scenario to reproduce the problem:
> >> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> >> # ./a.out tunl1
> >> # ip tunnel del tunl1
> >>
> >> Note: a.out binary create an IPv4 raw socket, attach it to tunl1 
> >> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the 
> >> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header 
> >> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
> >>
> >> Note2: when a.out is executed, tunl1 has no ip address and is down.
> >>
> > 
> > CC Octavian Purdila, the patch author.
> > 
> > I am just wondering why this route is created in the first place.
> At first, I asked myself the same question, but it seems that this is 
> allowed to send a packet through this kind of socket, even if interface 
> is down. Packet will be destroyed by the noop qdisk.
> But I agree that it is strange to perform route lookup and everything to 
>    destroy the packet at the end ...
> Maybe raw_sendmsg() can delete it directly ;-) ... or maybe 
> ip_route_output_flow().
> 
> Any suggestions welcome.
> 

Hmm...

One way to track this kind of problem would be to add a WARN_ON() in
dev_hold()

-> Check that when a reference on dev is taken, we are in a known state.

Something like this ?

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 83de0eb..54bef78 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1773,6 +1774,7 @@ static inline void dev_put(struct net_device *dev)
  */
 static inline void dev_hold(struct net_device *dev)
 {
+	WARN_ON(dev->reg_state != NETREG_REGISTERED);
 	atomic_inc(&dev->refcnt);
 }
 



^ permalink raw reply related

* [PATCH -next] pch_gbe: add header files
From: Randy Dunlap @ 2010-09-28 16:56 UTC (permalink / raw)
  To: Stephen Rothwell, netdev, davem; +Cc: linux-next, LKML, masa-korg
In-Reply-To: <20100928141823.8c61000e.sfr@canb.auug.org.au>

From: Randy Dunlap <randy.dunlap@oracle.com>

Fix build errors, more header files needed.

drivers/net/pch_gbe/pch_gbe_main.c:965: error: implicit declaration of function 'tcp_hdr'
drivers/net/pch_gbe/pch_gbe_main.c:965: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:968: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:976: error: implicit declaration of function 'udp_hdr'
drivers/net/pch_gbe/pch_gbe_main.c:976: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:980: error: invalid type argument of '->' (have 'int')

Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com>
---
 drivers/net/pch_gbe/pch_gbe.h |    2 ++
 1 file changed, 2 insertions(+)

--- linux-next-20100927.orig/drivers/net/pch_gbe/pch_gbe.h
+++ linux-next-20100927/drivers/net/pch_gbe/pch_gbe.h
@@ -31,6 +31,8 @@
 #include <linux/ethtool.h>
 #include <linux/vmalloc.h>
 #include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
 
 /**
  * pch_gbe_regs_mac_adr - Structure holding values of mac address registers

^ permalink raw reply

* [PATCH] Add Qualcomm Gobi 2000 driver.
From: Elly Jones @ 2010-09-28 17:10 UTC (permalink / raw)
  To: netdev; +Cc: dbrownell, mjg59, jglasgow, msb, olofj

From: Elizabeth Jones <ellyjones@google.com>

This driver is a rewrite of the original Qualcomm GPL driver, released as part
of Qualcomm's "Code Aurora" initiative. The driver has been transformed into
Linux kernel style and made to use kernel APIs where appropriate; some bugs have
also been fixed. Note that the device in question requires firmware and a
firmware loader; the latter has been written by mjg (see
http://www.codon.org.uk/~mjg59/gobi_loader/).

Signed-Off-By: Elizabeth Jones <ellyjones@google.com>
Signed-Off-By: Jason Glasgow <jglasgow@google.com>
---
 drivers/net/usb/Kconfig              |    6 +
 drivers/net/usb/Makefile             |    2 +-
 drivers/net/usb/qcusbnet/Makefile    |    2 +
 drivers/net/usb/qcusbnet/qcusbnet.c  |  706 +++++++++++++++
 drivers/net/usb/qcusbnet/qcusbnet.h  |   24 +
 drivers/net/usb/qcusbnet/qmi.c       |  358 ++++++++
 drivers/net/usb/qcusbnet/qmi.h       |   67 ++
 drivers/net/usb/qcusbnet/qmidevice.c | 1621 ++++++++++++++++++++++++++++++++++
 drivers/net/usb/qcusbnet/qmidevice.h |   36 +
 drivers/net/usb/qcusbnet/readme.txt  |  118 +++
 drivers/net/usb/qcusbnet/structs.h   |   99 ++
 11 files changed, 3038 insertions(+), 1 deletions(-)
 create mode 100755 drivers/net/usb/qcusbnet/Makefile
 create mode 100644 drivers/net/usb/qcusbnet/qcusbnet.c
 create mode 100644 drivers/net/usb/qcusbnet/qcusbnet.h
 create mode 100755 drivers/net/usb/qcusbnet/qmi.c
 create mode 100755 drivers/net/usb/qcusbnet/qmi.h
 create mode 100755 drivers/net/usb/qcusbnet/qmidevice.c
 create mode 100755 drivers/net/usb/qcusbnet/qmidevice.h
 create mode 100755 drivers/net/usb/qcusbnet/readme.txt
 create mode 100755 drivers/net/usb/qcusbnet/structs.h

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index d7b7018..8a210d9 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -406,4 +406,10 @@ config USB_SIERRA_NET
 	  To compile this driver as a module, choose M here: the
 	  module will be called sierra_net.
 
+config USB_NET_GOBI
+	tristate "Qualcomm Gobi"
+	depends on USB_USBNET
+	help
+	  Choose this option if you have a Qualcomm Gobi 2000 cellular modem.
+
 endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index b13a279..f3f702f 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -25,4 +25,4 @@ obj-$(CONFIG_USB_NET_INT51X1)	+= int51x1.o
 obj-$(CONFIG_USB_CDC_PHONET)	+= cdc-phonet.o
 obj-$(CONFIG_USB_IPHETH)	+= ipheth.o
 obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
-
+obj-$(CONFIG_USB_NET_GOBI)      += qcusbnet/
diff --git a/drivers/net/usb/qcusbnet/Makefile b/drivers/net/usb/qcusbnet/Makefile
new file mode 100755
index 0000000..a186c19
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_NET_GOBI) += qcusbnet2k.o
+qcusbnet2k-objs += qcusbnet.o qmidevice.o qmi.o
diff --git a/drivers/net/usb/qcusbnet/qcusbnet.c b/drivers/net/usb/qcusbnet/qcusbnet.c
new file mode 100644
index 0000000..a075b55
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qcusbnet.c
@@ -0,0 +1,706 @@
+/* qcusbnet.c - gobi network device
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "structs.h"
+#include "qmidevice.h"
+#include "qmi.h"
+#include "qcusbnet.h"
+
+#define DRIVER_VERSION "1.0.110"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "QCUSBNet2k"
+
+int debug;
+static struct class *devclass;
+
+int qc_suspend(struct usb_interface *iface, pm_message_t event)
+{
+	struct usbnet * usbnet;
+	struct qcusbnet * dev;
+	
+	if (!iface)
+		return -ENOMEM;
+	
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+	usbnet = usb_get_intfdata(iface);
+#else
+	usbnet = iface->dev.platform_data;
+#endif
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get netdevice\n");
+		return -ENXIO;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return -ENXIO;
+	}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+	if (!usbnet->udev->auto_pm)
+#else
+	if (!(event.event & PM_EVENT_AUTO))
+#endif
+	{
+		DBG("device suspended to power level %d\n", 
+			  event.event);
+		qc_setdown(dev, DOWN_DRIVER_SUSPENDED);
+	} else {
+		DBG("device autosuspend\n");
+	}
+	  
+	if (event.event & PM_EVENT_SUSPEND) {
+		qc_stopread(dev);
+		usbnet->udev->reset_resume = 0;
+		iface->dev.power.power_state.event = event.event;
+	} else {
+		usbnet->udev->reset_resume = 1;
+	}
+	
+	return usbnet_suspend(iface, event);
+}
+
+static int qc_resume(struct usb_interface * iface)
+{
+	struct usbnet *usbnet;
+	struct qcusbnet *dev;
+	int ret;
+	int oldstate;
+	
+	if (iface == 0)
+		return -ENOMEM;
+	
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+	usbnet = usb_get_intfdata(iface);
+#else
+	usbnet = iface->dev.platform_data;
+#endif
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get netdevice\n");
+		return -ENXIO;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return -ENXIO;
+	}
+
+	oldstate = iface->dev.power.power_state.event;
+	iface->dev.power.power_state.event = PM_EVENT_ON;
+	DBG("resuming from power mode %d\n", oldstate);
+
+	if (oldstate & PM_EVENT_SUSPEND) {
+		qc_cleardown(dev, DOWN_DRIVER_SUSPENDED);
+	
+		ret = usbnet_resume(iface);
+		if (ret) {
+			DBG("usbnet_resume error %d\n", ret);
+			return ret;
+		}
+
+		ret = qc_startread(dev);
+		if (ret) {
+			DBG("qc_startread error %d\n", ret);
+			return ret;
+		}
+
+		complete(&dev->worker.work);
+	} else {
+		DBG("nothing to resume\n");
+		return 0;
+	}
+	
+	return ret;
+}
+
+static int qcnet_bind(struct usbnet *usbnet, struct usb_interface *iface)
+{
+	int numends;
+	int i;
+	struct usb_host_endpoint *endpoint = NULL;
+	struct usb_host_endpoint *in = NULL;
+	struct usb_host_endpoint *out = NULL;
+	
+	if (iface->num_altsetting != 1) {
+		DBG("invalid num_altsetting %u\n", iface->num_altsetting);
+		return -EINVAL;
+	}
+
+	if (iface->cur_altsetting->desc.bInterfaceNumber != 0) {
+		DBG("invalid interface %d\n", 
+			  iface->cur_altsetting->desc.bInterfaceNumber);
+		return -EINVAL;
+	}
+	
+	numends = iface->cur_altsetting->desc.bNumEndpoints;
+	for (i = 0; i < numends; i++) {
+		endpoint = iface->cur_altsetting->endpoint + i;
+		if (!endpoint) {
+			DBG("invalid endpoint %u\n", i);
+			return -EINVAL;
+		}
+		
+		if (usb_endpoint_dir_in(&endpoint->desc)
+		&&  !usb_endpoint_xfer_int(&endpoint->desc)) {
+			in = endpoint;
+		} else if (!usb_endpoint_dir_out(&endpoint->desc)) {
+			out = endpoint;
+		}
+	}
+	
+	if (!in || !out) {
+		DBG("invalid endpoints\n");
+		return -EINVAL;
+	}
+
+	if (usb_set_interface(usbnet->udev,
+	                      iface->cur_altsetting->desc.bInterfaceNumber, 0))	{
+		DBG("unable to set interface\n");
+		return -EINVAL;
+	}
+
+	usbnet->in = usb_rcvbulkpipe(usbnet->udev, in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	usbnet->out = usb_sndbulkpipe(usbnet->udev, out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+	DBG("in %x, out %x\n",
+	    in->desc.bEndpointAddress, 
+	    out->desc.bEndpointAddress);
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23)
+	iface->dev.platform_data = usbnet;
+#endif
+	return 0;
+}
+
+static void qcnet_unbind(struct usbnet *usbnet, struct usb_interface *iface)
+{
+	struct qcusbnet *dev = (struct qcusbnet *)usbnet->data[0];
+
+	netif_carrier_off(usbnet->net);
+	qc_deregister(dev);
+	
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
+	kfree(usbnet->net->netdev_ops);
+	usbnet->net->netdev_ops = NULL;
+#endif
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23)
+	iface->dev.platform_data = NULL;
+#endif
+
+	kfree(dev);
+}
+
+static void qcnet_urbhook(struct urb * urb)
+{
+	unsigned long flags;
+	struct worker * worker = urb->context;
+	if (!worker) {
+		DBG("bad context\n");
+		return;
+	}
+
+	if (urb->status) {
+		DBG("urb finished with error %d\n", urb->status);
+	}
+
+	spin_lock_irqsave(&worker->active_lock, flags);
+	worker->active = ERR_PTR(-EAGAIN);
+	spin_unlock_irqrestore(&worker->active_lock, flags);
+	/* XXX-fix race against qcnet_stop()? */
+	complete(&worker->work);
+	usb_free_urb(urb);
+}
+
+static void qcnet_txtimeout(struct net_device *netdev)
+{
+	struct list_head *node, *tmp;
+	struct qcusbnet *dev;
+	struct worker *worker;
+	struct urbreq *req;
+	unsigned long activeflags, listflags;
+	struct usbnet *usbnet = netdev_priv(netdev);
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get usbnet device\n");
+		return;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return;
+	}
+	worker = &dev->worker;
+
+	DBG("\n");
+
+	spin_lock_irqsave(&worker->active_lock, activeflags);
+	if (worker->active)
+		usb_kill_urb(worker->active);
+	spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+	spin_lock_irqsave(&worker->urbs_lock, listflags);
+	list_for_each_safe(node, tmp, &worker->urbs) {
+		req = list_entry(node, struct urbreq, node);
+		usb_free_urb(req->urb);
+		list_del(&req->node);
+		kfree(req);
+	}
+	spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+	complete(&worker->work);
+}
+
+static int qcnet_worker(void *arg)
+{
+	struct list_head *node, *tmp;
+	unsigned long activeflags, listflags;
+	struct urbreq *req;
+	int status;
+	struct usb_device *usbdev;
+	struct worker *worker = arg;
+	if (!worker) {
+		DBG("passed null pointer\n");
+		return -EINVAL;
+	}
+	
+	usbdev = interface_to_usbdev(worker->iface);
+
+	DBG("traffic thread started\n");
+
+	while (!kthread_should_stop()) {
+		wait_for_completion_interruptible(&worker->work);
+
+		if (kthread_should_stop()) {
+			spin_lock_irqsave(&worker->active_lock, activeflags);
+			if (worker->active) {
+				usb_kill_urb(worker->active);
+			}
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+			spin_lock_irqsave(&worker->urbs_lock, listflags);
+			list_for_each_safe(node, tmp, &worker->urbs) {
+				req = list_entry(node, struct urbreq, node);
+				usb_free_urb(req->urb);
+				list_del(&req->node);
+				kfree(req);
+			}
+			spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+			break;
+		}
+		
+		spin_lock_irqsave(&worker->active_lock, activeflags);
+		if (IS_ERR(worker->active) && PTR_ERR(worker->active) == -EAGAIN) {
+			worker->active = NULL;
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+			usb_autopm_put_interface(worker->iface);
+			spin_lock_irqsave(&worker->active_lock, activeflags);
+		}
+
+		if (worker->active) {
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+			continue;
+		}
+		
+		spin_lock_irqsave(&worker->urbs_lock, listflags);
+		if (list_empty(&worker->urbs)) {
+			spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+			continue;
+		}
+
+		req = list_first_entry(&worker->urbs, struct urbreq, node);
+		list_del(&req->node);
+		spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+		worker->active = req->urb;
+		spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+		status = usb_autopm_get_interface(worker->iface);
+		if (status < 0) {
+			DBG("unable to autoresume interface: %d\n", status);
+			if (status == -EPERM)
+			{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+				usbdev->auto_pm = 0;
+#endif
+				qc_suspend(worker->iface, PMSG_SUSPEND);
+			}
+
+			spin_lock_irqsave(&worker->urbs_lock, listflags);
+			list_add(&req->node, &worker->urbs);
+			spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+			
+			spin_lock_irqsave(&worker->active_lock, activeflags);
+			worker->active = NULL;
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+			
+			continue;
+		}
+
+		status = usb_submit_urb(worker->active, GFP_KERNEL);
+		if (status < 0) {
+			DBG("Failed to submit URB: %d.  Packet dropped\n", status);
+			spin_lock_irqsave(&worker->active_lock, activeflags);
+			usb_free_urb(worker->active);
+			worker->active = NULL;
+			spin_unlock_irqrestore(&worker->active_lock, activeflags);
+			usb_autopm_put_interface(worker->iface);
+			complete(&worker->work);
+		}
+		
+		kfree(req);
+	}	
+	
+	DBG("traffic thread exiting\n");
+	worker->thread = NULL;
+	return 0;
+}
+
+static int qcnet_startxmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	unsigned long listflags;
+	struct qcusbnet *dev;
+	struct worker *worker;
+	struct urbreq *req;
+	void *data;
+	struct usbnet *usbnet = netdev_priv(netdev);
+	
+	DBG("\n");
+	
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get usbnet device\n");
+		return NETDEV_TX_BUSY;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return NETDEV_TX_BUSY;
+	}
+	worker = &dev->worker;
+	
+	if (qc_isdown(dev, DOWN_DRIVER_SUSPENDED)) {
+		DBG("device is suspended\n");
+		dump_stack();
+		return NETDEV_TX_BUSY;
+	}
+	
+	req = kmalloc(sizeof(*req), GFP_ATOMIC);
+	if (!req) {
+		DBG("unable to allocate URBList memory\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	req->urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+	if (!req->urb) {
+		kfree(req);
+		DBG("unable to allocate URB\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	data = kmalloc(skb->len, GFP_ATOMIC);
+	if (!data) {
+		usb_free_urb(req->urb);
+		kfree(req);
+		DBG("unable to allocate URB data\n");
+		return NETDEV_TX_BUSY;
+	}
+	memcpy(data, skb->data, skb->len);
+
+	usb_fill_bulk_urb(req->urb, dev->usbnet->udev, dev->usbnet->out,
+	                  data, skb->len, qcnet_urbhook, worker);
+	
+	spin_lock_irqsave(&worker->urbs_lock, listflags);
+	list_add_tail(&req->node, &worker->urbs);
+	spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+	complete(&worker->work);
+
+	netdev->trans_start = jiffies;
+	dev_kfree_skb_any(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static int qcnet_open(struct net_device *netdev)
+{
+	int status = 0;
+	struct qcusbnet *dev;
+	struct usbnet *usbnet = netdev_priv(netdev);
+	
+	if (!usbnet) {
+		DBG("failed to get usbnet device\n");
+		return -ENXIO;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return -ENXIO;
+	}
+
+	DBG("\n");
+
+	dev->worker.iface = dev->iface;
+	INIT_LIST_HEAD(&dev->worker.urbs);
+	dev->worker.active = NULL;
+	spin_lock_init(&dev->worker.urbs_lock);
+	spin_lock_init(&dev->worker.active_lock);
+	init_completion(&dev->worker.work);
+	
+	dev->worker.thread = kthread_run(qcnet_worker, &dev->worker, "qcnet_worker");
+	if (IS_ERR(dev->worker.thread))
+	{
+		DBG("AutoPM thread creation error\n");
+		return PTR_ERR(dev->worker.thread);
+	}
+
+	qc_cleardown(dev, DOWN_NET_IFACE_STOPPED);
+	if (dev->open)
+	{
+		status = dev->open(netdev);
+		if (status == 0)
+		{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+			usb_autopm_enable(dev->iface);
+#else
+			usb_autopm_put_interface(dev->iface);
+#endif
+		}
+	} else {
+		DBG("no USBNetOpen defined\n");
+	}
+	
+	return status;
+}
+
+int qcnet_stop(struct net_device *netdev)
+{
+	struct qcusbnet *dev;
+	struct usbnet *usbnet = netdev_priv(netdev);
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get netdevice\n");
+		return -ENXIO;
+	}
+	
+	dev = (struct qcusbnet *)usbnet->data[0];
+	if (!dev) {
+		DBG("failed to get QMIDevice\n");
+		return -ENXIO;
+	}
+
+	qc_setdown(dev, DOWN_NET_IFACE_STOPPED);
+	complete(&dev->worker.work);
+	kthread_stop(dev->worker.thread);
+	DBG("thread stopped\n");
+
+	if (dev->stop != NULL)
+		return dev->stop(netdev);
+	return 0;
+}
+
+static const struct driver_info qc_netinfo = 
+{
+   .description   = "QCUSBNet Ethernet Device",
+   .flags         = FLAG_ETHER,
+   .bind          = qcnet_bind,
+   .unbind        = qcnet_unbind,
+   .data          = 0,
+};
+
+#define MKVIDPID(v,p) \
+	{ \
+		USB_DEVICE(v,p), \
+		.driver_info = (unsigned long)&qc_netinfo, \
+	}
+
+static const struct usb_device_id qc_vidpids [] =
+{
+	MKVIDPID(0x05c6, 0x9215),	/* Acer Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9265),	/* Asus Gobi 2000 */
+	MKVIDPID(0x16d8, 0x8002),	/* CMOTech Gobi 2000 */
+	MKVIDPID(0x413c, 0x8186),	/* Dell Gobi 2000 */
+	MKVIDPID(0x1410, 0xa010),	/* Entourage Gobi 2000 */
+	MKVIDPID(0x1410, 0xa011),	/* Entourage Gobi 2000 */
+	MKVIDPID(0x1410, 0xa012),	/* Entourage Gobi 2000 */
+	MKVIDPID(0x1410, 0xa013),	/* Entourage Gobi 2000 */
+	MKVIDPID(0x03f0, 0x251d),	/* HP Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9205),	/* Lenovo Gobi 2000 */
+	MKVIDPID(0x05c6, 0x920b),	/* Generic Gobi 2000 */
+	MKVIDPID(0x04da, 0x250f),	/* Panasonic Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9245),	/* Samsung Gobi 2000 */
+	MKVIDPID(0x1199, 0x9001),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9002),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9003),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9004),	/* Sierra Wireless Gobi 2000 */ 
+	MKVIDPID(0x1199, 0x9005),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9006),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9007),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9008),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x9009),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x1199, 0x900a),	/* Sierra Wireless Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9225),	/* Sony Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9235),	/* Top Global Gobi 2000 */
+	MKVIDPID(0x05c6, 0x9275),	/* iRex Technologies Gobi 2000 */
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, qc_vidpids);
+
+int qcnet_probe(struct usb_interface *iface, const struct usb_device_id *vidpids)
+{
+	int status;
+	struct usbnet *usbnet;
+	struct qcusbnet *dev;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
+	struct net_device_ops *netdevops;
+#endif
+
+	status = usbnet_probe(iface, vidpids);
+	if (status < 0) {
+		DBG("usbnet_probe failed %d\n", status);
+		return status;
+	}
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+	usbnet = usb_get_intfdata(iface);
+#else
+	usbnet = iface->dev.platform_data;
+#endif
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get netdevice\n");
+		return -ENXIO;
+	}
+
+	dev = kmalloc(sizeof(struct qcusbnet), GFP_KERNEL);
+	if (!dev) {
+		DBG("falied to allocate device buffers");
+		return -ENOMEM;
+	}
+	
+	usbnet->data[0] = (unsigned long)dev;
+	
+	dev->usbnet = usbnet;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+	dev->open = usbnet->net->open;
+	usbnet->net->open = qcnet_open;
+	dev->stop = usbnet->net->stop;
+	usbnet->net->stop = qcnet_stop;
+	usbnet->net->hard_start_xmit = qcnet_startxmit;
+	usbnet->net->tx_timeout = qcnet_txtimeout;
+#else
+	netdevops = kmalloc(sizeof(struct net_device_ops), GFP_KERNEL);
+	if (!netdevops) {
+		DBG("falied to allocate net device ops");
+		return -ENOMEM;
+	}
+	memcpy(netdevops, usbnet->net->netdev_ops, sizeof(struct net_device_ops));
+	
+	dev->open = netdevops->ndo_open;
+	netdevops->ndo_open = qcnet_open;
+	dev->stop = netdevops->ndo_stop;
+	netdevops->ndo_stop = qcnet_stop;
+	netdevops->ndo_start_xmit = qcnet_startxmit;
+	netdevops->ndo_tx_timeout = qcnet_txtimeout;
+
+	usbnet->net->netdev_ops = netdevops;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+	memset(&(dev->usbnet->stats), 0, sizeof(struct net_device_stats));
+#else
+	memset(&(dev->usbnet->net->stats), 0, sizeof(struct net_device_stats));
+#endif
+
+	dev->iface = iface;
+	memset(&(dev->meid), '0', 14);
+	
+	DBG("Mac Address:\n");
+	printhex(&dev->usbnet->net->dev_addr[0], 6);
+
+	dev->valid = false;
+	memset(&dev->qmi, 0, sizeof(struct qmidev));
+
+	dev->qmi.devclass = devclass;
+	
+	INIT_LIST_HEAD(&dev->qmi.clients);
+	init_completion(&dev->worker.work);
+	spin_lock_init(&dev->qmi.clients_lock);
+
+	dev->down = 0;
+	qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+	qc_setdown(dev, DOWN_NET_IFACE_STOPPED);
+
+	status = qc_register(dev);
+	if (status) {
+		qc_deregister(dev);
+	}
+	
+	return status;
+}
+
+EXPORT_SYMBOL_GPL(qcnet_probe);
+
+static struct usb_driver qcusbnet =
+{
+	.name       = "QCUSBNet2k",
+	.id_table   = qc_vidpids,
+	.probe      = qcnet_probe,
+	.disconnect = usbnet_disconnect,
+	.suspend    = qc_suspend,
+	.resume     = qc_resume,
+	.supports_autosuspend = true,
+};
+
+static int __init modinit(void)
+{
+	devclass = class_create(THIS_MODULE, "QCQMI");
+	if (IS_ERR(devclass)) {
+		DBG("error at class_create %ld\n", PTR_ERR(devclass));
+		return -ENOMEM;
+	}
+	printk(KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION);
+	return usb_register(&qcusbnet);
+}
+module_init(modinit);
+
+static void __exit modexit(void)
+{
+	usb_deregister(&qcusbnet);
+	class_destroy(devclass);
+}
+module_exit(modexit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("Dual BSD/GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debuging enabled or not");
diff --git a/drivers/net/usb/qcusbnet/qcusbnet.h b/drivers/net/usb/qcusbnet/qcusbnet.h
new file mode 100644
index 0000000..2f20868
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qcusbnet.h
@@ -0,0 +1,24 @@
+/* qcusbnet.h - gobi network device header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QCUSBNET_H
+#define QCUSBNET_QCUSBNET_H
+
+extern int qc_suspend(struct usb_interface *iface, pm_message_t event);
+
+#endif /* !QCUSBNET_QCUSBNET_H */
diff --git a/drivers/net/usb/qcusbnet/qmi.c b/drivers/net/usb/qcusbnet/qmi.c
new file mode 100755
index 0000000..2fc8ce8
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmi.c
@@ -0,0 +1,358 @@
+/* qmi.c - QMI protocol implementation
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "qmi.h"
+
+#include <linux/slab.h>
+
+struct qmux {
+	u8 tf;	/* always 1 */
+	u16 len;
+	u8 ctrl;
+	u8 service;
+	u8 qmicid;
+} __attribute__((__packed__));
+
+struct getcid_req {
+	struct qmux header;
+	u8 req;
+	u8 tid;
+	u16 msgid;
+	u16 tlvsize;
+	u8 service;
+	u16 size;
+	u8 qmisvc;
+} __attribute__((__packed__));
+
+struct releasecid_req {
+	struct qmux header;
+	u8 req;
+	u8 tid;
+	u16 msgid;
+	u16 tlvsize;
+	u8 rlscid;
+	u16 size;
+	u16 cid;
+} __attribute__((__packed__));
+
+struct ready_req {
+	struct qmux header;
+	u8 req;
+	u8 tid;
+	u16 msgid;
+	u16 tlvsize;
+} __attribute__((__packed__));
+
+struct seteventreport_req {
+	struct qmux header;
+	u8 req;
+	u16 tid;
+	u16 msgid;
+	u16 tlvsize;
+	u8 reportchanrate;
+	u16 size;
+	u8 period;
+	u32 mask;
+} __attribute__((__packed__));
+
+struct getpkgsrvcstatus_req {
+	struct qmux header;
+	u8 req;
+	u16 tid;
+	u16 msgid;
+	u16 tlvsize;
+} __attribute__((__packed__));
+
+struct getmeid_req {
+	struct qmux header;
+	u8 req;
+	u16 tid;
+	u16 msgid;
+	u16 tlvsize;
+} __attribute__((__packed__));
+
+const size_t qmux_size = sizeof(struct qmux);
+
+void *qmictl_new_getcid(u8 tid, u8 svctype, size_t *size)
+{
+	struct getcid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return NULL;
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x0022;
+	req->tlvsize = 0x0004;
+	req->service = 0x01;
+	req->size = 0x0001;
+	req->qmisvc = svctype;
+	*size = sizeof(*req);
+	return req;
+}
+
+void *qmictl_new_releasecid(u8 tid, u16 cid, size_t *size)
+{
+	struct releasecid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return NULL;
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x0023;
+	req->tlvsize = 0x05;
+	req->rlscid = 0x01;
+	req->size = 0x0002;
+	req->cid = cid;
+	*size = sizeof(*req);
+	return req;
+}
+
+void *qmictl_new_ready(u8 tid, size_t *size)
+{
+	struct ready_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return NULL;
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x21;
+	req->tlvsize = 0;
+	*size = sizeof(*req);
+	return req;
+}
+
+void *qmiwds_new_seteventreport(u8 tid, size_t *size)
+{
+	struct seteventreport_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x0001;
+	req->tlvsize = 0x0008;
+	req->reportchanrate = 0x11;
+	req->size = 0x0005;
+	req->period = 0x01;
+	req->mask = 0x000000ff;
+	*size = sizeof(*req);
+	return req;
+}
+
+void *qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size)
+{
+	struct getpkgsrvcstatus_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return NULL;
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x22;
+	req->tlvsize = 0x0000;
+	*size = sizeof(*req);
+	return req;
+}
+
+void *qmidms_new_getmeid(u8 tid, size_t *size)
+{
+	struct getmeid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return NULL;
+	req->req = 0x00;
+	req->tid = tid;
+	req->msgid = 0x25;
+	req->tlvsize = 0x0000;
+	*size = sizeof(*req);
+	return req;
+}
+
+int qmux_parse(u16 *cid, void *buf, size_t size)
+{
+	struct qmux *qmux = buf;
+   
+	if (!buf || size < 12)
+		return -ENOMEM;
+
+	if (qmux->tf != 1 || qmux->len != size - 1 || qmux->ctrl != 0x80)
+		return -EINVAL;
+
+	*cid = (qmux->qmicid << 8) + qmux->service;
+	return sizeof(*qmux);
+}
+
+int qmux_fill(u16 cid, void *buf, size_t size)
+{
+	struct qmux *qmux = buf;
+
+	if (!buf || size < sizeof(*qmux))
+		return -ENOMEM;
+
+	qmux->tf = 1;
+	qmux->len = size - 1;
+	qmux->ctrl = 0;
+	qmux->service = cid & 0xff;
+	qmux->qmicid = cid >> 8;
+	return 0;
+}
+
+static u16 tlv_get(void *msg, u16 msgsize, u8 type, void *buf, u16 bufsize) {
+	u16 pos;
+	u16 msize = 0;
+	
+	if (!msg || !buf)
+		return -ENOMEM;
+	
+	for (pos = 4;  pos + 3 < msgsize; pos += msize + 3) {
+		msize = *(u16 *)(msg + pos + 1);
+		if (*(u8 *)(msg + pos) == type) {
+			if (bufsize < msize)
+				return -ENOMEM;
+
+			memcpy(buf, msg + pos + 3, msize);
+			return msize;
+		}
+	}
+	
+	return -ENOMSG;
+}
+
+int qmi_msgisvalid(void *msg, u16 size)
+{
+	char tlv[4];
+
+	if (tlv_get(msg, size, 2, &tlv[0], 4) == 4) {
+		if (*(u16 *)&tlv[0] != 0)
+			return *(u16 *)&tlv[2];
+		else
+			return 0;
+	}
+	return -ENOMSG;
+}
+
+int qmi_msgid(void *msg, u16 size)
+{
+	return size < 2 ? -ENODATA : *(u16 *)msg;
+}
+
+int qmictl_alloccid_resp(void *buf, u16 size, u16 *cid)
+{
+	int result;
+	u8 offset = sizeof(struct qmux) + 2;
+
+	if (!buf || size < offset)
+		return -ENOMEM;
+
+	buf = buf + offset;
+	size -= offset;
+
+	result = qmi_msgid(buf, size);
+	if (result != 0x22)
+		return -EFAULT;
+
+	result = qmi_msgisvalid(buf, size);
+	if (result != 0)
+		return -EFAULT;
+
+	result = tlv_get(buf, size, 0x01, cid, 2);
+	if (result != 2)
+		return -EFAULT;
+
+	return 0;
+}
+
+int qmictl_freecid_resp(void *buf, u16 size)
+{
+	int result;
+	u8 offset = sizeof(struct qmux) + 2;
+
+	if (!buf || size < offset)
+		return -ENOMEM;
+
+	buf = buf + offset;
+	size -= offset;
+
+	result = qmi_msgid(buf, size);
+	if (result != 0x23)
+		return -EFAULT;
+
+	result = qmi_msgisvalid(buf, size);
+	if (result != 0)
+		return -EFAULT;
+
+	return 0;
+}
+
+int qmiwds_event_resp(void *buf, u16 size, struct qmiwds_stats *stats)
+{
+	int result;
+	u8 status[2];
+
+	u8 offset = sizeof(struct qmux) + 3;
+
+	if (!buf || size < offset || !stats)
+		return -ENOMEM;
+
+	buf = buf + offset;
+	size -= offset;
+
+	result = qmi_msgid(buf, size);
+	if (result == 0x01) {
+		tlv_get(buf, size, 0x10, (void*)&stats->txok, 4);
+		tlv_get(buf, size, 0x11, (void*)&stats->rxok, 4);
+		tlv_get(buf, size, 0x12, (void*)&stats->txerr, 4);
+		tlv_get(buf, size, 0x13, (void*)&stats->rxerr, 4);
+		tlv_get(buf, size, 0x14, (void*)&stats->txofl, 4);
+		tlv_get(buf, size, 0x15, (void*)&stats->rxofl, 4);
+		tlv_get(buf, size, 0x19, (void*)&stats->txbytesok, 8);
+		tlv_get(buf, size, 0x1A, (void*)&stats->rxbytesok, 8);
+	} else if (result == 0x22) {
+		result = tlv_get(buf, size, 0x01, &status[0], 2);
+		if (result >= 1)
+			stats->linkstate = status[0] == 0x02;
+		if (result == 2)
+			stats->reconfigure = status[1] == 0x01;
+		
+		if (result < 0)
+			return result;
+	} else {
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int qmidms_meid_resp(void *buf,	u16 size, char *meid, int meidsize)
+{
+	int result;
+
+	u8 offset = sizeof(struct qmux) + 3;
+
+	if (!buf || size < offset || meidsize < 14)
+		return -ENOMEM;
+
+	buf = buf + offset;
+	size -= offset;
+
+	result = qmi_msgid(buf, size);
+	if (result != 0x25)
+		return -EFAULT;
+
+	result = qmi_msgisvalid(buf, size);
+	if (result)
+		return -EFAULT;
+
+	result = tlv_get(buf, size, 0x12, (void*)meid, 14);
+	if (result != 14)
+		return -EFAULT;
+
+	return 0;
+}
+
diff --git a/drivers/net/usb/qcusbnet/qmi.h b/drivers/net/usb/qcusbnet/qmi.h
new file mode 100755
index 0000000..7954790
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmi.h
@@ -0,0 +1,67 @@
+/* qmi.h - QMI protocol header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QMI_H
+#define QCUSBNET_QMI_H
+
+#include <linux/types.h>
+
+#define QMICTL 0
+#define QMIWDS 1
+#define QMIDMS 2
+
+#define true      1
+#define false     0
+
+#define ENOMEM    12
+#define EFAULT    14
+#define EINVAL    22
+#define ENOMSG    42
+#define ENODATA   61
+
+int qmux_parse(u16 *cid, void *buf, size_t size);
+int qmux_fill(u16 cid, void *buf, size_t size);
+
+extern const size_t qmux_size;
+
+void *qmictl_new_getcid(u8 tid, u8 svctype, size_t *size);
+void *qmictl_new_releasecid(u8 tid, u16 cid, size_t *size);
+void *qmictl_new_ready(u8 tid, size_t *size);
+void *qmiwds_new_seteventreport(u8 tid, size_t *size);
+void *qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size);
+void *qmidms_new_getmeid(u8 tid, size_t *size);
+
+struct qmiwds_stats {
+	u32 txok;
+	u32 rxok;
+	u32 txerr;
+	u32 rxerr;
+	u32 txofl;
+	u32 rxofl;
+	u64 txbytesok;
+	u64 rxbytesok;
+	bool linkstate;
+	bool reconfigure;
+};
+
+int qmictl_alloccid_resp(void *buf, u16 size, u16 *cid);
+int qmictl_freecid_resp(void *buf, u16 size);
+int qmiwds_event_resp(void *buf, u16 size, struct qmiwds_stats *stats);
+int qmidms_meid_resp(void *buf, u16 size, char *meid, int meidsize);
+
+#endif /* !QCUSBNET_QMI_H */
diff --git a/drivers/net/usb/qcusbnet/qmidevice.c b/drivers/net/usb/qcusbnet/qmidevice.c
new file mode 100755
index 0000000..b5006be
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmidevice.c
@@ -0,0 +1,1621 @@
+/* qmidevice.c - gobi QMI device
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "qmidevice.h"
+#include "qcusbnet.h"
+
+struct readreq {
+	struct list_head node;
+	void *data;
+	u16 tid;
+	u16 size;
+};
+
+struct notifyreq {
+	struct list_head node;
+	void (* func)(struct qcusbnet *, u16, void *);
+	u16  tid;
+	void *data;
+};
+
+struct client {
+	struct list_head node;
+	u16 cid;
+	struct list_head reads;
+	struct list_head notifies;
+	struct list_head urbs;
+};
+
+struct urbsetup {
+	u8 type;
+	u8 code;
+	u16 value;
+	u16 index;
+	u16 len;
+};
+
+struct qmihandle {
+	u16 cid;
+	struct qcusbnet *dev;
+};
+
+extern int debug;
+static int qcusbnet2k_fwdelay = 0;
+
+static bool device_valid(struct qcusbnet *dev);
+static struct client *client_bycid(struct qcusbnet *dev, u16 cid);
+static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data, u16 size);
+static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data, u16 *size);
+static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid,
+                             void (*hook)(struct qcusbnet *, u16 cid, void *),
+                             void *data);
+static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid);
+static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb);
+static struct urb *client_delurb(struct qcusbnet *dev, u16 cid);
+
+static int devqmi_open(struct inode *inode, struct file *file);
+static long devqmi_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static int devqmi_close(struct file *file, fl_owner_t ftable);
+static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size,
+                           loff_t *pos);
+static ssize_t devqmi_write(struct file *file, const char __user *buf,
+                            size_t size, loff_t *pos);
+
+static bool qmi_ready(struct qcusbnet *dev, u16 timeout);
+static void wds_callback(struct qcusbnet *dev, u16 cid, void *data);
+static int setup_wds_callback(struct qcusbnet *dev);
+static int qmidms_getmeid(struct qcusbnet *dev);
+
+#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
+#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
+#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
+#define CDC_GET_ENCAPSULATED_RESPONSE 0x01A1ll
+#define CDC_CONNECTION_SPEED_CHANGE 0x08000000002AA1ll
+
+struct file_operations devqmi_fops = 
+{
+	.owner = THIS_MODULE,
+	.read  = devqmi_read,
+	.write = devqmi_write,
+	.unlocked_ioctl = devqmi_ioctl,
+	.open  = devqmi_open,
+	.flush = devqmi_close,
+};
+
+#ifdef CONFIG_SMP
+static inline void assert_locked(struct qcusbnet *dev)
+{
+	BUG_ON(!spin_is_locked(&dev->qmi.clients_lock));
+}
+#else
+static inline void assert_locked(struct qcusbnet *dev)
+{
+
+}
+#endif
+
+static bool device_valid(struct qcusbnet *dev)
+{
+	return dev && dev->valid;
+} 
+
+void printhex(const void *data, size_t size)
+{
+	const u8 *cdata = data;
+	char *buf;
+	size_t pos;
+
+	buf = kmalloc(size * 3 + 1, GFP_ATOMIC);
+	if (!buf) {
+		DBG("Unable to allocate buffer\n");
+		return;
+	}
+
+	memset(buf, 0 , size * 3 + 1);
+
+	for (pos = 0; pos < size; pos++) {
+		snprintf(buf + (pos * 3), 4, "%02X ", cdata[pos]);
+	}
+
+	DBG( "   : %s\n", buf);
+
+	kfree(buf);
+}
+
+void qc_setdown(struct qcusbnet *dev, u8 reason)
+{
+	set_bit(reason, &dev->down);
+	netif_carrier_off(dev->usbnet->net);
+}
+
+void qc_cleardown(struct qcusbnet *dev, u8 reason)
+{
+	clear_bit(reason, &dev->down);
+	if (!dev->down)
+		netif_carrier_on(dev->usbnet->net);
+}
+
+bool qc_isdown(struct qcusbnet *dev, u8 reason)
+{
+	return test_bit(reason, &dev->down);
+}
+
+static void read_callback(struct urb *urb)
+{
+	struct list_head *node;
+	int result;
+	u16 cid;
+	struct client *client;
+	void *data;
+	void *copy;
+	u16 size;
+	struct qcusbnet *dev;
+	unsigned long flags;
+	u16 tid;
+
+	if (!urb) {
+		DBG("bad read URB\n");
+		return;
+	}
+
+	dev = urb->context;
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return;
+	}
+
+	if (urb->status) {
+		DBG("Read status = %d\n", urb->status);
+		return;
+	}
+
+	DBG("Read %d bytes\n", urb->actual_length);
+
+	data = urb->transfer_buffer;
+	size = urb->actual_length;
+
+	printhex(data, size);
+
+	result = qmux_parse(&cid, data, size);
+	if (result < 0) {
+		DBG("Read error parsing QMUX %d\n", result);
+		return;
+	}
+
+	if (size < result + 3) {
+		DBG("Data buffer too small to parse\n");
+		return;
+	}
+
+	if (cid == QMICTL)
+		tid = *(u8*)(data + result + 1);
+	else
+		tid = *(u16*)(data + result + 1);
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+	list_for_each(node, &dev->qmi.clients) {
+		client = list_entry(node, struct client, node);
+		if (client->cid == cid || (client->cid | 0xff00) == cid) {
+			copy = kmalloc(size, GFP_ATOMIC);
+			memcpy(copy, data, size);
+			if (!client_addread(dev, client->cid, tid, copy, size)) {
+				DBG("Error allocating pReadMemListEntry "
+					  "read will be discarded\n");
+				kfree(copy);
+				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+				return;
+			}
+
+			DBG("Creating new readListEntry for client 0x%04X, TID %x\n",
+			    cid, tid);
+
+			client_notify(dev, client->cid, tid);
+
+			if (cid >> 8 != 0xff)
+				break;
+		}
+	}
+	
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+}
+
+static void int_callback(struct urb *urb)
+{
+	int status;
+	int interval;
+	struct qcusbnet *dev = (struct qcusbnet *)urb->context;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return;
+	}
+
+	if (urb->status) {
+		DBG("Int status = %d\n", urb->status);
+		if (urb->status != -EOVERFLOW)
+			return;
+	} else {
+		if ((urb->actual_length == 8)
+		&&  (*(u64*)urb->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) {
+			usb_fill_control_urb(dev->qmi.readurb, dev->usbnet->udev,
+			                     usb_rcvctrlpipe(dev->usbnet->udev, 0),
+			                     (unsigned char *)dev->qmi.readsetup,
+			                     dev->qmi.readbuf,
+			                     DEFAULT_READ_URB_LENGTH,
+			                     read_callback, dev);
+			status = usb_submit_urb(dev->qmi.readurb, GFP_ATOMIC);
+			if (status) {
+				DBG("Error submitting Read URB %d\n", status);
+				return;
+			}
+		} else if ((urb->actual_length == 16)
+		&& (*(u64*)urb->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) {
+			/* if upstream or downstream is 0, stop traffic.
+			 * Otherwise resume it */
+			if ((*(u32*)(urb->transfer_buffer + 8) == 0)
+			||  (*(u32*)(urb->transfer_buffer + 12) == 0)) {
+				qc_setdown(dev, DOWN_CDC_CONNECTION_SPEED);
+				DBG("traffic stopping due to CONNECTION_SPEED_CHANGE\n");
+			} else {
+				qc_cleardown(dev, DOWN_CDC_CONNECTION_SPEED);
+				DBG("resuming traffic due to CONNECTION_SPEED_CHANGE\n");
+			}
+		} else {
+			DBG("ignoring invalid interrupt in packet\n");
+			printhex(urb->transfer_buffer, urb->actual_length);
+		}
+	}
+
+	interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
+
+	usb_fill_int_urb(urb, urb->dev,	urb->pipe, urb->transfer_buffer,
+	                 urb->transfer_buffer_length, urb->complete,
+	                 urb->context, interval);
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status)
+		DBG("Error re-submitting Int URB %d\n", status);
+	return;
+}
+
+int qc_startread(struct qcusbnet *dev)
+{
+	int interval;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+	
+	dev->qmi.readurb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->qmi.readurb) {
+		DBG("Error allocating read urb\n");
+		return -ENOMEM;
+	}
+	
+	dev->qmi.inturb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->qmi.inturb) {
+		usb_free_urb(dev->qmi.readurb);
+		DBG("Error allocating int urb\n");
+		return -ENOMEM;
+	}
+
+	dev->qmi.readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+	if (!dev->qmi.readbuf) {
+		usb_free_urb(dev->qmi.readurb);
+		usb_free_urb(dev->qmi.inturb);
+		DBG("Error allocating read buffer\n");
+		return -ENOMEM;
+	}
+	
+	dev->qmi.intbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+	if (!dev->qmi.intbuf) {
+		usb_free_urb(dev->qmi.readurb);
+		usb_free_urb(dev->qmi.inturb);
+		kfree(dev->qmi.readbuf);
+		DBG("Error allocating int buffer\n");
+		return -ENOMEM;
+	}		
+	
+	dev->qmi.readsetup = kmalloc(sizeof(*dev->qmi.readsetup), GFP_KERNEL);
+	if (!dev->qmi.readsetup) {
+		usb_free_urb(dev->qmi.readurb);
+		usb_free_urb(dev->qmi.inturb);
+		kfree(dev->qmi.readbuf);
+		kfree(dev->qmi.intbuf);
+		DBG("Error allocating setup packet buffer\n");
+		return -ENOMEM;
+	}
+
+	dev->qmi.readsetup->type = 0xA1;
+	dev->qmi.readsetup->code = 1;
+	dev->qmi.readsetup->value = 0;
+	dev->qmi.readsetup->index = 0;
+	dev->qmi.readsetup->len = DEFAULT_READ_URB_LENGTH;
+
+	interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
+	
+	usb_fill_int_urb(dev->qmi.inturb, dev->usbnet->udev,
+	                 usb_rcvintpipe(dev->usbnet->udev, 0x81),
+	                 dev->qmi.intbuf, DEFAULT_READ_URB_LENGTH,
+	                 int_callback, dev, interval);
+	return usb_submit_urb(dev->qmi.inturb, GFP_KERNEL);
+}
+
+void qc_stopread(struct qcusbnet * dev)
+{
+	if (dev->qmi.readurb) {
+		DBG("Killng read URB\n");
+		usb_kill_urb(dev->qmi.readurb);
+	}
+
+	if (dev->qmi.inturb) {
+		DBG("Killng int URB\n");
+		usb_kill_urb(dev->qmi.inturb);
+	}
+
+	kfree(dev->qmi.readsetup);
+	dev->qmi.readsetup = NULL;
+	kfree(dev->qmi.readbuf);
+	dev->qmi.readbuf = NULL;
+	kfree(dev->qmi.intbuf);
+	dev->qmi.intbuf = NULL;
+	
+	usb_free_urb(dev->qmi.readurb);
+	dev->qmi.readurb = NULL;
+	usb_free_urb(dev->qmi.inturb);
+	dev->qmi.inturb = NULL;
+}
+
+static int read_async(struct qcusbnet *dev, u16 cid, u16 tid,
+                      void (*hook)(struct qcusbnet*, u16, void *), void *data)
+{
+	struct list_head *node;
+	struct client *client;
+	struct readreq *readreq;
+	
+	unsigned long flags;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find matching client ID 0x%04X\n", cid);
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		return -ENXIO;
+	}
+
+	list_for_each(node, &client->reads) {
+		readreq = list_entry(node, struct readreq, node);
+		if (!tid || tid == readreq->tid) {
+			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+			hook(dev, cid, data);
+			return 0;
+		}
+	}
+
+	if (!client_addnotify(dev, cid, tid, hook, data))
+		DBG("Unable to register for notification\n");
+
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+	return 0;
+}
+
+static void upsem(struct qcusbnet *dev, u16 cid, void *data) 
+{
+	DBG("0x%04X\n", cid);
+	up((struct semaphore *)data);
+}
+
+static int read_sync(struct qcusbnet *dev, void **buf, u16 cid, u16 tid)
+{
+	struct list_head *node;
+	int result;
+	struct client * client;
+	struct notifyreq *notify;
+	struct semaphore sem;
+	void * data;
+	unsigned long flags;
+	u16 size;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+	
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find matching client ID 0x%04X\n", cid);
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		return -ENXIO;
+	}
+	
+	while (!client_delread(dev, cid, tid, &data, &size)) {
+		sema_init(&sem, 0);
+		if (!client_addnotify(dev, cid, tid, upsem, &sem)) {
+			DBG("unable to register for notification\n");
+			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+			return -EFAULT;
+		}
+
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+
+		result = down_interruptible(&sem);
+		if (result) {
+			DBG("Interrupted %d\n", result);
+			spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+			list_for_each(node, &client->notifies) {
+				notify = list_entry(node, struct notifyreq, node);
+				if (notify->data == &sem) {
+					list_del(&notify->node);
+					kfree(notify);
+					break;
+				}
+			}
+
+			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+			return -EINTR;
+		}
+		
+		if (!device_valid(dev)) {
+			DBG("Invalid device!\n");
+			return -ENXIO;
+		}
+		
+		spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+	}
+	
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+	*buf = data;
+	return size;
+}
+
+static void write_callback(struct urb *urb)
+{
+	if (!urb) {
+		DBG("null urb\n");
+		return;
+	}
+
+	DBG("Write status/size %d/%d\n", urb->status, urb->actual_length);
+	up((struct semaphore *)urb->context);
+}
+
+static int write_sync(struct qcusbnet *dev, char *buf, int size, u16 cid)
+{
+	int result;
+	struct semaphore sem;
+	struct urb *urb;
+	struct urbsetup setup;
+	unsigned long flags;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		DBG("URB mem error\n");
+		return -ENOMEM;
+	}
+
+	result = qmux_fill(cid, buf, size);
+	if (result < 0) {
+		usb_free_urb(urb);
+		return result;
+	}
+
+	/* CDC Send Encapsulated Request packet */
+	setup.type = 0x21;
+	setup.code = 0;
+	setup.value = 0;
+	setup.index = 0;
+	setup.len = 0;
+	setup.len = size;
+
+	usb_fill_control_urb(urb, dev->usbnet->udev,
+	                     usb_sndctrlpipe(dev->usbnet->udev, 0),
+	                     (unsigned char *)&setup, (void*)buf, size,
+	                     NULL, dev);
+
+	DBG("Actual Write:\n");
+	printhex(buf, size);
+
+	sema_init(&sem, 0);
+	
+	urb->complete = write_callback;
+	urb->context = &sem;
+	
+	result = usb_autopm_get_interface(dev->iface);
+	if (result < 0) {
+		DBG("unable to resume interface: %d\n", result);
+		if (result == -EPERM) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+			dev->usbnet->udev->auto_pm = 0;
+#endif
+			qc_suspend(dev->iface, PMSG_SUSPEND);
+		}
+		return result;
+	}
+
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+	if (!client_addurb(dev, cid, urb)) {
+		usb_free_urb(urb);
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);	
+		usb_autopm_put_interface(dev->iface);
+		return -EINVAL;
+	}
+
+	result = usb_submit_urb(urb, GFP_KERNEL);
+	if (result < 0)	{
+		DBG("submit URB error %d\n", result);
+		if (client_delurb(dev, cid) != urb) {
+			DBG("Didn't get write URB back\n");
+		}
+
+		usb_free_urb(urb);
+
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		usb_autopm_put_interface(dev->iface);
+		return result;
+	}
+	
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);	
+	result = down_interruptible(&sem);
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+
+	usb_autopm_put_interface(dev->iface);
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+	if (client_delurb(dev, cid) != urb)
+	{
+		DBG("Didn't get write URB back\n");
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);	
+		return -EINVAL;
+	}
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);	
+
+	if (!result) {
+		if (!urb->status) {
+			result = size;
+		} else {
+			DBG("bad status = %d\n", urb->status);
+			result = urb->status;
+		}
+	} else {
+		DBG("Interrupted %d !!!\n", result);
+		DBG("Device may be in bad state and need reset !!!\n");
+		usb_kill_urb(urb);
+	}
+
+	usb_free_urb(urb);
+	return result;
+}
+
+static int client_alloc(struct qcusbnet *dev, u8 type)
+{
+	u16 cid;
+	struct client *client;
+	int result;
+	void * wbuf;
+	size_t wbufsize;
+	void * rbuf;
+	u16 rbufsize;
+	unsigned long flags;
+	u8 tid;
+	
+	if (!device_valid(dev)) {
+		DBG("Invalid device!\n");
+		return -ENXIO;
+	}
+
+	if (type) {
+		tid = atomic_add_return(1, &dev->qmi.qmitid);
+		if (!tid)
+			atomic_add_return(1, &dev->qmi.qmitid);
+		wbuf = qmictl_new_getcid(tid, type, &wbufsize);
+		if (!wbuf)
+			return -ENOMEM;
+		result = write_sync(dev, wbuf, wbufsize, QMICTL);
+		kfree(wbuf);
+
+		if (result < 0)
+			return result;
+
+		result = read_sync(dev, &rbuf, QMICTL, tid);
+		if (result < 0) {
+			DBG("bad read data %d\n", result);
+			return result;
+		}
+		rbufsize = result;
+
+		result = qmictl_alloccid_resp(rbuf, rbufsize, &cid);
+		kfree(rbuf);
+
+		if (result < 0)
+			return result;
+	} else {
+		cid = 0;
+	}
+
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+	if (client_bycid(dev, cid)) {
+		DBG("Client memory already exists\n");
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		return -ETOOMANYREFS;
+	}
+
+	client = kmalloc(sizeof(*client), GFP_ATOMIC);
+	if (!client) {
+		DBG("Error allocating read list\n");
+		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		return -ENOMEM;
+	}
+
+	list_add_tail(&client->node, &dev->qmi.clients);
+	client->cid = cid;
+	INIT_LIST_HEAD(&client->reads);
+	INIT_LIST_HEAD(&client->notifies);
+	INIT_LIST_HEAD(&client->urbs);
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+	return cid;
+}
+
+static void client_free(struct qcusbnet *dev, u16 cid)
+{
+	struct list_head *node, *tmp;
+	int result;
+	struct client *client;
+	struct urb * urb;
+	void * data;
+	u16 size;
+	void * wbuf;
+	size_t wbufsize;
+	void * rbuf;
+	u16 rbufsize;
+	unsigned long flags;
+	u8 tid;
+
+	if (!device_valid(dev)) {
+		DBG("invalid device\n");
+		return;
+	}
+	
+	DBG("releasing 0x%04X\n", cid);
+
+	if (cid != QMICTL)
+	{
+		tid = atomic_add_return(1, &dev->qmi.qmitid);
+		if (!tid)
+			tid = atomic_add_return(1, &dev->qmi.qmitid);
+		wbuf = qmictl_new_releasecid(tid, cid, &wbufsize);
+		if (!wbuf) {
+			DBG("memory error\n");
+		} else {
+			result = write_sync(dev, wbuf, wbufsize, QMICTL);
+			kfree(wbuf);
+
+			if (result < 0) {
+				DBG("bad write status %d\n", result);
+			} else {
+				result = read_sync(dev, &rbuf, QMICTL, tid);
+				if (result < 0) {
+					DBG("bad read status %d\n", result);
+				} else {
+					rbufsize = result;
+					result = qmictl_freecid_resp(rbuf, rbufsize);
+					kfree(rbuf);
+					if (result < 0)
+						DBG("error %d parsing response\n", result);
+				}
+			}
+		}
+	}
+
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+	list_for_each_safe(node, tmp, &dev->qmi.clients) {
+		client = list_entry(node, struct client, node);
+		if (client->cid == cid) {
+			while (client_notify(dev, cid, 0));
+
+			urb = client_delurb(dev, cid);
+			while (urb != NULL) {
+				usb_kill_urb(urb);
+				usb_free_urb(urb);
+				urb = client_delurb(dev, cid);
+			}
+
+			while (client_delread(dev, cid, 0, &data, &size))
+				kfree(data);
+
+			list_del(&client->node);
+			kfree(client);
+		}
+	}
+	
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+}
+
+struct client *client_bycid(struct qcusbnet *dev, u16 cid)
+{
+	struct list_head *node;
+	struct client *client;
+	
+	if (!device_valid(dev))	{
+		DBG("Invalid device\n");
+		return NULL;
+	}
+
+	assert_locked(dev);	
+	
+	list_for_each(node, &dev->qmi.clients) {
+		client = list_entry(node, struct client, node);
+		if (client->cid == cid)
+			return client;
+	}
+
+	DBG("Could not find client mem 0x%04X\n", cid);
+	return NULL;
+}
+
+static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data,
+                           u16 size)
+{
+	struct client *client;
+	struct readreq *req;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return false;
+	}
+
+	req = kmalloc(sizeof(*req), GFP_ATOMIC);
+	if (!req) {
+		DBG("Mem error\n");
+		return false;
+	}
+
+	req->data = data;
+	req->size = size;
+	req->tid = tid;
+
+	list_add_tail(&req->node, &client->reads);
+	
+	return true;
+}
+
+static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data,
+                           u16 *size)
+{
+	struct client *client;
+	struct readreq *req;
+	struct list_head *node;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return false;
+	}
+	
+	list_for_each(node, &client->reads) {
+		req = list_entry(node, struct readreq, node);
+		if (!tid || tid == req->tid) {
+			*data = req->data;
+			*size = req->size;
+			list_del(&req->node);
+			kfree(req);
+			return true;
+		}
+		
+		DBG("skipping 0x%04X data TID = %x\n", cid, req->tid);
+	}
+	
+	DBG("No read memory to pop, Client 0x%04X, TID = %x\n", cid, tid);
+	return false;
+}
+
+static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid,
+                      void (*hook)(struct qcusbnet *, u16, void *),
+                      void *data)
+{
+	struct client *client;
+	struct notifyreq *req;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return false;
+	}
+
+	req = kmalloc(sizeof(*req), GFP_ATOMIC);
+	if (!req) {
+		DBG("Mem error\n");
+		return false;
+	}
+
+	list_add_tail(&req->node, &client->notifies);
+	req->func = hook;
+	req->data = data;
+	req->tid = tid;
+
+	return true;
+}
+
+static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid)
+{
+	struct client *client;
+	struct notifyreq *delnotify, *notify;
+	struct list_head *node;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return false;
+	}
+
+	delnotify = NULL;
+
+	list_for_each(node, &client->notifies) {
+		notify = list_entry(node, struct notifyreq, node);
+		if (!tid || !notify->tid || tid == notify->tid) {
+			delnotify = notify;
+			break;
+		}
+		
+		DBG("skipping data TID = %x\n", notify->tid);
+	}
+	
+	if (delnotify) {
+		list_del(&delnotify->node);
+		if (delnotify->func) {
+			spin_unlock(&dev->qmi.clients_lock);
+			delnotify->func(dev, cid, delnotify->data);
+			spin_lock(&dev->qmi.clients_lock);
+		}
+		kfree(delnotify);
+		return true;
+	}
+
+	DBG("no one to notify for TID %x\n", tid);	
+	return false;
+}
+
+static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb)
+{
+	struct client *client;
+	struct urbreq *req;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return false;
+	}
+
+	req = kmalloc(sizeof(*req), GFP_ATOMIC);
+	if (!req) {
+		DBG("Mem error\n");
+		return false;
+	}	
+
+	req->urb = urb;
+	list_add_tail(&req->node, &client->urbs);
+	
+	return true;
+}
+
+static struct urb *client_delurb(struct qcusbnet *dev, u16 cid)
+{
+	struct client *client;
+	struct urbreq *req;
+	struct urb *urb;
+
+	assert_locked(dev);
+
+	client = client_bycid(dev, cid);
+	if (!client) {
+		DBG("Could not find this client's memory 0x%04X\n", cid);
+		return NULL;
+	}
+
+	if (list_empty(&client->urbs)) {
+		DBG("No URB's to pop\n");
+		return NULL;
+	}
+
+	req = list_first_entry(&client->urbs, struct urbreq, node);
+	list_del(&req->node);
+	urb = req->urb;
+	kfree(req);
+	return urb;
+}
+
+static int devqmi_open(struct inode *inode, struct file *file)
+{
+	struct qmihandle * handle;
+	struct qmidev *qmidev = container_of(inode->i_cdev, struct qmidev, cdev);
+	struct qcusbnet * dev = container_of(qmidev, struct qcusbnet, qmi);
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device\n");
+		return -ENXIO;
+	}
+
+	file->private_data = kmalloc(sizeof(struct qmihandle), GFP_KERNEL);
+	if (!file->private_data) {
+		DBG("Mem error\n");
+		return -ENOMEM;
+	}
+	
+	handle = (struct qmihandle *)file->private_data;
+	handle->cid = (u16)-1;
+	handle->dev = dev;
+	
+	return 0;
+}
+
+static long devqmi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int result;
+	u32 vidpid;
+	
+	struct qmihandle *handle = (struct qmihandle *)file->private_data;
+
+	if (!handle) {
+		DBG("Bad file data\n");
+		return -EBADF;
+	}
+	
+	if (!device_valid(handle->dev)) {
+		DBG("Invalid device! Updating f_ops\n");
+		file->f_op = file->f_dentry->d_inode->i_fop;
+		return -ENXIO;
+	}
+
+	switch (cmd) {
+		case IOCTL_QMI_GET_SERVICE_FILE:
+
+			DBG("Setting up QMI for service %lu\n", arg);
+			if (!(u8)arg) {
+				DBG("Cannot use QMICTL from userspace\n");
+				return -EINVAL;
+			}
+
+			if (handle->cid != (u16)-1) {
+				DBG("Close the current connection before opening a new one\n");
+				return -EBADR;
+			}
+			
+			result = client_alloc(handle->dev, (u8)arg);
+			if (result < 0)
+				return result;
+			handle->cid = result;
+
+			return 0;
+			break;
+
+
+		case IOCTL_QMI_GET_DEVICE_VIDPID:
+			if (!arg) {
+				DBG("Bad VIDPID buffer\n");
+				return -EINVAL;
+			}
+			
+			if (!handle->dev->usbnet) {
+				DBG("Bad usbnet\n");
+				return -ENOMEM;
+			}
+
+			if (!handle->dev->usbnet->udev) {
+				DBG("Bad udev\n");
+				return -ENOMEM;
+			}
+
+			vidpid = ((le16_to_cpu(handle->dev->usbnet->udev->descriptor.idVendor) << 16)
+			       + le16_to_cpu(handle->dev->usbnet->udev->descriptor.idProduct));
+
+			result = copy_to_user((unsigned int *)arg, &vidpid, 4);
+			if (result)
+				DBG("Copy to userspace failure\n");
+
+			return result;
+			break;
+
+		case IOCTL_QMI_GET_DEVICE_MEID:
+			if (!arg) {
+				DBG("Bad MEID buffer\n");
+				return -EINVAL;
+			}
+			
+			result = copy_to_user((unsigned int *)arg, &handle->dev->meid[0], 14);
+			if (result)
+				DBG("copy to userspace failure\n");
+
+			return result;
+			break;
+		default:
+			return -EBADRQC;		 
+	}
+}
+
+static int devqmi_close(struct file *file, fl_owner_t ftable)
+{
+	struct qmihandle * handle = (struct qmihandle *)file->private_data;
+	struct list_head * tasks;
+	struct task_struct * task;
+	struct fdtable * fdtable;
+	int count = 0;
+	int used = 0;
+	unsigned long flags;
+
+	if (!handle) {
+		DBG("bad file data\n");
+		return -EBADF;
+	}
+
+	if (file_count(file) != 1) {
+		/* XXX: This can't possibly be safe. We don't hold any sort of
+		 * lock here, and we're walking a list of threads... */
+		list_for_each(tasks, &current->group_leader->tasks) {
+			task = container_of(tasks, struct task_struct, tasks);
+			if (!task || !task->files)
+				continue;
+			spin_lock_irqsave(&task->files->file_lock, flags);
+			fdtable = files_fdtable(task->files);
+			for (count = 0; count < fdtable->max_fds; count++) {
+				/* Before this function was called, this file was removed
+				 * from our task's file table so if we find it in a file
+				 * table then it is being used by another task
+				 */
+				if (fdtable->fd[count] == file) {
+					used++;
+					break;
+				}
+			}
+			spin_unlock_irqrestore(&task->files->file_lock, flags);
+		}
+		
+		if (used > 0) {
+			DBG("not closing, as this FD is open by %d other process\n", used);
+			return 0;
+		}
+	}
+
+	if (!device_valid(handle->dev)) {
+		DBG("Invalid device! Updating f_ops\n");
+		file->f_op = file->f_dentry->d_inode->i_fop;
+		return -ENXIO;
+	}
+	
+	DBG("0x%04X\n", handle->cid);
+	
+	file->private_data = NULL;
+
+	if (handle->cid != (u16)-1)
+		client_free(handle->dev, handle->cid);
+
+	kfree(handle);
+	return 0;
+}
+
+static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size,
+                           loff_t *pos)
+{
+	int result;
+	void * data = NULL;
+	void * smalldata;
+	struct qmihandle * handle = (struct qmihandle *)file->private_data;
+
+	if (!handle) {
+		DBG("Bad file data\n");
+		return -EBADF;
+	}
+
+	if (!device_valid(handle->dev)) {
+		DBG("Invalid device! Updating f_ops\n");
+		file->f_op = file->f_dentry->d_inode->i_fop;
+		return -ENXIO;
+	}
+	
+	if (handle->cid == (u16)-1) {
+		DBG("Client ID must be set before reading 0x%04X\n",
+		    handle->cid);
+		return -EBADR;
+	}
+	
+	result = read_sync(handle->dev, &data, handle->cid, 0);
+	if (result <= 0)
+		return result;
+	
+	result -= qmux_size;
+	smalldata = data + qmux_size;
+
+	if (result > size) {
+		DBG("Read data is too large for amount user has requested\n");
+		kfree(data);
+		return -EOVERFLOW;
+	}
+
+	if (copy_to_user(buf, smalldata, result)) {
+		DBG("Error copying read data to user\n");
+		result = -EFAULT;
+	}
+	
+	kfree(data);
+	return result;
+}
+
+static ssize_t devqmi_write (struct file *file, const char __user * buf,
+                             size_t size, loff_t *pos)
+{
+	int status;
+	void *wbuf;
+	struct qmihandle *handle = (struct qmihandle *)file->private_data;
+
+	if (!handle) {
+		DBG("Bad file data\n");
+		return -EBADF;
+	}
+
+	if (!device_valid(handle->dev)) {
+		DBG("Invalid device! Updating f_ops\n");
+		file->f_op = file->f_dentry->d_inode->i_fop;
+		return -ENXIO;
+	}
+
+	if (handle->cid == (u16)-1) {
+		DBG("Client ID must be set before writing 0x%04X\n",
+			  handle->cid);
+		return -EBADR;
+	}
+	
+	wbuf = kmalloc(size + qmux_size, GFP_KERNEL);
+	if (!wbuf)
+		return -ENOMEM;
+	status = copy_from_user(wbuf + qmux_size, buf, size);
+	if (status) {
+		DBG("Unable to copy data from userspace %d\n", status);
+		kfree(wbuf);
+		return status;
+	}
+
+	status = write_sync(handle->dev, wbuf, size + qmux_size,
+	                    handle->cid);
+
+	kfree(wbuf);
+	if (status == size + qmux_size)
+		return size;
+	return status;
+}
+
+int qc_register(struct qcusbnet *dev)
+{
+	int result;
+	int qmiidx = 0;
+	dev_t devno; 
+	char * name;
+	
+	dev->valid = true;
+	result = client_alloc(dev, QMICTL);
+	if (result) {
+		dev->valid = false;
+		return result;
+	}
+	atomic_set(&dev->qmi.qmitid, 1);
+
+	result = qc_startread(dev);
+	if (result) {
+		dev->valid = false;
+		return result;
+	}
+	
+	if (!qmi_ready(dev, 30000)) {
+		DBG("Device unresponsive to QMI\n");
+		return -ETIMEDOUT;
+	}
+
+	result = setup_wds_callback(dev);
+	if (result) {
+		dev->valid = false;
+		return result;
+	}
+
+	result = qmidms_getmeid(dev);
+	if (result) {
+		dev->valid = false;
+		return result;
+	}
+
+	result = alloc_chrdev_region(&devno, 0, 1, "qcqmi");
+	if (result < 0)
+		return result;
+
+	cdev_init(&dev->qmi.cdev, &devqmi_fops);
+	dev->qmi.cdev.owner = THIS_MODULE;
+	dev->qmi.cdev.ops = &devqmi_fops;
+
+	result = cdev_add(&dev->qmi.cdev, devno, 1);
+	if (result) {
+		DBG("error adding cdev\n");
+		return result;
+	}
+
+	name = strstr(dev->usbnet->net->name, "usb");
+	if (!name) {
+		DBG("Bad net name: %s\n", dev->usbnet->net->name);
+		return -ENXIO;
+	}
+	name += strlen("usb");
+	qmiidx = simple_strtoul(name, NULL, 10);
+	if (qmiidx < 0) {
+		DBG("Bad minor number\n");
+		return -ENXIO;
+	}
+
+	printk(KERN_INFO "creating qcqmi%d\n", qmiidx);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+	device_create(dev->qmi.devclass, NULL, devno, NULL, "qcqmi%d", qmiidx);
+#else
+	device_create(dev->qmi.devclass, NULL, devno, "qcqmi%d", qmiidx);
+#endif
+	
+	dev->qmi.devnum = devno;
+	return 0;
+}
+
+void qc_deregister(struct qcusbnet *dev)
+{
+	struct list_head *node;
+	struct client *client;
+	struct inode * inode;
+	struct list_head * inodes;
+	struct list_head * tasks;
+	struct task_struct * task;
+	struct fdtable * fdtable;
+	struct file * file;
+	unsigned long flags;
+	int count = 0;
+
+	if (!device_valid(dev)) {
+		DBG("wrong device\n");
+		return;
+	}
+
+	list_for_each(node, &dev->qmi.clients) {
+		client = list_entry(node, struct client, node);
+		DBG("release 0x%04X\n", client->cid);
+		client_free(dev, client->cid);
+	}
+
+	qc_stopread(dev);
+	dev->valid = false;
+	list_for_each(inodes, &dev->qmi.cdev.list) {
+		inode = container_of(inodes, struct inode, i_devices);
+		if (inode != NULL && !IS_ERR(inode)) {
+			list_for_each(tasks, &current->group_leader->tasks) {
+				task = container_of(tasks, struct task_struct, tasks);
+				if (!task || !task->files)
+					continue;
+				spin_lock_irqsave(&task->files->file_lock, flags);
+				fdtable = files_fdtable(task->files);
+				for (count = 0; count < fdtable->max_fds; count++) {
+					file = fdtable->fd[count];
+					if (file != NULL &&  file->f_dentry != NULL) {
+						if (file->f_dentry->d_inode == inode) {
+							rcu_assign_pointer(fdtable->fd[count], NULL);	
+							spin_unlock_irqrestore(&task->files->file_lock, flags);
+							DBG("forcing close of open file handle\n");
+							filp_close(file, task->files);
+							spin_lock_irqsave(&task->files->file_lock, flags);
+						}
+					}
+				}
+				spin_unlock_irqrestore(&task->files->file_lock, flags);
+			}
+		}
+	}
+
+	if (!IS_ERR(dev->qmi.devclass))
+		device_destroy(dev->qmi.devclass, dev->qmi.devnum);
+	cdev_del(&dev->qmi.cdev);
+	unregister_chrdev_region(dev->qmi.devnum, 1);
+}
+
+static bool qmi_ready(struct qcusbnet *dev, u16 timeout)
+{
+	int result;
+	void * wbuf;
+	size_t wbufsize;
+	void * rbuf;
+	u16 rbufsize;
+	struct semaphore sem;
+	u16 now;
+	unsigned long flags;
+	u8 tid;
+	
+	if (!device_valid(dev)) {
+		DBG("Invalid device\n");
+		return -EFAULT;
+	}
+
+
+	for (now = 0; now < timeout; now += 100) {
+		sema_init(&sem, 0);
+
+		tid = atomic_add_return(1, &dev->qmi.qmitid);
+		if (!tid)
+			tid = atomic_add_return(1, &dev->qmi.qmitid);
+		if (wbuf)
+			kfree(wbuf);
+		wbuf = qmictl_new_ready(tid, &wbufsize);
+		if (!wbuf)
+			return -ENOMEM;
+	
+		result = read_async(dev, QMICTL, tid, upsem, &sem);
+		if (result) {
+			kfree(wbuf);
+			return false;
+		}
+
+		write_sync(dev, wbuf, wbufsize, QMICTL);
+
+		msleep(100);
+		if (!down_trylock(&sem)) {
+			spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+			if (client_delread(dev,	QMICTL,	tid, &rbuf, &rbufsize)) {
+				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+				kfree(rbuf);
+				break;
+			}
+		} else {
+			spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+			client_notify(dev, QMICTL, tid);
+			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+		}
+	}
+
+	if (wbuf)
+		kfree(wbuf);
+
+	if (now >= timeout)
+		return false;
+	
+	DBG("QMI Ready after %u milliseconds\n", now);
+
+	/* 3580 and newer doesn't need a delay; older needs 5000ms */
+	if (qcusbnet2k_fwdelay)
+		msleep(qcusbnet2k_fwdelay * 1000);
+
+	return true;
+}
+
+static void wds_callback(struct qcusbnet *dev, u16 cid, void *data)
+{
+	bool ret;
+	int result;
+	void * rbuf;
+	u16 rbufsize;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+	struct net_device_stats * stats = &(dev->usbnet->stats);
+#else
+	struct net_device_stats * stats = &(dev->usbnet->net->stats);
+#endif
+
+	struct qmiwds_stats dstats = {
+		.txok = (u32)-1,
+		.rxok = (u32)-1,
+		.txerr = (u32)-1,
+		.rxerr = (u32)-1,
+		.txofl = (u32)-1,
+		.rxofl = (u32)-1,
+		.txbytesok = (u64)-1,
+		.rxbytesok = (u64)-1,
+	};
+	unsigned long flags;
+	
+	if (!device_valid(dev)) {
+		DBG("Invalid device\n");
+		return;
+	}
+
+	spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+	ret = client_delread(dev, cid, 0, &rbuf, &rbufsize);
+	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); 
+	
+	if (!ret) {
+		DBG("WDS callback failed to get data\n");
+		return;
+	}
+	
+	dstats.linkstate = !qc_isdown(dev, DOWN_NO_NDIS_CONNECTION);
+	dstats.reconfigure = false;
+
+	result = qmiwds_event_resp(rbuf, rbufsize, &dstats);
+	if (result < 0) {
+		DBG("bad WDS packet\n");
+	} else {
+		if (dstats.txofl != (u32)-1)
+			stats->tx_fifo_errors = dstats.txofl;
+		
+		if (dstats.rxofl != (u32)-1)
+			stats->rx_fifo_errors = dstats.rxofl;
+
+		if (dstats.txerr != (u32)-1)
+			stats->tx_errors = dstats.txerr;
+		
+		if (dstats.rxerr != (u32)-1)
+			stats->rx_errors = dstats.rxerr;
+
+		if (dstats.txok != (u32)-1)
+			stats->tx_packets = dstats.txok + stats->tx_errors;
+		
+		if (dstats.rxok != (u32)-1)
+			stats->rx_packets = dstats.rxok + stats->rx_errors;
+
+		if (dstats.txbytesok != (u64)-1)
+			stats->tx_bytes = dstats.txbytesok;
+		
+		if (dstats.rxbytesok != (u64)-1)
+			stats->rx_bytes = dstats.rxbytesok;
+
+		if (dstats.reconfigure) {
+			DBG("Net device link reset\n");
+			qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+			qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION);
+		} else {
+			if (dstats.linkstate) {
+				DBG("Net device link is connected\n");
+				qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION);
+			} else {
+				DBG("Net device link is disconnected\n");
+				qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+			}
+		}
+	}
+
+	kfree(rbuf);
+
+	result = read_async(dev, cid, 0, wds_callback, data);
+	if (result != 0)
+		DBG("unable to setup next async read\n");
+}
+
+static int setup_wds_callback(struct qcusbnet * dev)
+{
+	int result;
+	void *buf;
+	size_t size;
+	u16 cid;
+
+	if (!device_valid(dev)) {
+		DBG("Invalid device\n");
+		return -EFAULT;
+	}
+	
+	result = client_alloc(dev, QMIWDS);
+	if (result < 0)
+		return result;
+	cid = result;
+
+	buf = qmiwds_new_seteventreport(1, &size);
+	if (!buf)
+		return -ENOMEM;
+
+	result = write_sync(dev, buf, size, cid);
+	kfree(buf);
+
+	if (result < 0) {
+		return result;
+	}
+
+	buf = qmiwds_new_getpkgsrvcstatus(2, &size);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	result = write_sync(dev, buf, size, cid);
+	kfree(buf);
+
+	if (result < 0)
+		return result;
+
+	result = read_async(dev, cid, 0, wds_callback, NULL);
+	if (result) {
+		DBG("unable to setup async read\n");
+		return result;
+	}
+
+	result = usb_control_msg(dev->usbnet->udev,
+	                         usb_sndctrlpipe(dev->usbnet->udev, 0),
+	                         0x22, 0x21, 1, 0, NULL, 0, 100);
+	if (result < 0) {
+		DBG("Bad SetControlLineState status %d\n", result);
+		return result;
+	}
+
+	return 0;
+}
+
+static int qmidms_getmeid(struct qcusbnet * dev)
+{
+	int result;
+	void * wbuf;
+	size_t wbufsize;
+	void * rbuf;
+	u16 rbufsize;
+	u16 cid;
+
+	if (!device_valid(dev))	{
+		DBG("Invalid device\n");
+		return -EFAULT;
+	}
+
+	result = client_alloc(dev, QMIDMS);
+	if (result < 0)
+		return result;
+	cid = result;
+
+	wbuf = qmidms_new_getmeid(1, &wbufsize);
+	if (!wbuf)
+		return -ENOMEM;
+
+	result = write_sync(dev, wbuf, wbufsize, cid);
+	kfree(wbuf);
+
+	if (result < 0)
+		return result;
+
+	result = read_sync(dev, &rbuf, cid, 1);
+	if (result < 0)
+		return result;
+	rbufsize = result;
+
+	result = qmidms_meid_resp(rbuf, rbufsize, &dev->meid[0], 14);
+	kfree(rbuf);
+
+	if (result < 0) {
+		DBG("bad get MEID resp\n");
+		memset(&dev->meid[0], '0', 14);
+	}
+
+	client_free(dev, cid);
+	return 0;
+}
+
+module_param(qcusbnet2k_fwdelay, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(qcusbnet2k_fwdelay, "Delay for old firmware");
diff --git a/drivers/net/usb/qcusbnet/qmidevice.h b/drivers/net/usb/qcusbnet/qmidevice.h
new file mode 100755
index 0000000..39a4b7f
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmidevice.h
@@ -0,0 +1,36 @@
+/* qmidevice.h - gobi QMI device header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QMIDEVICE_H
+#define QCUSBNET_QMIDEVICE_H
+
+#include "structs.h"
+#include "qmi.h"
+
+void printhex(const void *data, size_t size);
+void qc_setdown(struct qcusbnet *dev, u8 reason);
+void qc_cleardown(struct qcusbnet *dev, u8 reason);
+bool qc_isdown(struct qcusbnet *dev, u8 reason);
+
+int qc_startread(struct qcusbnet *dev);
+void qc_stopread(struct qcusbnet *dev);
+
+int qc_register(struct qcusbnet *dev);
+void qc_deregister(struct qcusbnet *dev);
+
+#endif /* !QCUSBNET_QMIDEVICE_H */
diff --git a/drivers/net/usb/qcusbnet/readme.txt b/drivers/net/usb/qcusbnet/readme.txt
new file mode 100755
index 0000000..cec6790
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/readme.txt
@@ -0,0 +1,118 @@
+Gobi2000 network driver for HP 1.0.110
+06/18/2010
+
+This readme covers important information concerning 
+the QCUSBNet2kHP driver, provided in correlation with
+the Gobi2000-Linux-Package.
+
+Table of Contents
+
+1. Prerequisites
+2. Installation instructions
+3. What's new in this release
+4. Notes
+5. Known issues
+
+-------------------------------------------------------------------------------
+
+1. PREREQUISITES
+
+a. Kernel headers or full kernel source installed for the currently 
+   running kernel.  There must be a link "/lib/modules/<uname -r>/build"
+   that points to these kernel headers or sources.
+b. The kernel must support the usbcore and usbnet drivers, either as
+   modules or built into the kernel.
+c. Tools required for building the kernel.  These tools usually come in a 
+   package called "kernel-devel".
+d. "gcc" compiler
+e. "make" tool
+
+-------------------------------------------------------------------------------
+
+2. INSTALLATION INSTRUCTIONS
+
+a. Navigate to the folder "QCUSBNet2k" that contains:
+      Makefile
+      QCUSBNetHP.c
+      QMIDevice.c
+      qmidevice.h
+      structs.h
+      QMI.c
+      qmi.h
+b. (only required for kernels prior to 2.6.25) Create a symbolic link 
+   to the usbnet.h file in your kernel sources:
+      ln -s <linux sources>/drivers/net/usb/usbnet.h ./
+c. Run the command:
+      make
+d. Copy the newly created QCUSBNet2kHP.ko into the directory
+   /lib/modules/`uname -r`/kernel/drivers/net/usb/
+e. Run the command:
+      depmod
+f. (Optional) Load the driver manually with the command:
+      modprobe QCUSBNet2kHP
+   - This is only required the first time after you install the
+     drivers.  If you restart or plug the Gobi device in the drivers 
+     will be automatically loaded.
+
+-------------------------------------------------------------------------------
+
+3. WHAT'S NEW
+
+This Release (Gobi2000 network driver for HP 1.0.110) 06/18/2010
+a. Correctly initialize semaphore during probe function.
+
+Prior Release (Gobi2000 network driver for HP 1.0.100) 06/02/2010
+a. Merge QCQMI driver into QCUSBNet2k driver, removing dependency.
+
+Prior Release (Gobi2000 network driver for HP 1.0.90) 05/13/2010
+a. Fix devqmi_close() from a thread
+b. Add 2.6.33 kernel support
+
+Prior Release (Gobi2000 network driver for HP 1.0.80) 04/19/2010
+a. Add support to check for duplicate or out of sequence QMI messages.
+
+Prior Release (Gobi2000 network driver for HP 1.0.70) 03/15/2010
+a. Added support for CDC CONNECTION_SPEED_CHANGE indication.
+b. Modified device cleanup function to better handle the device
+   losing power during suspend or hibernate.
+c. Replaced autosuspend feature with more aggressive "Selective suspend"
+   style power management.  Even if QCWWAN2k or Network connections are
+   active the device will still enter autosuspend after the delay time,
+   and wake on remote wakeup or new data being sent.
+
+Prior Release (Gobi2000 Serial driver for HP 1.0.60) 02/16/2010
+a. Fix to allow proper fork() functionality
+b. Add supports_autosuspend flag
+c. Ignore EOVERFLOW errors on interrupt endpoint and resubmit URB
+d. Change driver versions to match installer package
+e. Minor update for 2.6.23 kernel compatibility
+
+Prior Release (in correlation with Gobi2000-Linux-Package 1.0.30) 12/04/2009
+a. Modify ioctl numbering to avoid conflict in 2.6.31 kernel
+   This release is only compatible with GOBI2000_LINUX_SDK 1.0.30 and newer.
+b. Made minor compatibility changes for 2.6.31 kernel
+
+Prior Release (in correlation with Gobi2000-Linux-Package 1.0.20) 11/20/2009
+a. Initial release
+
+-------------------------------------------------------------------------------
+
+4. NOTES
+
+a. In Composite mode, the Gobi device will enumerate a network interface
+   usb<#> where <#> signifies the next available USB network interface.
+b. In Composite mode, the Gobi device will enumerate a device node
+   /dev/qcqmi<#>.  This device node is for use by the QCWWANCMAPI2k.
+c. Ownership, group, and permissions are managed by your system 
+   configuration.
+
+-------------------------------------------------------------------------------
+
+5. KNOWN ISSUES
+
+No known issues.
+         
+-------------------------------------------------------------------------------
+
+
+
diff --git a/drivers/net/usb/qcusbnet/structs.h b/drivers/net/usb/qcusbnet/structs.h
new file mode 100755
index 0000000..0f9f4eb
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/structs.h
@@ -0,0 +1,99 @@
+/* structs.h - shared structures
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_STRUCTS_H
+#define QCUSBNET_STRUCTS_H
+
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/cdev.h>
+#include <linux/kthread.h>
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)
+	#include "usbnet.h"
+#else
+	#include <linux/usb/usbnet.h>
+#endif
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)
+	#include <linux/fdtable.h>
+#else
+	#include <linux/file.h>
+#endif
+
+#define DBG(format, arg...) \
+	if (debug == 1) { \
+		printk(KERN_INFO "QCUSBNet2k::%s " format, __FUNCTION__, ## arg); \
+	}
+
+struct qcusbnet;
+
+struct urbreq {
+	struct list_head node;
+	struct urb *urb;
+};
+
+#define DEFAULT_READ_URB_LENGTH 0x1000
+
+struct worker {
+	struct task_struct *thread;
+	struct completion work;
+	struct list_head urbs;
+	spinlock_t urbs_lock;
+	struct urb *active;
+	spinlock_t active_lock;
+	struct usb_interface *iface;
+};
+
+struct qmidev {
+	dev_t devnum;
+	struct class *devclass;
+	struct cdev cdev;
+	struct urb *readurb;
+	struct urbsetup *readsetup;
+	void *readbuf;
+	struct urb *inturb;
+	void *intbuf;
+	struct list_head clients;
+	spinlock_t clients_lock;
+	atomic_t qmitid;
+};
+
+enum {
+	DOWN_NO_NDIS_CONNECTION = 0,
+	DOWN_CDC_CONNECTION_SPEED = 1,
+	DOWN_DRIVER_SUSPENDED = 2,
+	DOWN_NET_IFACE_STOPPED = 3,
+};
+
+struct qcusbnet {
+	struct usbnet *usbnet;
+	struct usb_interface *iface;
+	int (*open)(struct net_device *);
+	int (*stop)(struct net_device *);
+	unsigned long down;
+	bool valid;
+	struct qmidev qmi;
+	char meid[14];
+	struct worker worker;
+};
+
+#endif /* !QCUSBNET_STRUCTS_H */
-- 
1.7.1


^ permalink raw reply related

* Re: [PATCH] Add Qualcomm Gobi 2000 driver.
From: Ben Hutchings @ 2010-09-28 17:16 UTC (permalink / raw)
  To: Elly Jones; +Cc: netdev, dbrownell, mjg59, jglasgow, msb, olofj
In-Reply-To: <20100928171026.GB6083@google.com>

On Tue, 2010-09-28 at 13:10 -0400, Elly Jones wrote:
> From: Elizabeth Jones <ellyjones@google.com>
> 
> This driver is a rewrite of the original Qualcomm GPL driver, released as part
> of Qualcomm's "Code Aurora" initiative. The driver has been transformed into
> Linux kernel style and made to use kernel APIs where appropriate; some bugs have
> also been fixed. Note that the device in question requires firmware and a
> firmware loader; the latter has been written by mjg (see
> http://www.codon.org.uk/~mjg59/gobi_loader/).
[...]

You should run this through checkpatch.pl and fix the style errors it
highlights.

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.


^ permalink raw reply

* Re: [PATCH -next] pch_gbe: add header files
From: David Miller @ 2010-09-28 17:34 UTC (permalink / raw)
  To: randy.dunlap; +Cc: sfr, netdev, linux-next, linux-kernel, masa-korg
In-Reply-To: <20100928095643.18c5020f.randy.dunlap@oracle.com>

From: Randy Dunlap <randy.dunlap@oracle.com>
Date: Tue, 28 Sep 2010 09:56:43 -0700

> From: Randy Dunlap <randy.dunlap@oracle.com>
> 
> Fix build errors, more header files needed.
> 
> drivers/net/pch_gbe/pch_gbe_main.c:965: error: implicit declaration of function 'tcp_hdr'
> drivers/net/pch_gbe/pch_gbe_main.c:965: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:968: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:976: error: implicit declaration of function 'udp_hdr'
> drivers/net/pch_gbe/pch_gbe_main.c:976: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:980: error: invalid type argument of '->' (have 'int')
> 
> Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com>

Applied, thanks Randy.

^ permalink raw reply

* Re: [PATCH net-next-2.6 04/17] bnx2x: Use netif_set_real_num_{rx,tx}_queues()
From: David Miller @ 2010-09-28 17:39 UTC (permalink / raw)
  To: vladz; +Cc: bhutchings, netdev, linux-net-drivers, eilong
In-Reply-To: <8628FE4E7912BF47A96AE7DD7BAC0AADDDEDAE6ECA@SJEXCHCCR02.corp.ad.broadcom.com>

From: "Vladislav Zolotarov" <vladz@broadcom.com>
Date: Tue, 28 Sep 2010 05:59:15 -0700

> Ben, I'm going to fix the bnx2x the way your patch would apply. I
> suspect this would probably require u to respin the bnx2x patch.
> Sorry for the inconvenience.

Ben's patch is already in the tree, so you need to send me patches
relative to his.

^ permalink raw reply

* Re: [PATCH net-next 2.6] myri10ge: DCA update
From: David Miller @ 2010-09-28 17:40 UTC (permalink / raw)
  To: gallatin; +Cc: eric.dumazet, netdev, loic
In-Reply-To: <4CA202FC.5000609@myri.com>

From: Andrew Gallatin <gallatin@myri.com>
Date: Tue, 28 Sep 2010 11:00:12 -0400

> I've corrected the style problems pointed out by checkpatch, and
> I've attached a new diff.  Is that sufficient, or do I start a
> new thread, or..?

Please send a new fresh patch submission, or as you term it
"start a new thread" :-)

^ permalink raw reply

* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Octavian Purdila @ 2010-09-28 17:35 UTC (permalink / raw)
  To: nicolas.dichtel; +Cc: Eric Dumazet, netdev
In-Reply-To: <1285691629.3154.80.camel@edumazet-laptop>

From: Eric Dumazet <eric.dumazet@gmail.com>
Date: Tuesday 28 September 2010, 19:33:49

> Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> > Hi,
> > 
> > I face a problem when I try to remove an interface,
> > netdev_wait_allrefs() complains about refcount.
> > 
> > Here is a trivial scenario to reproduce the problem:
> > # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> > # ./a.out tunl1
> > # ip tunnel del tunl1
> > 
> > Note: a.out binary create an IPv4 raw socket, attach it to tunl1
> > (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the
> > multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header
> > (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
> > 
> > Note2: when a.out is executed, tunl1 has no ip address and is down.
> 
> CC Octavian Purdila, the patch author.
> 
> I am just wondering why this route is created in the first place.
> 
> Maybe a fix would be to forbid this ?
> 
> Some machines have a giant route cache, so its very important to avoid
> expensive scans.
> 
> > Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice:
> > waiting for tunl1 to become free. Usage count = 3" and after some time,
> > interface is removed.
> > 
> > The problem is that route cache entries are only invalidate on
> > UNREGISTER event, and not removed (introduced by commit
> > e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that
> > rt_check_expire() remove the remaining route cache entries.
> > 
> > To fix the problem, I propose to remove a part of the previous commit.
> > 


Hi Nicolas,

The purpose of my original patch was to speed up interfaces deregistration 
even more after Eric's batch work. Reverting it might slow things down again, 
but since this is breaking things we should probably revert it and think a 
proper optimization afterward. I know that Eric B has done some more work in 
this area, for batch namespace cleanup, maybe the issue is not even there 
anymore. 

So, Ack from me.

We might even fully revert the patch, since the bit that is left doesn't have 
any value anymore.

Thanks,
tavi

^ permalink raw reply

* [RFC] Online firmware upgrade in non-embedded systems
From: Ben Hutchings @ 2010-09-28 17:59 UTC (permalink / raw)
  To: netdev, linux-kernel, linux-mtd; +Cc: sf-linux-drivers

Network and disk controllers normally have at least some firmware in
flash to support their use as boot devices.  Solarflare's current
network adapters can have as many as 4 distinct firmware partitions: one
for each PHY, one for the management CPU and one for net-boot.  Any or
all of these may need to be updated in the field, to fix bugs or to add
features.  (Furthermore, the net-boot firmware is based on gPXE and is
therefore modifiable by any user.)

Currently the sfc network driver is optionally combined with an MTD
driver (CONFIG_SFC_MTD) which exposes all upgradable firmware and
configuration partitions in flash.  This works nicely in kernels with
MTD enabled, but since MTD is mainly used in embedded systems with
on-board flash it is often disabled in distribution kernels and custom
kernels alike.  This leaves users of sfc unable to upgrade firmware
without rebuilding the kernel or booting some other distribution.  The
lack of widespread MTD support is a regular cause of support requests.

There are two main alternatives I'm aware of:

- Use the ethtool ETHTOOL_SEEPROM command with multiple magic numbers
for different partitions. bnx2x does something a little like this for
PHY firmware upgrades. The ethtool core passes in a page at a time so
this won't work well with flash block sizes greater than PAGE_SIZE.

- Use the ethtool ETHTOOL_FLASHDEV command. It's intended for precisely
this purpose, but the driver is supposed to implement it using
request_firmware(). That seems to require any firmware update program to
integrate with udev or to copy firmware images under /lib/firmware even
though they are one-time updates. That doesn't make a lot of sense to
me. Am I missing something?

Of course these are both specific to network devices; it seems deisrable
to have a more general convention for online firmware upgrades.  MTDs
clearly are more generally applicable, and pretty much every computer
does have flash storage for firmware and boot configuration, so perhaps
it should be treated as more of a standard feature?

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.

^ permalink raw reply

* Re: [PATCH net-next2.6] net: rename netdev rx_queue to ingress_queue
From: Jarek Poplawski @ 2010-09-28 18:04 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: jamal, David Miller, netdev
In-Reply-To: <1285689517.3154.76.camel@edumazet-laptop>

On Tue, Sep 28, 2010 at 05:58:37PM +0200, Eric Dumazet wrote:
> There is some confusion with rx_queue name after RPS, and net drivers
> private rx_queue fields.
> 
> I suggest to rename "struct net_device"->rx_queue to ingress_queue.
> 
> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
> ---
> 
> My plan is to make this structure dynamically allocated, in the rare
> case ingress is setup, to shrink net_device.

I hope Master of Ingress will forgive you this CC some day... ;-)

Jarek P.

> 
>  include/linux/netdevice.h |    2 +-
>  net/core/dev.c            |    8 ++++----
>  net/sched/sch_api.c       |   14 +++++++-------
>  net/sched/sch_generic.c   |    8 ++++----
>  4 files changed, 16 insertions(+), 16 deletions(-)
> 
> diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> index 83de0eb..1ae0d65 100644
> --- a/include/linux/netdevice.h
> +++ b/include/linux/netdevice.h
> @@ -983,7 +983,7 @@ struct net_device {
>  	rx_handler_func_t	*rx_handler;
>  	void			*rx_handler_data;
>  
> -	struct netdev_queue	rx_queue; /* use two cache lines */
> +	struct netdev_queue	ingress_queue; /* use two cache lines */
>  
>  /*
>   * Cache lines mostly used on transmit path
> diff --git a/net/core/dev.c b/net/core/dev.c
> index 42b200f..0ed0669 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -2674,7 +2674,7 @@ static int ing_filter(struct sk_buff *skb)
>  	skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
>  	skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
>  
> -	rxq = &dev->rx_queue;
> +	rxq = &dev->ingress_queue;
>  
>  	q = rxq->qdisc;
>  	if (q != &noop_qdisc) {
> @@ -2691,7 +2691,7 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb,
>  					 struct packet_type **pt_prev,
>  					 int *ret, struct net_device *orig_dev)
>  {
> -	if (skb->dev->rx_queue.qdisc == &noop_qdisc)
> +	if (skb->dev->ingress_queue.qdisc == &noop_qdisc)
>  		goto out;
>  
>  	if (*pt_prev) {
> @@ -4894,7 +4894,7 @@ static void __netdev_init_queue_locks_one(struct net_device *dev,
>  static void netdev_init_queue_locks(struct net_device *dev)
>  {
>  	netdev_for_each_tx_queue(dev, __netdev_init_queue_locks_one, NULL);
> -	__netdev_init_queue_locks_one(dev, &dev->rx_queue, NULL);
> +	__netdev_init_queue_locks_one(dev, &dev->ingress_queue, NULL);
>  }
>  
>  unsigned long netdev_fix_features(unsigned long features, const char *name)
> @@ -5406,7 +5406,7 @@ static void netdev_init_one_queue(struct net_device *dev,
>  
>  static void netdev_init_queues(struct net_device *dev)
>  {
> -	netdev_init_one_queue(dev, &dev->rx_queue, NULL);
> +	netdev_init_one_queue(dev, &dev->ingress_queue, NULL);
>  	netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
>  	spin_lock_init(&dev->tx_global_lock);
>  }
> diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
> index 6fb3d41..b802078 100644
> --- a/net/sched/sch_api.c
> +++ b/net/sched/sch_api.c
> @@ -240,7 +240,7 @@ struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
>  	if (q)
>  		goto out;
>  
> -	q = qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle);
> +	q = qdisc_match_from_root(dev->ingress_queue.qdisc_sleeping, handle);
>  out:
>  	return q;
>  }
> @@ -701,7 +701,7 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
>  		}
>  
>  		for (i = 0; i < num_q; i++) {
> -			struct netdev_queue *dev_queue = &dev->rx_queue;
> +			struct netdev_queue *dev_queue = &dev->ingress_queue;
>  
>  			if (!ingress)
>  				dev_queue = netdev_get_tx_queue(dev, i);
> @@ -979,7 +979,7 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
>  					return -ENOENT;
>  				q = qdisc_leaf(p, clid);
>  			} else { /* ingress */
> -				q = dev->rx_queue.qdisc_sleeping;
> +				q = dev->ingress_queue.qdisc_sleeping;
>  			}
>  		} else {
>  			q = dev->qdisc;
> @@ -1044,7 +1044,7 @@ replay:
>  					return -ENOENT;
>  				q = qdisc_leaf(p, clid);
>  			} else { /*ingress */
> -				q = dev->rx_queue.qdisc_sleeping;
> +				q = dev->ingress_queue.qdisc_sleeping;
>  			}
>  		} else {
>  			q = dev->qdisc;
> @@ -1124,7 +1124,7 @@ create_n_graft:
>  	if (!(n->nlmsg_flags&NLM_F_CREATE))
>  		return -ENOENT;
>  	if (clid == TC_H_INGRESS)
> -		q = qdisc_create(dev, &dev->rx_queue, p,
> +		q = qdisc_create(dev, &dev->ingress_queue, p,
>  				 tcm->tcm_parent, tcm->tcm_parent,
>  				 tca, &err);
>  	else {
> @@ -1304,7 +1304,7 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
>  		if (tc_dump_qdisc_root(dev->qdisc, skb, cb, &q_idx, s_q_idx) < 0)
>  			goto done;
>  
> -		dev_queue = &dev->rx_queue;
> +		dev_queue = &dev->ingress_queue;
>  		if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
>  			goto done;
>  
> @@ -1595,7 +1595,7 @@ static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
>  	if (tc_dump_tclass_root(dev->qdisc, skb, tcm, cb, &t, s_t) < 0)
>  		goto done;
>  
> -	dev_queue = &dev->rx_queue;
> +	dev_queue = &dev->ingress_queue;
>  	if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
>  		goto done;
>  
> diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> index 2aeb3a4..545278a 100644
> --- a/net/sched/sch_generic.c
> +++ b/net/sched/sch_generic.c
> @@ -753,7 +753,7 @@ void dev_activate(struct net_device *dev)
>  
>  	need_watchdog = 0;
>  	netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);
> -	transition_one_qdisc(dev, &dev->rx_queue, NULL);
> +	transition_one_qdisc(dev, &dev->ingress_queue, NULL);
>  
>  	if (need_watchdog) {
>  		dev->trans_start = jiffies;
> @@ -812,7 +812,7 @@ static bool some_qdisc_is_busy(struct net_device *dev)
>  void dev_deactivate(struct net_device *dev)
>  {
>  	netdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);
> -	dev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);
> +	dev_deactivate_queue(dev, &dev->ingress_queue, &noop_qdisc);
>  
>  	dev_watchdog_down(dev);
>  
> @@ -838,7 +838,7 @@ void dev_init_scheduler(struct net_device *dev)
>  {
>  	dev->qdisc = &noop_qdisc;
>  	netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
> -	dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
> +	dev_init_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
>  
>  	setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
>  }
> @@ -861,7 +861,7 @@ static void shutdown_scheduler_queue(struct net_device *dev,
>  void dev_shutdown(struct net_device *dev)
>  {
>  	netdev_for_each_tx_queue(dev, shutdown_scheduler_queue, &noop_qdisc);
> -	shutdown_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
> +	shutdown_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
>  	qdisc_destroy(dev->qdisc);
>  	dev->qdisc = &noop_qdisc;
>  
> 
> 

^ permalink raw reply

* Re: [IPoIB] Identify multicast packets and fix IGMP breakage V3
From: Roland Dreier @ 2010-09-28 18:09 UTC (permalink / raw)
  To: Christoph Lameter
  Cc: Jason Gunthorpe, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
	ogerlitz-smomgflXvOZWk0Htik3J/w, yosefe-smomgflXvOZWk0Htik3J/w,
	netdev-u79uwXL29TY76Z2rM5mHXA, David Miller
In-Reply-To: <alpine.DEB.2.00.1008270827280.11792-sBS69tsa9Uj/9pzu0YdTqQ@public.gmane.org>

thanks, applied.
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH] Add Qualcomm Gobi 2000 driver.
From: Joe Perches @ 2010-09-28 18:11 UTC (permalink / raw)
  To: Elly Jones; +Cc: netdev, dbrownell, mjg59, jglasgow, msb, olofj
In-Reply-To: <20100928171026.GB6083@google.com>

On Tue, 2010-09-28 at 13:10 -0400, Elly Jones wrote:
> From: Elizabeth Jones <ellyjones@google.com>

Hi.

The readme is out of date and should be updated.

[]

> +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
> +	usbnet = usb_get_intfdata(iface);
> +#else
> +	usbnet = iface->dev.platform_data;
> +#endif

I think it's not particularly useful to include support
for versions < current when this driver is added.

[]

> +#define DBG(format, arg...) \
> +	if (debug == 1) { \
> +		printk(KERN_INFO "QCUSBNet2k::%s " format, __FUNCTION__, ## arg); \
> +	}

This should be surrounded by a do {} while(0)

#define DBG(fmt, arg...)							\
do {										\
	if (debug == 1)								\
		printk(KERN_INFO "QCUSBNet2k::%s " fmt, __func__, ##arg);	\
} while (0)

I think many of these DBG messages should
not use DBG but the more normal pr_<level>,
dev_<level> or netdev_<level> so generally
useful messages are logged without getting
all the debugging messages.



^ permalink raw reply

* [PATCH net-next 2.6] myri10ge: DCA update (resubmit)
From: Andrew Gallatin @ 2010-09-28 18:13 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, Loic Prylli

[-- Attachment #1: Type: text/plain, Size: 760 bytes --]

This patch contains the following DCA improvements to myri10ge:

1) Finally move myri10ge to use dca3 API

2) Disable PCIe relaxed ordering when enabling DCA on
     myri10ge.  This provides a performance boost on Nehalem
     based Xeons

3) Make sure to properly initialize NIC's DCA state when it is enabled,
     rather than giving the NIC a bogus tag (0) and waiting for
     the first received packet to trigger an update.  Not using a
     real tag can cause hardware exceptions on some motherboards
     when a CPU socket is empty.

3) Always update the cached CPU when our interrupt affinity changes
     so as to avoid excessive calls to dca3_get_tag()

Signed-off-by: Andrew Gallatin <gallatin@myri.com>
Signed-off-by: Loic Prylli <loic@myri.com>



[-- Attachment #2: myri10ge_dca_2.diff --]
[-- Type: text/x-diff, Size: 2216 bytes --]

diff --git a/drivers/net/myri10ge/myri10ge.c b/drivers/net/myri10ge/myri10ge.c
index 4f3a3c0..02dd92e 100644
--- a/drivers/net/myri10ge/myri10ge.c
+++ b/drivers/net/myri10ge/myri10ge.c
@@ -225,6 +225,7 @@ struct myri10ge_priv {
 	struct msix_entry *msix_vectors;
 #ifdef CONFIG_MYRI10GE_DCA
 	int dca_enabled;
+	int relaxed_order;
 #endif
 	u32 link_state;
 	unsigned int rdma_tags_available;
@@ -1074,10 +1075,28 @@ static int myri10ge_reset(struct myri10ge_priv *mgp)
 }
 
 #ifdef CONFIG_MYRI10GE_DCA
+static int myri10ge_toggle_relaxed(struct pci_dev *pdev, int on)
+{
+	int ret, cap, err;
+	u16 ctl;
+
+	cap = pci_find_capability(pdev, PCI_CAP_ID_EXP);
+	if (!cap)
+		return 0;
+
+	err = pci_read_config_word(pdev, cap + PCI_EXP_DEVCTL, &ctl);
+	ret = (ctl & PCI_EXP_DEVCTL_RELAX_EN) >> 4;
+	if (ret != on) {
+		ctl &= ~PCI_EXP_DEVCTL_RELAX_EN;
+		ctl |= (on << 4);
+		pci_write_config_word(pdev, cap + PCI_EXP_DEVCTL, ctl);
+	}
+	return ret;
+}
+
 static void
 myri10ge_write_dca(struct myri10ge_slice_state *ss, int cpu, int tag)
 {
-	ss->cpu = cpu;
 	ss->cached_dca_tag = tag;
 	put_be32(htonl(tag), ss->dca_tag);
 }
@@ -1088,9 +1107,10 @@ static inline void myri10ge_update_dca(struct myri10ge_slice_state *ss)
 	int tag;
 
 	if (cpu != ss->cpu) {
-		tag = dca_get_tag(cpu);
+		tag = dca3_get_tag(&ss->mgp->pdev->dev, cpu);
 		if (ss->cached_dca_tag != tag)
 			myri10ge_write_dca(ss, cpu, tag);
+		ss->cpu = cpu;
 	}
 	put_cpu();
 }
@@ -1113,9 +1133,13 @@ static void myri10ge_setup_dca(struct myri10ge_priv *mgp)
 				"dca_add_requester() failed, err=%d\n", err);
 		return;
 	}
+	mgp->relaxed_order = myri10ge_toggle_relaxed(pdev, 0);
 	mgp->dca_enabled = 1;
-	for (i = 0; i < mgp->num_slices; i++)
-		myri10ge_write_dca(&mgp->ss[i], -1, 0);
+	for (i = 0; i < mgp->num_slices; i++) {
+		mgp->ss[i].cpu = -1;
+		mgp->ss[i].cached_dca_tag = -1;
+		myri10ge_update_dca(&mgp->ss[i]);
+	 }
 }
 
 static void myri10ge_teardown_dca(struct myri10ge_priv *mgp)
@@ -1126,6 +1150,8 @@ static void myri10ge_teardown_dca(struct myri10ge_priv *mgp)
 	if (!mgp->dca_enabled)
 		return;
 	mgp->dca_enabled = 0;
+	if (mgp->relaxed_order)
+		myri10ge_toggle_relaxed(pdev, 1);
 	err = dca_remove_requester(&pdev->dev);
 }
 

^ permalink raw reply related

* [PATCH] de2104x: disable media debug messages by default
From: Ondrej Zary @ 2010-09-28 18:18 UTC (permalink / raw)
  To: jgarzik; +Cc: netdev, Kernel development list

Print media debug messages only when HW debug is enabled.

Signed-off-by: Ondrej Zary <linux@rainbow-software.org>

--- linux-2.6.36-rc3-/drivers/net/tulip/de2104x.c	2010-09-28 19:50:51.000000000 +0200
+++ linux-2.6.36-rc3/drivers/net/tulip/de2104x.c	2010-09-28 20:05:34.000000000 +0200
@@ -948,8 +948,9 @@ static void de_set_media (struct de_priv
 	else
 		macmode &= ~FullDuplex;
 
-	if (netif_msg_link(de)) {
+	if (netif_msg_link(de))
 		dev_info(&de->dev->dev, "set link %s\n", media_name[media]);
+	if (netif_msg_hw(de)) {
 		dev_info(&de->dev->dev, "mode 0x%x, sia 0x%x,0x%x,0x%x,0x%x\n",
 			 dr32(MacMode), dr32(SIAStatus),
 			 dr32(CSR13), dr32(CSR14), dr32(CSR15));


-- 
Ondrej Zary

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox