Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH 6/6] r8169: print errors when dma mapping fail
From: Stanislaw Gruszka @ 2010-10-15 15:59 UTC (permalink / raw)
  To: Francois Romieu; +Cc: netdev, Denis Kirjanov
In-Reply-To: <20101015145201.GB4417@electric-eye.fr.zoreil.com>

On Fri, Oct 15, 2010 at 04:52:01PM +0200, Francois Romieu wrote:
> Stanislaw Gruszka <sgruszka@redhat.com> :
> > Print errors because dma mapping failures can cause device to stop
> > working and will need user intervention to recover.
> 
> I am hesitating (overengineered ? bloaty ? not the right place ?).

As someone who seen lot's of bug reports like "my network device stops
working, nothing in dmesg", or like "my network device stops working,
there is NETDEV WATCHDOG: eth0 (r8169): transmit queue 0 timed out in
dmesg" (what is nothing but useful information), I do no think this is
overengineered or bloaty. I could agree for "not the right place", but
even if the error would be reported by upper layers, exact reason of
the problem will be unknown. Regarding lower layers, I don't think iommu
or other dma code print warning with calltrace in case of failure.

> The Tx stats are kept up-to-date : Tx failure will go along a Tx drop
> stat increase.

In current implementation, I stop tx queue on dma errors, if that
happens the queue can never be started again. I will probably change
that as you suggest not returning NETDEV_TX_BUSY, stopping the queue
is also wrong. But I would like to keep this error messages, perhaps
after adding net_ratelimit() check.
 
> Regarding a mapping failure in the Rx path, either it will behave as
> an allocation failure at open / resume time -

Still it's worth to know exact reason of failure.

> and I have no idea how
> the user will recover - or it will happen during a Rx ring refill.

ifconfig eth0 down/up or reloading module

Stanislaw

^ permalink raw reply

* Re: [PATCH 3/7] drivers/net/wireless/p54/eeprom.c: Return -ENOMEM on memory allocation failure
From: Christian Lamparter @ 2010-10-15 15:49 UTC (permalink / raw)
  To: Julia Lawall
  Cc: kernel-janitors-u79uwXL29TY76Z2rM5mHXA, John W. Linville,
	linux-wireless-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1287147610-8041-3-git-send-email-julia-dAYI7NvHqcQ@public.gmane.org>

On Friday 15 October 2010 15:00:06 Julia Lawall wrote:
> From: Julia Lawall <julia-dAYI7NvHqcQ@public.gmane.org>
> 
> In this code, 0 is returned on memory allocation failure, even though other
> failures return -ENOMEM or other similar values.
> 
> A simplified version of the semantic match that finds this problem is as
> follows: (http://coccinelle.lip6.fr/)
> 
> // <smpl>
> @@
> expression ret;
> expression x,e1,e2,e3;
> @@
> 
> ret = 0
> ... when != ret = e1
> *x = \(kmalloc\|kcalloc\|kzalloc\)(...)
> ... when != ret = e2
> if (x == NULL) { ... when != ret = e3
>   return ret;
> }
> // </smpl>
> 
> Signed-off-by: Julia Lawall <julia-dAYI7NvHqcQ@public.gmane.org>
Cc: <stable-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Christian Lamparter <chunkeey-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

> ---
>  drivers/net/wireless/p54/eeprom.c |    4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
> 
> diff -u -p a/drivers/net/wireless/p54/eeprom.c b/drivers/net/wireless/p54/eeprom.c
> --- a/drivers/net/wireless/p54/eeprom.c
> +++ b/drivers/net/wireless/p54/eeprom.c
> @@ -261,8 +261,10 @@ static int p54_generate_channel_lists(st
>  	list->max_entries = max_channel_num;
>  	list->channels = kzalloc(sizeof(struct p54_channel_entry) *
>  				 max_channel_num, GFP_KERNEL);
> -	if (!list->channels)
> +	if (!list->channels) {
> +		ret = -ENOMEM;
>  		goto free;
> +	}
>  
>  	for (i = 0; i < max_channel_num; i++) {
>  		if (i < priv->iq_autocal_len) {
> 
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" 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

* [PATCH net-next] net: avoid RCU for NOCACHE dst
From: Eric Dumazet @ 2010-10-15 15:44 UTC (permalink / raw)
  To: David Miller; +Cc: netdev

There is no point using RCU for dst we allocate for a very short time
(used once).

Change dst_release() to take DST_NOCACHE into account, but also change
skb_dst_set_noref() to force a refcount increment for such dst.

This is a _huge_ gain, because we dont waste memory to store xx thousand
of dsts. Instead of queueing them to RCU, we can free them instantly. 

CPU caches can stay hot, re-using same memory blocks to hold temporary
dsts.

Note : remove unneeded smp_mb__before_atomic_dec(); in dst_release(),
since atomic_dec_return() implies a full memory barrier.

Stress test, 160.000.000 udp frames sent, IP route cache disabled
(DDOS).

Before:

real    0m38.091s
user    0m13.189s
sys     7m53.018s

After:

real	0m29.946s
user	0m12.157s
sys	7m40.605s

For reference, if IP route cache was enabled :

real	0m32.030s
user	0m10.521s
sys	8m15.243s

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
 include/linux/skbuff.h |   14 +-------------
 net/core/dst.c         |   29 ++++++++++++++++++++++++++++-
 net/ipv4/route.c       |    9 ++++-----
 3 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 0b53c43..9ccbbbd 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -460,19 +460,7 @@ static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
 	skb->_skb_refdst = (unsigned long)dst;
 }
 
-/**
- * skb_dst_set_noref - sets skb dst, without a reference
- * @skb: buffer
- * @dst: dst entry
- *
- * Sets skb dst, assuming a reference was not taken on dst
- * skb_dst_drop() should not dst_release() this dst
- */
-static inline void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst)
-{
-	WARN_ON(!rcu_read_lock_held() && !rcu_read_lock_bh_held());
-	skb->_skb_refdst = (unsigned long)dst | SKB_DST_NOREF;
-}
+extern void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst);
 
 /**
  * skb_dst_is_noref - Test if skb dst isnt refcounted
diff --git a/net/core/dst.c b/net/core/dst.c
index 32e542d..8abe628 100644
--- a/net/core/dst.c
+++ b/net/core/dst.c
@@ -271,13 +271,40 @@ void dst_release(struct dst_entry *dst)
 	if (dst) {
 		int newrefcnt;
 
-		smp_mb__before_atomic_dec();
 		newrefcnt = atomic_dec_return(&dst->__refcnt);
 		WARN_ON(newrefcnt < 0);
+		if (unlikely(dst->flags & DST_NOCACHE) && !newrefcnt) {
+			dst = dst_destroy(dst);
+			if (dst)
+				__dst_free(dst);
+		}
 	}
 }
 EXPORT_SYMBOL(dst_release);
 
+/**
+ * skb_dst_set_noref - sets skb dst, without a reference
+ * @skb: buffer
+ * @dst: dst entry
+ *
+ * Sets skb dst, assuming a reference was not taken on dst
+ * skb_dst_drop() should not dst_release() this dst
+ */
+void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst)
+{
+	WARN_ON(!rcu_read_lock_held() && !rcu_read_lock_bh_held());
+	/* If dst not in cache, we must take a reference, because
+	 * dst_release() will destroy dst as soon as its refcount becomes zero
+	 */
+	if (unlikely(dst->flags & DST_NOCACHE)) {
+		dst_hold(dst);
+		skb_dst_set(skb, dst);
+	} else {
+		skb->_skb_refdst = (unsigned long)dst | SKB_DST_NOREF;
+	}
+}
+EXPORT_SYMBOL(skb_dst_set_noref);
+
 /* Dirty hack. We did it in 2.2 (in __dst_free),
  * we have _very_ good reasons not to repeat
  * this mistake in 2.3, but we have no choice
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 0755aa4..f2ce07b 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1105,9 +1105,9 @@ restart:
 		 * Note that we do rt_free on this new route entry, so that
 		 * once its refcount hits zero, we are still able to reap it
 		 * (Thanks Alexey)
-		 * Note also the rt_free uses call_rcu.  We don't actually
-		 * need rcu protection here, this is just our path to get
-		 * on the route gc list.
+		 * Note: To avoid expensive rcu stuff for this uncached dst,
+		 * we set DST_NOCACHE so that dst_release() can free dst without
+		 * waiting a grace period.
 		 */
 
 		rt->dst.flags |= DST_NOCACHE;
@@ -1117,12 +1117,11 @@ restart:
 				if (net_ratelimit())
 					printk(KERN_WARNING
 					    "Neighbour table failure & not caching routes.\n");
-				rt_drop(rt);
+				ip_rt_put(rt);
 				return err;
 			}
 		}
 
-		rt_free(rt);
 		goto skip_hashing;
 	}
 



^ permalink raw reply related

* [PATCH V2 net-next] can-raw: add msg_flags to distinguish local traffic
From: Kurt Van Dijck @ 2010-10-15 15:38 UTC (permalink / raw)
  To: netdev, socketcan-core-0fE9KPoRgkgATYTw5x5z8w; +Cc: Oliver Hartkopp

differences in V2:
1) use 'unsigned int' for flags, conform to the msg_flags
   in the kernel headers (glibc uses 'int')
2) fixed typo in comments
3) Add description in Documentation
4) correct email address in Signed-off-by

CAN has no addressing scheme. It is currently impossible
for userspace to tell is a received CAN frame comes from
another process on the local host, or from a remote CAN
device.
This patch add support for userspace applications to distinguish
between 'own', 'local' and 'remote' CAN traffic.
Distinction is made by returning flags in msg->msg_flags
in the call to recvmsg.
The Documentation/...  explains the flags.

Signed-off-by: Kurt Van Dijck <kurt.van.dijck-/BeEPy95v10@public.gmane.org>
---
 Documentation/networking/can.txt |   11 +++++++++++
 net/can/raw.c                    |   33 ++++++++++++++++++++++++++++++---
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/Documentation/networking/can.txt b/Documentation/networking/can.txt
index cd79735..95341aa 100644
--- a/Documentation/networking/can.txt
+++ b/Documentation/networking/can.txt
@@ -22,6 +22,7 @@ This file contains
       4.1.2 RAW socket option CAN_RAW_ERR_FILTER
       4.1.3 RAW socket option CAN_RAW_LOOPBACK
       4.1.4 RAW socket option CAN_RAW_RECV_OWN_MSGS
+      4.1.5 RAW socket returned flags
     4.2 Broadcast Manager protocol sockets (SOCK_DGRAM)
     4.3 connected transport protocols (SOCK_SEQPACKET)
     4.4 unconnected transport protocols (SOCK_DGRAM)
@@ -471,6 +472,16 @@ solution for a couple of reasons:
     setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
                &recv_own_msgs, sizeof(recv_own_msgs));
 
+  4.1.5 RAW socket returned flags
+
+  When using recvmsg() call, the msg->msg_flags may contain following flags:
+
+    MSG_DONTROUTE: set when the frame was sent via the same physical device,
+	ie. a loopback frame.
+    MSG_CONFIRM: set when the frame was sent via the socket it is received on.
+	This flag acts as a 'transmission confirmation'.
+	In order to receive such messages, CAN_RAW_RECV_OWN_MSGS must be set.
+
   4.2 Broadcast Manager protocol sockets (SOCK_DGRAM)
   4.3 connected transport protocols (SOCK_SEQPACKET)
   4.4 unconnected transport protocols (SOCK_DGRAM)
diff --git a/net/can/raw.c b/net/can/raw.c
index 7d77e67..9020c4f 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -90,23 +90,39 @@ struct raw_sock {
 	can_err_mask_t err_mask;
 };
 
+/*
+ * return some space to store extra msg flags in.
+ * We use 1 int beyond the 'struct sockaddr_can' in skb->cb
+ * to store those.
+ * These flags will be use in raw_recvmsg()
+ */
+static inline unsigned int *raw_flags(struct sk_buff *skb)
+{
+	BUILD_BUG_ON(sizeof(skb->cb) <= (sizeof(struct sockaddr_can)
+				+ sizeof(unsigned int)));
+	/* return pointer after struct sockaddr_can */
+	return (unsigned int *)(&((struct sockaddr_can *)skb->cb)[1]);
+}
+
 static inline struct raw_sock *raw_sk(const struct sock *sk)
 {
 	return (struct raw_sock *)sk;
 }
 
-static void raw_rcv(struct sk_buff *skb, void *data)
+static void raw_rcv(struct sk_buff *oskb, void *data)
 {
 	struct sock *sk = (struct sock *)data;
 	struct raw_sock *ro = raw_sk(sk);
 	struct sockaddr_can *addr;
+	struct sk_buff *skb;
+	unsigned int *pflags;
 
 	/* check the received tx sock reference */
-	if (!ro->recv_own_msgs && skb->sk == sk)
+	if (!ro->recv_own_msgs && oskb->sk == sk)
 		return;
 
 	/* clone the given skb to be able to enqueue it into the rcv queue */
-	skb = skb_clone(skb, GFP_ATOMIC);
+	skb = skb_clone(oskb, GFP_ATOMIC);
 	if (!skb)
 		return;
 
@@ -123,6 +139,14 @@ static void raw_rcv(struct sk_buff *skb, void *data)
 	addr->can_family  = AF_CAN;
 	addr->can_ifindex = skb->dev->ifindex;
 
+	/* prepare the flags for raw_recvmsg() */
+	pflags = raw_flags(skb);
+	*pflags = 0;
+	if (oskb->sk)
+		*pflags |= MSG_DONTROUTE;
+	if (oskb->sk == sk)
+		*pflags |= MSG_CONFIRM;
+
 	if (sock_queue_rcv_skb(sk, skb) < 0)
 		kfree_skb(skb);
 }
@@ -707,6 +731,9 @@ static int raw_recvmsg(struct kiocb *iocb, struct socket *sock,
 		memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
 	}
 
+	/* assign the flags that have been recorded in raw_rcv() */
+	msg->msg_flags |= *(raw_flags(skb));
+
 	skb_free_datagram(sk, skb);
 
 	return size;

^ permalink raw reply related

* Re: [PATCH net-next] bonding: make release_and_destroy static
From: Andy Gospodarek @ 2010-10-15 15:37 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: Jay Vosburgh, David Miller, bonding-devel, netdev
In-Reply-To: <20101015080934.6dc28388@nehalam>

On Fri, Oct 15, 2010 at 08:09:34AM -0700, Stephen Hemminger wrote:
> Only used in main file.
> 
> Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
> 
> --- a/drivers/net/bonding/bond_main.c	2010-10-15 08:07:14.163898236 -0700
> +++ b/drivers/net/bonding/bond_main.c	2010-10-15 08:07:31.956373660 -0700
> @@ -2057,8 +2057,8 @@ int bond_release(struct net_device *bond
>  * First release a slave and than destroy the bond if no more slaves are left.
>  * Must be under rtnl_lock when this function is called.
>  */
> -int  bond_release_and_destroy(struct net_device *bond_dev,
> -			      struct net_device *slave_dev)
> +static int  bond_release_and_destroy(struct net_device *bond_dev,
> +				     struct net_device *slave_dev)
>  {
>  	struct bonding *bond = netdev_priv(bond_dev);
>  	int ret;
> --- a/drivers/net/bonding/bonding.h	2010-10-15 08:07:14.179898632 -0700
> +++ b/drivers/net/bonding/bonding.h	2010-10-15 08:07:31.956373660 -0700
> @@ -334,7 +334,6 @@ static inline void bond_unset_master_alb
>  struct vlan_entry *bond_next_vlan(struct bonding *bond, struct vlan_entry *curr);
>  int bond_dev_queue_xmit(struct bonding *bond, struct sk_buff *skb, struct net_device *slave_dev);
>  int bond_create(struct net *net, const char *name);
> -int  bond_release_and_destroy(struct net_device *bond_dev, struct net_device *slave_dev);
>  int bond_create_sysfs(void);
>  void bond_destroy_sysfs(void);
>  void bond_prepare_sysfs_group(struct bonding *bond);

Seems fine.

Acked-by: Andy Gospodarek <andy@greyhouse.net>


^ permalink raw reply

* Re: netlink versus pid namespaces
From: Andi Kleen @ 2010-10-15 15:23 UTC (permalink / raw)
  To: Alexey Kuznetsov; +Cc: Andi Kleen, netdev, xemul, virtualization
In-Reply-To: <20101015151903.GA3487@ms2.inr.ac.ru>

On Fri, Oct 15, 2010 at 07:19:03PM +0400, Alexey Kuznetsov wrote:
> Hello!
> 
> > netlink uses pids (or really tids I hope?) to address sockets
> > associated with processes.
> 
> Not really. It uses port number which is called "pid" occasionally. Bad name.
> Autobind function simply selects tgid of calling process as the first guess.

Thanks for the clarification, Alexey. I guess I should have read more
code :/

-Andi

^ permalink raw reply

* Re: netlink versus pid namespaces
From: Alexey Kuznetsov @ 2010-10-15 15:19 UTC (permalink / raw)
  To: Andi Kleen; +Cc: netdev, xemul, virtualization
In-Reply-To: <20101015142357.GA29321@basil.fritz.box>

Hello!

> netlink uses pids (or really tids I hope?) to address sockets
> associated with processes.

Not really. It uses port number which is called "pid" occasionally. Bad name.
Autobind function simply selects tgid of calling process as the first guess.

Actually sockets are addressed by pair (net namespace, port) and
communication is possible only inside net namespace. So, communication
between namespaces is already prohibited.

pid namespaces do not participate in the picture at all.

Alexey

^ permalink raw reply

* [PATCH net-next] xfrm6: make xfrm6_tunnel_free_spi local
From: Stephen Hemminger @ 2010-10-15 15:15 UTC (permalink / raw)
  To: David Miller, Herbert Xu, YOSHIFUJI Hideaki; +Cc: netdev

Function only defined and used in one file.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>

--- a/include/net/xfrm.h	2010-10-05 23:33:55.959118983 +0900
+++ b/include/net/xfrm.h	2010-10-05 23:34:05.079117188 +0900
@@ -1419,7 +1419,6 @@ extern int xfrm6_input_addr(struct sk_bu
 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);
-extern void xfrm6_tunnel_free_spi(struct net *net, xfrm_address_t *saddr);
 extern __be32 xfrm6_tunnel_spi_lookup(struct net *net, xfrm_address_t *saddr);
 extern int xfrm6_extract_output(struct xfrm_state *x, struct sk_buff *skb);
 extern int xfrm6_prepare_output(struct xfrm_state *x, struct sk_buff *skb);
--- a/net/ipv6/xfrm6_tunnel.c	2010-10-05 23:33:56.039118393 +0900
+++ b/net/ipv6/xfrm6_tunnel.c	2010-10-05 23:34:21.011117560 +0900
@@ -199,7 +199,7 @@ static void x6spi_destroy_rcu(struct rcu
 			container_of(head, struct xfrm6_tunnel_spi, rcu_head));
 }
 
-void xfrm6_tunnel_free_spi(struct net *net, xfrm_address_t *saddr)
+static void xfrm6_tunnel_free_spi(struct net *net, xfrm_address_t *saddr)
 {
 	struct xfrm6_tunnel_net *xfrm6_tn = xfrm6_tunnel_pernet(net);
 	struct xfrm6_tunnel_spi *x6spi;
@@ -223,8 +223,6 @@ void xfrm6_tunnel_free_spi(struct net *n
 	spin_unlock_bh(&xfrm6_tunnel_spi_lock);
 }
 
-EXPORT_SYMBOL(xfrm6_tunnel_free_spi);
-
 static int xfrm6_tunnel_output(struct xfrm_state *x, struct sk_buff *skb)
 {
 	skb_push(skb, -skb_network_offset(skb));

^ permalink raw reply

* [PATCH net-next] xfrm: make xfrm_bundle_ok local
From: Stephen Hemminger @ 2010-10-15 15:14 UTC (permalink / raw)
  To: Herbert Xu, David Miller; +Cc: netdev

Only used in one place.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>

--- a/include/net/xfrm.h	2010-10-05 23:27:43.971148720 +0900
+++ b/include/net/xfrm.h	2010-10-05 23:27:54.631127801 +0900
@@ -1466,8 +1466,6 @@ struct xfrm_state *xfrm_find_acq(struct 
 				 xfrm_address_t *saddr, int create,
 				 unsigned short family);
 extern int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol);
-extern int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *xdst,
-			  struct flowi *fl, int family, int strict);
 
 #ifdef CONFIG_XFRM_MIGRATE
 extern int km_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
--- a/net/xfrm/xfrm_policy.c	2010-10-05 23:27:43.995125930 +0900
+++ b/net/xfrm/xfrm_policy.c	2010-10-05 23:28:27.244135115 +0900
@@ -50,6 +50,9 @@ static struct xfrm_policy_afinfo *xfrm_p
 static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo);
 static void xfrm_init_pmtu(struct dst_entry *dst);
 static int stale_bundle(struct dst_entry *dst);
+static int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *xdst,
+			  struct flowi *fl, int family, int strict);
+
 
 static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,
 						int dir);
@@ -2276,7 +2279,7 @@ static void xfrm_init_pmtu(struct dst_en
  * still valid.
  */
 
-int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
+static int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
 		struct flowi *fl, int family, int strict)
 {
 	struct dst_entry *dst = &first->u.dst;
@@ -2358,8 +2361,6 @@ int xfrm_bundle_ok(struct xfrm_policy *p
 	return 1;
 }
 
-EXPORT_SYMBOL(xfrm_bundle_ok);
-
 int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
 {
 	struct net *net;

^ permalink raw reply

* [PATCH net-next] rtnetlink: remove rtnl_kill_links
From: Stephen Hemminger @ 2010-10-15 15:12 UTC (permalink / raw)
  To: David Miller; +Cc: netdev

The function rtnl_kill_links is defined but never used.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>

--- a/include/net/rtnetlink.h	2010-10-02 15:28:38.350854857 +0900
+++ b/include/net/rtnetlink.h	2010-10-02 15:29:42.650840398 +0900
@@ -79,7 +79,6 @@ struct rtnl_link_ops {
 
 extern int	__rtnl_link_register(struct rtnl_link_ops *ops);
 extern void	__rtnl_link_unregister(struct rtnl_link_ops *ops);
-extern void	rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops);
 
 extern int	rtnl_link_register(struct rtnl_link_ops *ops);
 extern void	rtnl_link_unregister(struct rtnl_link_ops *ops);
--- a/net/core/rtnetlink.c	2010-10-02 15:28:38.370839030 +0900
+++ b/net/core/rtnetlink.c	2010-10-02 15:29:42.660839574 +0900
@@ -299,14 +299,6 @@ static void __rtnl_kill_links(struct net
 	unregister_netdevice_many(&list_kill);
 }
 
-void rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops)
-{
-	rtnl_lock();
-	__rtnl_kill_links(net, ops);
-	rtnl_unlock();
-}
-EXPORT_SYMBOL_GPL(rtnl_kill_links);
-
 /**
  * __rtnl_link_unregister - Unregister rtnl_link_ops from rtnetlink.
  * @ops: struct rtnl_link_ops * to unregister

^ permalink raw reply

* [PATCH net-next] bonding: make release_and_destroy static
From: Stephen Hemminger @ 2010-10-15 15:09 UTC (permalink / raw)
  To: Jay Vosburgh, David Miller; +Cc: bonding-devel, netdev

Only used in main file.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>

--- a/drivers/net/bonding/bond_main.c	2010-10-15 08:07:14.163898236 -0700
+++ b/drivers/net/bonding/bond_main.c	2010-10-15 08:07:31.956373660 -0700
@@ -2057,8 +2057,8 @@ int bond_release(struct net_device *bond
 * First release a slave and than destroy the bond if no more slaves are left.
 * Must be under rtnl_lock when this function is called.
 */
-int  bond_release_and_destroy(struct net_device *bond_dev,
-			      struct net_device *slave_dev)
+static int  bond_release_and_destroy(struct net_device *bond_dev,
+				     struct net_device *slave_dev)
 {
 	struct bonding *bond = netdev_priv(bond_dev);
 	int ret;
--- a/drivers/net/bonding/bonding.h	2010-10-15 08:07:14.179898632 -0700
+++ b/drivers/net/bonding/bonding.h	2010-10-15 08:07:31.956373660 -0700
@@ -334,7 +334,6 @@ static inline void bond_unset_master_alb
 struct vlan_entry *bond_next_vlan(struct bonding *bond, struct vlan_entry *curr);
 int bond_dev_queue_xmit(struct bonding *bond, struct sk_buff *skb, struct net_device *slave_dev);
 int bond_create(struct net *net, const char *name);
-int  bond_release_and_destroy(struct net_device *bond_dev, struct net_device *slave_dev);
 int bond_create_sysfs(void);
 void bond_destroy_sysfs(void);
 void bond_prepare_sysfs_group(struct bonding *bond);

^ permalink raw reply

* Re: [PATCH] secmark: do not return early if there was no error
From: Patrick McHardy @ 2010-10-15 15:08 UTC (permalink / raw)
  To: Eric Paris
  Cc: netfilter-devel, netfilter, coreteam, netdev, davem, jengelh,
	paul.moore, jmorris
In-Reply-To: <20101013202105.15011.60553.stgit@paris.rdu.redhat.com>

Am 13.10.2010 22:21, schrieb Eric Paris:
> Commit 4a5a5c73 attempted to pass decent error messages back to userspace for
> netfilter errors.  In xt_SECMARK.c however the patch screwed up and returned
> on 0 (aka no error) early and didn't finish setting up secmark.  This results
> in a kernel BUG if you use SECMARK.
> 
> ------------[ cut here ]------------
> kernel BUG at net/netfilter/xt_SECMARK.c:38!
> invalid opcode: 0000 [#1] SMP
> last sysfs file: /sys/devices/system/cpu/cpu2/cache/index2/shared_cpu_map
> CPU 0
> Modules linked in: xt_SECMARK iptable_mangle nfs lockd fscache nfs_acl
> auth_rpcgss sunrpc ip6t_REJECT nf_conntrack_ipv6 ip6table_filter ip6_tables
> uinput virtio_net virtio_balloon i2c_piix4 i2c_core joydev microcode ipv6
> virtio_blk virtio_pci virtio_ring virtio [last unloaded: speedstep_lib]
> 
> ...
> RIP  [<ffffffffa022117d>] secmark_tg+0x17/0x2e [xt_SECMARK]
> RSP <ffff880003e03a40>
> ---[ end trace 9aa5d06a71143e74 ]---
> 
> Signed-off-by: Eric Paris <eparis@redhat.com>
> Acked-by: Paul Moore <paul.moore@hp.com>
> Acked-by: James Morris <jmorris@namei.org>

Acked-by: Patrick McHardy <kaber@trash.net>

I'll leave it up to Dave whether this can still go into 2.6.36.

^ permalink raw reply

* [PATCH] net/nuc900: change dev_warn to dev_dbg when link down occurs
From: Wan ZongShun @ 2010-10-15 15:00 UTC (permalink / raw)
  To: netdev, yachen, David S. Miller, linux-kernel

Hi David,

When I didnot connect the net cable, the warning infos are always showed,
so I change the dev_warn to dev_dbg.

Signed-off-by: Wan ZongShun <mcuos.com@gmail.com>

---
  drivers/net/arm/w90p910_ether.c |    2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/net/arm/w90p910_ether.c b/drivers/net/arm/w90p910_ether.c
index 4545d5a..98f8ada 100644
--- a/drivers/net/arm/w90p910_ether.c
+++ b/drivers/net/arm/w90p910_ether.c
@@ -213,7 +213,7 @@ static void update_linkspeed(struct net_device *dev)
  	if (!mii_link_ok(&ether->mii)) {
  		ether->linkflag = 0x0;
  		netif_carrier_off(dev);
-		dev_warn(&pdev->dev, "%s: Link down.\n", dev->name);
+		dev_dbg(&pdev->dev, "%s: Link down.\n", dev->name);
  		return;
  	}

-- 
1.6.3.3

^ permalink raw reply related

* Re: [PATCH 6/6] r8169: print errors when dma mapping fail
From: Francois Romieu @ 2010-10-15 14:52 UTC (permalink / raw)
  To: Stanislaw Gruszka; +Cc: netdev, Denis Kirjanov
In-Reply-To: <1287144922-3297-6-git-send-email-sgruszka@redhat.com>

Stanislaw Gruszka <sgruszka@redhat.com> :
> Print errors because dma mapping failures can cause device to stop
> working and will need user intervention to recover.

I am hesitating (overengineered ? bloaty ? not the right place ?).

The Tx stats are kept up-to-date : Tx failure will go along a Tx drop
stat increase.

Regarding a mapping failure in the Rx path, either it will behave as
an allocation failure at open / resume time - and I have no idea how
the user will recover - or it will happen during a Rx ring refill.

...

Ok, something could have gone unnoticed. Let's try it.

-- 
Ueimor

^ permalink raw reply

* Re: [PATCH 1/6] r8169: check dma mapping failures
From: Denis Kirjanov @ 2010-10-15 14:23 UTC (permalink / raw)
  To: Francois Romieu; +Cc: Stanislaw Gruszka, netdev, David S. Miller
In-Reply-To: <20101015134158.GA4417@electric-eye.fr.zoreil.com>

Right, we should pass TX_BUSY to upper layers only when the device hw
queue is full

On Fri, Oct 15, 2010 at 5:41 PM, Francois Romieu <romieu@fr.zoreil.com> wrote:
> Stanislaw Gruszka <sgruszka@redhat.com> :
>> Check possible dma mapping errors and do clean up if it happens,
>> when sending frames stop the tx queue.
>
> Almost ok: NETDEV_TX_BUSY can not be used like that. Afaik the DMA
> failure path in the driver really wants a NETDEV_TX_OK (and a device
> stats update, though missing in tg3 ?).
>
> Actually the former NETDEV_TX_BUSY condition mostly checks for a bug.
>
> --
> Ueimor
>



-- 
Regards,
Denis

^ permalink raw reply

* netlink versus pid namespaces
From: Andi Kleen @ 2010-10-15 14:23 UTC (permalink / raw)
  To: netdev, xemul, kuznet, virtualization

Hi,

I have been trying to figure out how pid namespaces interact
with netlink.

netlink uses pids (or really tids I hope?) to address sockets
associated with processes.

The netlink code passes around pids without caring much about 
the pid namespace.  It does pass around some information about the 
network namespace, but that doesn't help here because the pid
namespace is not necessarily related to the net namespace.

When the netlink consumer runs in kernel (like rtnetlink) and
happens to run in the same process context while receiving
and processing the data it should do the right thing because
it has the same pid namespace.

If it runs in some other process that is not guaranteed and
it may actually send the reply back to the wrong pid.

When a process receives netlink in user space and it isn't
in the same pid space as the sender it is unlikely that
the reply gets back.

Anything I'm missing here? 

Does netlink need to be extended? 
Or perhaps forbid passing netlink between name spaces?

Thanks,
-Andi
-- 
ak@linux.intel.com -- Speaking for myself only.

^ permalink raw reply

* Re: [PATCH] sctp: implement SIOCINQ ioctl() (take 3 bis)
From: Diego Elio Pettenò @ 2010-10-15 14:22 UTC (permalink / raw)
  To: netdev; +Cc: linux-sctp
In-Reply-To: <1285926965-5130-1-git-send-email-flameeyes@gmail.com>

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

Il giorno ven, 01/10/2010 alle 11.56 +0200, Diego Elio Pettenò ha
scritto:
> 
> 
> This simple patch copies the current approach for SIOCINQ ioctl() from
> DCCP
> into SCTP so that the userland code working with SCTP can use a
> similar
> interface across different protocols to know how much space to
> allocate for
> a buffer. 

Any news on getting this one in? Pretty please? :)

-- 
Diego Elio Pettenò — “Flameeyes”
http://blog.flameeyes.eu/

If you found a .asc file in this mail and know not what it is,
it's a GnuPG digital signature: http://www.gnupg.org/


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

^ permalink raw reply

* Re: [PATCH net-next] net:  allocate skbs on local node
From: Christoph Lameter @ 2010-10-15 14:23 UTC (permalink / raw)
  To: David Rientjes
  Cc: Pekka Enberg, Andrew Morton, Eric Dumazet, David Miller, netdev,
	Michael Chan, Eilon Greenstein, Christoph Hellwig, LKML,
	Nick Piggin
In-Reply-To: <alpine.DEB.2.00.1010131539440.27839@chino.kir.corp.google.com>


On Wed, 13 Oct 2010, David Rientjes wrote:

> On Wed, 13 Oct 2010, Christoph Lameter wrote:
>
> > > I was going to mention that as an idea, but I thought storing the metadata
> > > for certain debugging features might differ from the two allocators so
> > > substantially that it would be even more convoluted and difficult to
> > > maintain?
> >
> > We could have some callbacks to store allocator specific metadata?
> >
>
> It depends on whether we could share the same base for both slab (unified
> allocator) and slub, which you snipped from your reply, that would make
> this cleaner.

I already said before that we should consider having a common base so I
thought that was not necessary.

^ permalink raw reply

* Re: [PATCH net-next 4/8] tg3: Add EEE support
From: Ben Hutchings @ 2010-10-15 14:17 UTC (permalink / raw)
  To: Matt Carlson; +Cc: davem, netdev, andy
In-Reply-To: <1287088665-22135-5-git-send-email-mcarlson@broadcom.com>

On Thu, 2010-10-14 at 13:37 -0700, Matt Carlson wrote:
> This patch adds Energy Efficient Ethernet (EEE) support for the 5718
> device ID and the 57765 B0 asic revision.
[...]
> +/* Clause 45 expansion registers */
> +#define TG3_CL45_D7_EEEADV_CAP		0x003c
> +#define TG3_CL45_D7_EEEADV_CAP_100TX	0x0002
> +#define TG3_CL45_D7_EEEADV_CAP_1000T	0x0004

I assume this is going to be a standard register, so I think it should
be defined in <linux/mdio.h>.

> +#define TG3_CL45_D7_EEERES_STAT                0x803e
> +#define TG3_CL45_D7_EEERES_STAT_LP_100TX	0x0002
> +#define TG3_CL45_D7_EEERES_STAT_LP_1000T	0x0004

0x803e not 0x003e?

Also Dave suggested there should be an ethtool interface to control EEE
<http://thread.gmane.org/gmane.linux.network/164141/focus=165143>.

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 1/6] r8169: check dma mapping failures
From: Stanislaw Gruszka @ 2010-10-15 14:11 UTC (permalink / raw)
  To: Francois Romieu, Eric Dumazet, David S. Miller; +Cc: netdev, Denis Kirjanov
In-Reply-To: <20101015134158.GA4417@electric-eye.fr.zoreil.com>

On Fri, Oct 15, 2010 at 03:41:58PM +0200, Francois Romieu wrote:
> Stanislaw Gruszka <sgruszka@redhat.com> :
> > Check possible dma mapping errors and do clean up if it happens,
> > when sending frames stop the tx queue.
> 
> Almost ok: NETDEV_TX_BUSY can not be used like that. Afaik the DMA
> failure path in the driver really wants a NETDEV_TX_OK (and a device
> stats update, though missing in tg3 ?).

I'm not sure if any driver handle that in the right way. Returning
"TX OK" when the transmission was not "OK", doesn't look correctly
to me.

Eric, David, what you think?

> Actually the former NETDEV_TX_BUSY condition mostly checks for a bug.

Driver handling code from net/core/*.c does not give me such impression.

Stanislaw

^ permalink raw reply

* Re: [PATCH] connector: remove lazy workqueue creation
From: Tejun Heo @ 2010-10-15 13:50 UTC (permalink / raw)
  To: Evgeniy Polyakov
  Cc: netdev@vger.kernel.org, Frederic Weisbecker, David S. Miller
In-Reply-To: <20101015134942.GA4422@ioremap.net>

On 10/15/2010 03:49 PM, Evgeniy Polyakov wrote:
> On Fri, Oct 15, 2010 at 03:38:13PM +0200, Tejun Heo (tj@kernel.org) wrote:
>> Are you gonna route this patch?  If not, I can route it through
>> workqueue tree.
> 
> If it does not depend on other work, I believe Dave will pull into
> network tree.

Nope, there's no dependency, so network tree then.

Thank you.

-- 
tejun

^ permalink raw reply

* Re: [PATCH] connector: remove lazy workqueue creation
From: Evgeniy Polyakov @ 2010-10-15 13:49 UTC (permalink / raw)
  To: Tejun Heo; +Cc: netdev@vger.kernel.org, Frederic Weisbecker, David S. Miller
In-Reply-To: <4CB85945.8040801@kernel.org>

On Fri, Oct 15, 2010 at 03:38:13PM +0200, Tejun Heo (tj@kernel.org) wrote:
> Are you gonna route this patch?  If not, I can route it through
> workqueue tree.

If it does not depend on other work, I believe Dave will pull into
network tree.

-- 
	Evgeniy Polyakov

^ permalink raw reply

* Re: [PATCH 1/7] drivers/net/irda/irtty-sir.c: Return -ENOMEM on memory allocation failure
From: Samuel Ortiz @ 2010-10-15 13:48 UTC (permalink / raw)
  To: Julia Lawall; +Cc: kernel-janitors, netdev, linux-kernel
In-Reply-To: <1287147610-8041-1-git-send-email-julia@diku.dk>

Hi Julia,

On Fri, 2010-10-15 at 15:00 +0200, Julia Lawall wrote:
> From: Julia Lawall <julia@diku.dk>
> 
> In this code, 0 is returned on memory allocation failure, even though other
> failures return -ENOMEM or other similar values.
> 
> The initial value of ret as 0 is never used, so drop the initialization.
Thanks, patch applied to my irda-2.6 tree.

Cheers,
Samuel.



^ permalink raw reply

* Re: [PATCH 1/6] r8169: check dma mapping failures
From: Francois Romieu @ 2010-10-15 13:41 UTC (permalink / raw)
  To: Stanislaw Gruszka; +Cc: netdev, Denis Kirjanov, David S. Miller
In-Reply-To: <1287144922-3297-1-git-send-email-sgruszka@redhat.com>

Stanislaw Gruszka <sgruszka@redhat.com> :
> Check possible dma mapping errors and do clean up if it happens,
> when sending frames stop the tx queue.

Almost ok: NETDEV_TX_BUSY can not be used like that. Afaik the DMA
failure path in the driver really wants a NETDEV_TX_OK (and a device
stats update, though missing in tg3 ?).

Actually the former NETDEV_TX_BUSY condition mostly checks for a bug.

-- 
Ueimor

^ permalink raw reply

* [PATCH v3] Add Qualcomm Gobi 2000 driver.
From: Elly Jones @ 2010-10-15 13:42 UTC (permalink / raw)
  To: netdev; +Cc: jglasgow, mjg59, 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/).

Special thanks go to Joe Perches <joe@perches.com> for major cleanup.

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/gobi/Makefile    |    2 +
 drivers/net/usb/gobi/README      |   30 +
 drivers/net/usb/gobi/qcusbnet.c  |  649 ++++++++++++++++
 drivers/net/usb/gobi/qcusbnet.h  |   24 +
 drivers/net/usb/gobi/qmi.c       |  358 +++++++++
 drivers/net/usb/gobi/qmi.h       |   67 ++
 drivers/net/usb/gobi/qmidevice.c | 1590 ++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/gobi/qmidevice.h |   35 +
 drivers/net/usb/gobi/structs.h   |   92 +++
 11 files changed, 2854 insertions(+), 1 deletions(-)
 create mode 100644 drivers/net/usb/gobi/Makefile
 create mode 100644 drivers/net/usb/gobi/README
 create mode 100644 drivers/net/usb/gobi/qcusbnet.c
 create mode 100644 drivers/net/usb/gobi/qcusbnet.h
 create mode 100644 drivers/net/usb/gobi/qmi.c
 create mode 100644 drivers/net/usb/gobi/qmi.h
 create mode 100644 drivers/net/usb/gobi/qmidevice.c
 create mode 100644 drivers/net/usb/gobi/qmidevice.h
 create mode 100644 drivers/net/usb/gobi/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..c80e037 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)      += gobi/
diff --git a/drivers/net/usb/gobi/Makefile b/drivers/net/usb/gobi/Makefile
new file mode 100644
index 0000000..a6635f6
--- /dev/null
+++ b/drivers/net/usb/gobi/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_NET_GOBI) += gobi.o
+gobi-objs += qcusbnet.o qmidevice.o qmi.o
diff --git a/drivers/net/usb/gobi/README b/drivers/net/usb/gobi/README
new file mode 100644
index 0000000..113c523
--- /dev/null
+++ b/drivers/net/usb/gobi/README
@@ -0,0 +1,30 @@
+Qualcomm Gobi 2000 driver
+-------------------------
+
+This directory contains a driver for the Qualcomm Gobi 2000 CDMA/GSM modem. The
+device speaks a protocol called QMI with its host; this driver's responsibility
+is to multiplex transactions over QMI connections. Note that the relatively
+simple encoding defined in qmi.h and qmi.c is only the encapsulating protocol;
+the device speaks a more complex set of protocols embodied in the closed-source
+Gobi SDK which are necessary to use advanced features of the device.
+Furthermore, firmware (also proprietary) is needed to make the device useful. An
+open-source firmware loader exists: http://www.codon.org.uk/~mjg59/gobi_loader/.
+
+Interfaces:
+This driver presents two separate interfaces to userspace:
+- usbN, an ordinary usb network interface
+- /dev/qcqmiN, a channel to speak to the device using QMI messages
+The latter is for use by the Gobi SDK.
+
+History:
+This driver's original incarnation (of which this is a rewrite) was released
+under GPLv2 by Qualcomm on their Code Aurora site:
+https://www.codeaurora.org/gitweb/quic/la/?p=kernel/msm.git;a=commit;h=b5135a880f8942f990e8c2e383f7f876beacc55b
+
+Issues:
+Help and commentary on any of the issues below would be appreciated.
+- The locking in devqmi_close() is not threadsafe: we're walking a list of tasks
+  without holding a lock on the list. The original driver did this and I have
+  left it intact because I do not fully understand what's going on here.
+- The driver seems to deadlock very occasionally (one in ~6000 reboots during
+  stress testing). I've had no luck tracking this problem down.
diff --git a/drivers/net/usb/gobi/qcusbnet.c b/drivers/net/usb/gobi/qcusbnet.c
new file mode 100644
index 0000000..7e218a8
--- /dev/null
+++ b/drivers/net/usb/gobi/qcusbnet.c
@@ -0,0 +1,649 @@
+/* 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 qcusbnet_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;
+
+	usbnet = usb_get_intfdata(iface);
+
+	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 (!(event.event & PM_EVENT_AUTO)) {
+		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;
+
+	usbnet = usb_get_intfdata(iface);
+
+	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);
+
+	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);
+
+	kfree(usbnet->net->netdev_ops);
+	usbnet->net->netdev_ops = NULL;
+
+	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) {
+				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) {
+			usb_autopm_put_interface(dev->iface);
+		}
+	} 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;
+	struct net_device_ops *netdevops;
+
+	status = usbnet_probe(iface, vidpids);
+	if (status < 0) {
+		DBG("usbnet_probe failed %d\n", status);
+		return status;
+	}
+
+	usbnet = usb_get_intfdata(iface);
+
+	if (!usbnet || !usbnet->net) {
+		DBG("failed to get netdevice\n");
+		return -ENXIO;
+	}
+
+	dev = kmalloc(sizeof(struct qcusbnet), GFP_KERNEL);
+	if (!dev) {
+		DBG("failed to allocate device buffers\n");
+		return -ENOMEM;
+	}
+
+	usbnet->data[0] = (unsigned long)dev;
+
+	dev->usbnet = usbnet;
+
+	netdevops = kmalloc(sizeof(struct net_device_ops), GFP_KERNEL);
+	if (!netdevops) {
+		DBG("failed to allocate net device ops\n");
+		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;
+
+	memset(&(dev->usbnet->net->stats), 0, sizeof(struct net_device_stats));
+
+	dev->iface = iface;
+	memset(&(dev->meid), '0', 14);
+
+	DBG("Mac Address: %pM\n", dev->usbnet->net->dev_addr);
+
+	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(qcusbnet_debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(qcusbnet_debug, "Debugging enabled or not");
diff --git a/drivers/net/usb/gobi/qcusbnet.h b/drivers/net/usb/gobi/qcusbnet.h
new file mode 100644
index 0000000..2f20868
--- /dev/null
+++ b/drivers/net/usb/gobi/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/gobi/qmi.c b/drivers/net/usb/gobi/qmi.c
new file mode 100644
index 0000000..cdbdbaf
--- /dev/null
+++ b/drivers/net/usb/gobi/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, &stats->txok, 4);
+		tlv_get(buf, size, 0x11, &stats->rxok, 4);
+		tlv_get(buf, size, 0x12, &stats->txerr, 4);
+		tlv_get(buf, size, 0x13, &stats->rxerr, 4);
+		tlv_get(buf, size, 0x14, &stats->txofl, 4);
+		tlv_get(buf, size, 0x15, &stats->rxofl, 4);
+		tlv_get(buf, size, 0x19, &stats->txbytesok, 8);
+		tlv_get(buf, size, 0x1A, &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, meid, 14);
+	if (result != 14)
+		return -EFAULT;
+
+	return 0;
+}
diff --git a/drivers/net/usb/gobi/qmi.h b/drivers/net/usb/gobi/qmi.h
new file mode 100644
index 0000000..7954790
--- /dev/null
+++ b/drivers/net/usb/gobi/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/gobi/qmidevice.c b/drivers/net/usb/gobi/qmidevice.c
new file mode 100644
index 0000000..d1cb341
--- /dev/null
+++ b/drivers/net/usb/gobi/qmidevice.c
@@ -0,0 +1,1590 @@
+/* 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 qcusbnet_debug;
+static int qcusbnet2k_fwdelay;
+
+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
+
+static const 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 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;
+
+	print_hex_dump(KERN_INFO, "QCUSBNet2k: ", DUMP_PREFIX_OFFSET,
+		       16, 1, data, size, true);
+
+	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");
+			print_hex_dump(KERN_INFO, "QCUSBNet2k: ",
+				       DUMP_PREFIX_OFFSET, 16, 1,
+				       urb->transfer_buffer,
+				       urb->actual_length, true);
+		}
+	}
+
+	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("Killing read URB\n");
+		usb_kill_urb(dev->qmi.readurb);
+	}
+
+	if (dev->qmi.inturb) {
+		DBG("Killing 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");
+	print_hex_dump(KERN_INFO,  "QCUSBNet2k: ", DUMP_PREFIX_OFFSET,
+		       16, 1, buf, size, true);
+
+	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) {
+			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);
+	device_create(dev->qmi.devclass, NULL, devno, NULL, "qcqmi%d", qmiidx);
+
+	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);
+		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);
+		}
+	}
+
+	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;
+
+	struct net_device_stats *stats = &(dev->usbnet->net->stats);
+
+	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/gobi/qmidevice.h b/drivers/net/usb/gobi/qmidevice.h
new file mode 100644
index 0000000..5274a0d
--- /dev/null
+++ b/drivers/net/usb/gobi/qmidevice.h
@@ -0,0 +1,35 @@
+/* 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 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/gobi/structs.h b/drivers/net/usb/gobi/structs.h
new file mode 100644
index 0000000..7a89c5c
--- /dev/null
+++ b/drivers/net/usb/gobi/structs.h
@@ -0,0 +1,92 @@
+/* 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>
+
+#include <linux/usb/usbnet.h>
+
+#include <linux/fdtable.h>
+
+#define DBG(fmt, arg...)						\
+do {									\
+	if (qcusbnet_debug == 1)					\
+		printk(KERN_INFO "QCUSBNet2k::%s " fmt, __func__, ##arg); \
+} while (0)
+
+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


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