Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH] net: Fix IPv6 PMTU disc. w/ asymmetric routes
From: Maciej Żenczykowski @ 2010-09-28 22:37 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, Hideaki Yoshifuji
In-Reply-To: <20100928.135800.39205209.davem@davemloft.net>

> Please handle all 4 cases just like the ipv4 routing code does:
>
> { saddr = SADDR, ifindex = dev->ifindex }
> { saddr = SADDR, ifindex = 0 }
> { saddr = INADDR_ANY, ifindex = dev->ifindex }
> { saddr = INADDR_ANY, ifindex = 0 }
>
> I believe I've specifically asked for this every time someone mentioned that they wanted to fix this issue.

I believe that is exactly what the following code fragment from the
above patch does:

+       rt6_do_pmtu_disc(daddr, saddr, net, pmtu, 0);
+       rt6_do_pmtu_disc(daddr, saddr, net, pmtu, dev->ifindex);
+       rt6_do_pmtu_disc(daddr, NULL, net, pmtu, 0);
+       rt6_do_pmtu_disc(daddr, NULL, net, pmtu, dev->ifindex);

The last two of these lines were added to the patch last time around
per your feedback - precisely to address this issue.
I reposted with those changes and you appeared to be ok with the
patch, and your only comment was to add a 'Signed-off-by' line.


Side note:
* I still think that handling the saddr == NULL ie. INADDR_ANY case is
entirely superfluous, since it doesn't actually iterate through all
possible source addresses.  With IPv6 there can be many, many possible
source addresses (just think of link local vs global public vs privacy
addresses and then tack on 6to4 and mobility, etc... for example I see
13 ipv6 addresses on eth0 on my desktop at home, 12 of them globally
reachable).
* Currently PMTU discovery is broken.  With this patch with all 4
lines we end up doing PMTU discovery per source address that isn't the
default source address.  Without those last two lines we'd just do
PMTU discovery per source address.  Not much of a difference, both
allow PMTU discovery to actually happen.

Except:

Imagine a machine with a 1500 MTU network card and 2 source addresses:
  - one default v6 native 'A'
  - one additional 6to4 address 'B'
The native address can reach the internet with a max MTU of 1500.
While due to source address based routing the 6to4 address goes
through a sit ipv4 based tunnel and thus has a max MTU of 1480 (1500 -
20 bytes IPv4 header).

Imagine a remote machine with v6 address 'Z' (our default address to
reach 'Z' is 'A').
Clearly the PMTU of A->Z is 1500, while the PMTU of B->Z is 1480.
[Assume there's nothing which would cause them to be lower.]

If you do PMTU discovery of A->Z, you will indeed get 1500, and mark
that as the PMTU of A->Z.
However if you do PMTU discovery of B->Z, you will indeed get 1480,
but you will mark that as the PMTU of both B->Z and A->Z (since A is
the default to reach Z).
Suddenly the PMTU of A->Z _needlessly and incorrectly_ dropped from
1500 to 1480 merely because of PMTU discovery being performed on B->Z.

And this is precisely why I think that adding those last two lines
that handle INADDR_ANY is actually more of a problem then solution
(indeed as far as I can tell, adding them doesn't actually fix any
problem - the code works just fine and performs PMTU discovery
correctly with just the first two lines).

Thankfully ending up with a lower MTU than actually possible is only a
slight performance problem, while not doing PMTU discovery correctly
at all (current state with asymmetric routing) is a big issue.
Hence regardless of whether this patch includes INADDR_ANY or not the
end result will still be an improvement.

> And the ipv4 side is a good guide in other ways, it uses a set of two arrays so you can just loop over them, making your rt6_do_pmtu_disc() calls along the way.

Sure, I can change it to use arrays, although I don't see any
particular improvement in performance (even if rt6_do_pmtu_disc was
inlined) or readability or anything.

^ permalink raw reply

* Re: [PATCH v2 1/2] Phonet: Implement Pipe Controller to support Nokia Slim Modems
From: Rémi Denis-Courmont @ 2010-09-28 22:25 UTC (permalink / raw)
  To: ext Kumar A Sanghvi
  Cc: davem@davemloft.net, netdev@vger.kernel.org,
	STEricsson_nomadik_linux@list.st.com,
	sudeep.divakaran@stericsson.com, gulshan.karmani@stericsson.com,
	Linus Walleij
In-Reply-To: <1285564079-23066-1-git-send-email-kumar.sanghvi@stericsson.com>

On Monday 27 September 2010 08:07:59 ext Kumar A Sanghvi, you wrote:
> From: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
> 
> Phonet stack assumes the presence of Pipe Controller, either in Modem or
> on Application Processing Engine user-space for the Pipe data.
> Nokia Slim Modems like WG2.5 used in ST-Ericsson U8500 platform do not
> implement Pipe controller in them.
> This patch adds Pipe Controller implemenation to Phonet stack to support
> Pipe data over Phonet stack for Nokia Slim Modems.
> 
> Signed-off-by: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> ---
> Changes:
> 
>  -v2: Correction for header retrieving after pskb_may_pull
> 
>  include/linux/phonet.h   |    5 +
>  include/net/phonet/pep.h |   21 +++
>  net/phonet/Kconfig       |   11 ++
>  net/phonet/pep.c         |  448
> +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 479
> insertions(+), 6 deletions(-)
> 
> diff --git a/include/linux/phonet.h b/include/linux/phonet.h
> index 85e14a8..96f5625 100644
> --- a/include/linux/phonet.h
> +++ b/include/linux/phonet.h
> @@ -36,6 +36,11 @@
>  /* Socket options for SOL_PNPIPE level */
>  #define PNPIPE_ENCAP           1
>  #define PNPIPE_IFINDEX         2
> +#define PNPIPE_CREATE           3
> +#define PNPIPE_ENABLE           4
> +#define PNPIPE_DISABLE          5
> +#define PNPIPE_DESTROY          6
> +#define PNPIPE_INQ              7

As far as I know, you don't need to do that in kernel space. I don't know the 
internals of the STE modem. Regardless of having or not having a pipe 
controller, Linux userspace can send pipe messages using a plain Phonet 
datagram socket. This avoids adding ioctl()'s and protocol stuff in kernel 
space. Then, as far as kernel is concerned, only small changes to the data 
path would be required.

Both the Nokia modem plugin for oFono, and the (closed-source) Nokia N900 CSD-
GPRS service work that way. In other words, the pipe 'signaling' is done in 
userspace, while the pipe 'data' is done in kernel space for optimal 
performance.

For some background - Phonet pipes work very much like FTP. There are two 
endpoints exchaning data, and one client ('owner') deciding which endpoints 
and when to establish a pipe between. In most case the client is also one of 
the endpoint, but this is not required. With this patch, the client is tied to 
being one of the endpoint. Arguably, this is not a problem for most usecases. 
But I am not sure this belongs in *kernel* space. Sticking to the same 
comparison: with FTP, you have TCP (data path) in kernel, but not FTP itself 
(signaling).

> @@ -791,6 +1171,48 @@ static int pep_setsockopt(struct sock *sk, int level,
> int optname,
> 
>         lock_sock(sk);
>         switch (optname) {
> +#ifdef CONFIG_PHONET_PIPECTRLR
> +       case PNPIPE_CREATE:
> +               if (val) {
> +                       if (pn->pipe_state > PIPE_IDLE) {
> +                               err = -EFAULT;

Why EFAULT here? I can't see any user-space memory access failure.

> +                               break;
> +                       }
> +                       remote_pep = val & 0xFFFF;
> +                       pipe_handle =  (val >> 16) & 0xFF;
> +                       pn->remote_pep = remote_pep;
> +                       err = pipe_handler_create_pipe(sk, pipe_handle,
> +                                       PNPIPE_CREATE);
> +                       break;
> +               }
> +
> +       case PNPIPE_ENABLE:
> +               if (pn->pipe_state != PIPE_DISABLED) {
> +                       err = -EFAULT;

Same here.

> +                       break;
> +               }
> +               err = pipe_handler_enable_pipe(sk, PNPIPE_ENABLE);
> +               break;
> +
> +       case PNPIPE_DISABLE:
> +               if (pn->pipe_state != PIPE_ENABLED) {
> +                       err = -EFAULT;

Ditto.

> +                       break;
> +               }
> +
> +               err = pipe_handler_enable_pipe(sk, PNPIPE_DISABLE);
> +               break;
> +
> +       case PNPIPE_DESTROY:
> +               if (pn->pipe_state < PIPE_DISABLED) {
> +                       err = -EFAULT;

And here,

> @@ -877,11 +1313,11 @@ static int pipe_skb_send(struct sock *sk, struct
> sk_buff *skb) } else
>                 ph->message_id = PNS_PIPE_DATA;
>         ph->pipe_handle = pn->pipe_handle;
> -
> -       err = pn_skb_send(sk, skb, &pipe_srv);
> -       if (err && pn_flow_safe(pn->tx_fc))
> -               atomic_inc(&pn->tx_credits);
> -       return err;
> +#ifdef CONFIG_PHONET_PIPECTRLR
> +       return pn_skb_send(sk, skb, &spn);
> +#else
> +       return pn_skb_send(sk, skb, &pipe_srv);
> +#endif
>  }

This reintroduces the bug that I fixed in 
1a98214feef2221cd7c24b17cd688a5a9d85b2ea :-(

-- 
Rémi Denis-Courmont
Nokia Devices R&D, Maemo Software, Helsinki

^ permalink raw reply

* (unknown), 
From: FinancialAid @ 2010-09-28 22:09 UTC (permalink / raw)





An emergency,please respond.
I am Mrs. Ksenia Gutseriyev, the wife of Russian multi billionaire Mr. Mikhail Gutseriyev's, the former owner of Russneft Oil Company in Russia, i have a proposal for you, if intrested contact via my box: ksenia.gutseri@gala.net

^ permalink raw reply

* Re: [PATCHv2 net-next-2.6 0/5] XFRM,IPv6: Removal of RH2/HAO from IPsec-protected MIPv6 traffic
From: David Miller @ 2010-09-28 21:40 UTC (permalink / raw)
  To: arno; +Cc: eric.dumazet, herbert, yoshfuji, netdev
In-Reply-To: <87r5gdtuxv.fsf@small.ssi.corp>

From: arno@natisbad.org (Arnaud Ebalard)
Date: Tue, 28 Sep 2010 23:33:16 +0200

> Before following the (dumb) #ifdef path, I was about to do that but
> worried about the penalty of the additional xfrm_state_get/put_afinfo()
> calls on each packet I was about to add. Should I just reduce my amount
> of coffee or is it a valid concern?

Indeed, it is.

Even without the concern of afinfo refcounting, this test is very
heavy handed for the packet path.

Can you make it small enough that it can reasonably be inlined?

^ permalink raw reply

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

Hi,

David Miller <davem@davemloft.net> writes:

> Try again, this time with ipv6 modular:
>
> net/built-in.o: In function `xfrm_input_addr_check':
> /home/davem/src/GIT/net-next-2.6/net/xfrm/xfrm_input.c:115: undefined reference to `xfrm6_input_addr_check'
>
> You can't put xfrm6_input_addr_check into the ipv6.o object build if you want to
> call it from the generic xfrm stack which is always built statically.
>
> Put this and xfrm4_input_addr_check where it belongs, as an afinfo->op()

Before following the (dumb) #ifdef path, I was about to do that but
worried about the penalty of the additional xfrm_state_get/put_afinfo()
calls on each packet I was about to add. Should I just reduce my amount
of coffee or is it a valid concern?

Cheers,

a+

^ permalink raw reply

* Re: [PATCH] Phonet: Correct header retrieval after pskb_may_pull
From: Rémi Denis-Courmont @ 2010-09-28 21:23 UTC (permalink / raw)
  To: ext Kumar A Sanghvi
  Cc: netdev@vger.kernel.org, davem@davemloft.net,
	eric.dumazet@gmail.com, gulshan.karmani@stericsson.com,
	Linus Walleij
In-Reply-To: <1285665042-20548-1-git-send-email-kumar.sanghvi@stericsson.com>

On Tuesday 28 September 2010 12:10:42 ext Kumar A Sanghvi, you wrote:
> From: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
> 
> Retrieve the header after doing pskb_may_pull since, pskb_may_pull
> could change the buffer structure.
> 
> This is based on the comment given by Eric Dumazet on Phonet
> Pipe controller patch for a similar problem.
> 
> Signed-off-by: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> ---
>  net/phonet/pep.c |    3 ++-
>  1 files changed, 2 insertions(+), 1 deletions(-)
> 
> diff --git a/net/phonet/pep.c b/net/phonet/pep.c
> index 7bf23cf..9746c6d 100644
> --- a/net/phonet/pep.c
> +++ b/net/phonet/pep.c
> @@ -507,12 +507,13 @@ static void pipe_grant_credits(struct sock *sk)
>  static int pipe_rcv_status(struct sock *sk, struct sk_buff *skb)
>  {
>  	struct pep_sock *pn = pep_sk(sk);
> -	struct pnpipehdr *hdr = pnp_hdr(skb);
> +	struct pnpipehdr *hdr;
>  	int wake = 0;
> 
>  	if (!pskb_may_pull(skb, sizeof(*hdr) + 4))
>  		return -EINVAL;
> 
> +	hdr = pnp_hdr(skb);
>  	if (hdr->data[0] != PN_PEP_TYPE_COMMON) {
>  		LIMIT_NETDEBUG(KERN_DEBUG"Phonet unknown PEP type: %u\n",
>  				(unsigned)hdr->data[0]);

Acked-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>

Thanks!

-- 
Rémi Denis-Courmont
Nokia Devices R&D, Maemo Software, Helsinki

^ permalink raw reply

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

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

Also, is there any way you can wrangle Gobi 1000 support out of
Qualcomm?  I tried the driver with a 1000 part when Qualcomm originally
posted the 2K net driver back in June and it resulted in a panic when
the driver didn't get the response it expected.  I suppose I can try
again with this code too, but getting some feedback from Qualcomm that
1K parts might work here would be nice too.

Also, are 3K devices supported by this code?  The 3K net and serial
driver was recently posted on codeaurora.

Dan


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



^ permalink raw reply

* Re: [PATCH] net: Implement Any-IP support for IPv6.
From: David Miller @ 2010-09-28 21:17 UTC (permalink / raw)
  To: zenczykowski; +Cc: netdev
In-Reply-To: <AANLkTim05yqEiN+LbL5d3Eear0WMcG5LtCi5Ba_StvYv@mail.gmail.com>

From: Maciej Żenczykowski <zenczykowski@gmail.com>
Date: Tue, 28 Sep 2010 14:04:47 -0700

> AFAIK, Tom Herbert did post the ipv4 patch some time back.
> 
> http://patchwork.ozlabs.org/patch/53381/
> 
> I think the ipv6 code path (and hence the patch itself) is much cleaner.

Ok, I've moved that patch back to under-review state, maybe we
can get them both in at the same time.

Thanks.

^ permalink raw reply

* Re: [PATCH] net: Implement Any-IP support for IPv6.
From: Maciej Żenczykowski @ 2010-09-28 21:04 UTC (permalink / raw)
  To: David Miller; +Cc: netdev
In-Reply-To: <20100928.135923.59699712.davem@davemloft.net>

AFAIK, Tom Herbert did post the ipv4 patch some time back.

http://patchwork.ozlabs.org/patch/53381/

I think the ipv6 code path (and hence the patch itself) is much cleaner.

^ permalink raw reply

* Re: [PATCH] net: Implement Any-IP support for IPv6.
From: David Miller @ 2010-09-28 20:59 UTC (permalink / raw)
  To: zenczykowski; +Cc: netdev, maze
In-Reply-To: <1285582022-30787-1-git-send-email-zenczykowski@gmail.com>

From: Maciej Żenczykowski <zenczykowski@gmail.com>
Date: Mon, 27 Sep 2010 03:07:02 -0700

> From: Maciej Żenczykowski <maze@google.com>
> 
> AnyIP is the capability to receive packets and establish incoming
> connections on IPs we have not explicitly configured on the machine.
> 
> An example use case is to configure a machine to accept all incoming
> traffic on eth0, and leave the policy of whether traffic for a given IP
> should be delivered to the machine up to the load balancer.
> 
> Can be setup as follows:
>   ip -6 rule from all iif eth0 lookup 200
>   ip -6 route add local default dev lo table 200
> (in this case for all IPv6 addresses)
> 
> Signed-off-by: Maciej Żenczykowski <maze@google.com>

Does this already work on the ipv4 side?

If not, why only add it to ipv6?

^ permalink raw reply

* Re: [PATCH] net: Fix IPv6 PMTU disc. w/ asymmetric routes
From: David Miller @ 2010-09-28 20:58 UTC (permalink / raw)
  To: zenczykowski; +Cc: netdev, maze
In-Reply-To: <1285581957-30694-1-git-send-email-zenczykowski@gmail.com>

From: Maciej Żenczykowski <zenczykowski@gmail.com>
Date: Mon, 27 Sep 2010 03:05:57 -0700

> From: Maciej Żenczykowski <maze@google.com>
> 
> Signed-off-by: Maciej Żenczykowski <maze@google.com>

Please handle all 4 cases just like the ipv4 routing code does:

{ saddr = SADDR, ifindex = dev->ifindex }
{ saddr = SADDR, ifindex = 0 }
{ saddr = INADDR_ANY, ifindex = dev->ifindex }
{ saddr = INADDR_ANY, ifindex = 0 }

I believe I've specifically asked for this every time someone
mentioned that they wanted to fix this issue.

And the ipv4 side is a good guide in other ways, it uses a set
of two arrays so you can just loop over them, making your
rt6_do_pmtu_disc() calls along the way.



^ permalink raw reply

* Re: [PATCH 0/4] cxgbi: bug fixes and driver update
From: Mike Christie @ 2010-09-28 20:45 UTC (permalink / raw)
  To: kxie; +Cc: netdev, linux-scsi, open-iscsi, rranjan, James.Bottomley, davem
In-Reply-To: <201009232343.o8NNhN0E006459@localhost.localdomain>

On 09/23/2010 06:43 PM, kxie@chelsio.com wrote:
> [PATCH 0/4] cxgbi: bug fixes and driver update
>
> From: Karen Xie<kxie@chelsio.com>
>
> This patchset includes:
> - renamed alloc_cpl() to alloc_wr(),
> - fixed locking in pdu read,
> - fixed cxgb3i connecting over VLAN, and
> - updated cxgb4i connection setting and ddp programming.
>

Looks ok.

Reviewed-by: Mike Christie <michaelc@cs.wisc.edu>

^ permalink raw reply

* Re: [PATCHv2 net-next-2.6 0/5] XFRM,IPv6: Removal of RH2/HAO from IPsec-protected MIPv6 traffic
From: David Miller @ 2010-09-28 20:38 UTC (permalink / raw)
  To: arno; +Cc: eric.dumazet, herbert, yoshfuji, netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>

From: Arnaud Ebalard <arno@natisbad.org>
Date: Tue, 28 Sep 2010 17:53:50 +0200

> This an updated version of the patches. For reference, introduction of
> the feature is here http://thread.gmane.org/gmane.linux.network/172941
> Just tell me if you prefer I resend the full intro each time.
> 
> Compared to initial version, this v2 is
> 
>  - against net-next-2.6
>  - fixed and more exhaustively build tested for various kernel
>    configuration (w/ and w/o IPv6, IPv4, MIPv6, XFRM*, RO, ...).
>    It adds some #ifdef but I did not found good ways to spare those.
>  - built and tested on both current linux-2.6 and net-next-2.6 (*)
> 
> Comments welcome.

Try again, this time with ipv6 modular:

net/built-in.o: In function `xfrm_input_addr_check':
/home/davem/src/GIT/net-next-2.6/net/xfrm/xfrm_input.c:115: undefined reference to `xfrm6_input_addr_check'

You can't put xfrm6_input_addr_check into the ipv6.o object build if you want to
call it from the generic xfrm stack which is always built statically.

Put this and xfrm4_input_addr_check where it belongs, as an afinfo->op()



^ permalink raw reply

* Re: [PATCH] net-2.6: SYN retransmits: Add new parameter to retransmits_timed_out()
From: David Miller @ 2010-09-28 20:08 UTC (permalink / raw)
  To: damian; +Cc: netdev
In-Reply-To: <1285703094.7187.3.camel@nexus>

From: Damian Lukowski <damian@tvk.rwth-aachen.de>
Date: Tue, 28 Sep 2010 21:44:54 +0200

> 
> This patch adds a syn_set parameter to the retransmits_timed_out()
> routine and updates its callers. If not set, TCP_RTO_MIN is taken
> as the calculation basis as before. If set, TCP_TIMEOUT_INIT is
> used instead, so that sysctl_syn_retries represents the actual
> amount of SYN retransmissions in case no SYNACKs are received when
> establishing a new connection.
> 
> Signed-off-by: Damian Lukowski <damian@tvk.rwth-aachen.de>

Applied, thanks Damian.

^ permalink raw reply

* [PATCH 2/2] net-next-2.6: SYN retransmits: Add new parameter to retransmits_timed_out()
From: Damian Lukowski @ 2010-09-28 19:46 UTC (permalink / raw)
  To: netdev


This patch adds a syn_set parameter to the retransmits_timed_out()
routine and updates its callers. If not set, TCP_RTO_MIN is taken
as the calculation basis as before. If set, TCP_TIMEOUT_INIT is
used instead, so that sysctl_syn_retries represents the actual
amount of SYN retransmissions in case no SYNACKs are received when
establishing a new connection.

Signed-off-by: Damian Lukowski <damian@tvk.rwth-aachen.de>
---
 net/ipv4/tcp_timer.c |   26 +++++++++++++++-----------
 1 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
index 98cbc60..56a5aaf 100644
--- a/net/ipv4/tcp_timer.c
+++ b/net/ipv4/tcp_timer.c
@@ -135,13 +135,16 @@ static void tcp_mtu_probing(struct inet_connection_sock *icsk, struct sock *sk)
 
 /* This function calculates a "timeout" which is equivalent to the timeout of a
  * TCP connection after "boundary" unsuccessful, exponentially backed-off
- * retransmissions with an initial RTO of TCP_RTO_MIN.
+ * retransmissions with an initial RTO of TCP_RTO_MIN or TCP_TIMEOUT_INIT if
+ * syn_set flag is set.
  */
 static bool retransmits_timed_out(struct sock *sk,
 				  unsigned int boundary,
-				  unsigned int timeout)
+				  unsigned int timeout,
+				  bool syn_set)
 {
 	unsigned int backoff_thresh, start_ts;
+	unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;
 
 	if (!inet_csk(sk)->icsk_retransmits)
 		return false;
@@ -152,12 +155,12 @@ static bool retransmits_timed_out(struct sock *sk,
 		start_ts = tcp_sk(sk)->retrans_stamp;
 
 	if (likely(timeout == 0)) {
-		backoff_thresh = ilog2(TCP_RTO_MAX/TCP_RTO_MIN);
+		backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);
 
 		if (boundary <= backoff_thresh)
-			timeout = ((2 << boundary) - 1) * TCP_RTO_MIN;
+			timeout = ((2 << boundary) - 1) * rto_base;
 		else
-			timeout = ((2 << backoff_thresh) - 1) * TCP_RTO_MIN +
+			timeout = ((2 << backoff_thresh) - 1) * rto_base +
 				  (boundary - backoff_thresh) * TCP_RTO_MAX;
 	}
 	return (tcp_time_stamp - start_ts) >= timeout;
@@ -168,14 +171,15 @@ static int tcp_write_timeout(struct sock *sk)
 {
 	struct inet_connection_sock *icsk = inet_csk(sk);
 	int retry_until;
-	bool do_reset;
+	bool do_reset, syn_set = 0;
 
 	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
 		if (icsk->icsk_retransmits)
 			dst_negative_advice(sk);
 		retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;
+		syn_set = 1;
 	} else {
-		if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0)) {
+		if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) {
 			/* Black hole detection */
 			tcp_mtu_probing(icsk, sk);
 
@@ -188,7 +192,8 @@ static int tcp_write_timeout(struct sock *sk)
 
 			retry_until = tcp_orphan_retries(sk, alive);
 			do_reset = alive ||
-				   !retransmits_timed_out(sk, retry_until, 0);
+				   !retransmits_timed_out(sk,
+							  retry_until, 0, 0);
 
 			if (tcp_out_of_resources(sk, do_reset))
 				return 1;
@@ -196,8 +201,7 @@ static int tcp_write_timeout(struct sock *sk)
 	}
 
 	if (retransmits_timed_out(sk, retry_until,
-	    (1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV) ? 0 :
-	    icsk->icsk_user_timeout)) {
+	    syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) {
 		/* Has it gone just too far? */
 		tcp_write_err(sk);
 		return 1;
@@ -439,7 +443,7 @@ out_reset_timer:
 		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
 	}
 	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
-	if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0))
+	if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0, 0))
 		__sk_dst_reset(sk);
 
 out:;
-- 
1.7.2.2





^ permalink raw reply related

* [PATCH 1/2] net-next-2.6: SYN retransmits: Rename threshold variable
From: Damian Lukowski @ 2010-09-28 19:45 UTC (permalink / raw)
  To: netdev


This preparatory patch renames the threshold variable in
retransmits_timed_out() for proper code style.

Signed-off-by: Damian Lukowski <damian@tvk.rwth-aachen.de>
---
 net/ipv4/tcp_timer.c |   10 +++++-----
 1 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
index baea4a1..98cbc60 100644
--- a/net/ipv4/tcp_timer.c
+++ b/net/ipv4/tcp_timer.c
@@ -141,7 +141,7 @@ static bool retransmits_timed_out(struct sock *sk,
 				  unsigned int boundary,
 				  unsigned int timeout)
 {
-	unsigned int linear_backoff_thresh, start_ts;
+	unsigned int backoff_thresh, start_ts;
 
 	if (!inet_csk(sk)->icsk_retransmits)
 		return false;
@@ -152,13 +152,13 @@ static bool retransmits_timed_out(struct sock *sk,
 		start_ts = tcp_sk(sk)->retrans_stamp;
 
 	if (likely(timeout == 0)) {
-		linear_backoff_thresh = ilog2(TCP_RTO_MAX/TCP_RTO_MIN);
+		backoff_thresh = ilog2(TCP_RTO_MAX/TCP_RTO_MIN);
 
-		if (boundary <= linear_backoff_thresh)
+		if (boundary <= backoff_thresh)
 			timeout = ((2 << boundary) - 1) * TCP_RTO_MIN;
 		else
-			timeout = ((2 << linear_backoff_thresh) - 1) * TCP_RTO_MIN +
-				(boundary - linear_backoff_thresh) * TCP_RTO_MAX;
+			timeout = ((2 << backoff_thresh) - 1) * TCP_RTO_MIN +
+				  (boundary - backoff_thresh) * TCP_RTO_MAX;
 	}
 	return (tcp_time_stamp - start_ts) >= timeout;
 }
-- 
1.7.2.2





^ permalink raw reply related

* [PATCH] net-2.6: SYN retransmits: Add new parameter to retransmits_timed_out()
From: Damian Lukowski @ 2010-09-28 19:44 UTC (permalink / raw)
  To: netdev


This patch adds a syn_set parameter to the retransmits_timed_out()
routine and updates its callers. If not set, TCP_RTO_MIN is taken
as the calculation basis as before. If set, TCP_TIMEOUT_INIT is
used instead, so that sysctl_syn_retries represents the actual
amount of SYN retransmissions in case no SYNACKs are received when
establishing a new connection.

Signed-off-by: Damian Lukowski <damian@tvk.rwth-aachen.de>
---
 net/ipv4/tcp_timer.c |   24 ++++++++++++++----------
 1 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
index c35b469..74c54b3 100644
--- a/net/ipv4/tcp_timer.c
+++ b/net/ipv4/tcp_timer.c
@@ -135,13 +135,16 @@ static void tcp_mtu_probing(struct inet_connection_sock *icsk, struct sock *sk)
 
 /* This function calculates a "timeout" which is equivalent to the timeout of a
  * TCP connection after "boundary" unsuccessful, exponentially backed-off
- * retransmissions with an initial RTO of TCP_RTO_MIN.
+ * retransmissions with an initial RTO of TCP_RTO_MIN or TCP_TIMEOUT_INIT if
+ * syn_set flag is set.
  */
 static bool retransmits_timed_out(struct sock *sk,
-				  unsigned int boundary)
+				  unsigned int boundary,
+				  bool syn_set)
 {
 	unsigned int timeout, linear_backoff_thresh;
 	unsigned int start_ts;
+	unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;
 
 	if (!inet_csk(sk)->icsk_retransmits)
 		return false;
@@ -151,12 +154,12 @@ static bool retransmits_timed_out(struct sock *sk,
 	else
 		start_ts = tcp_sk(sk)->retrans_stamp;
 
-	linear_backoff_thresh = ilog2(TCP_RTO_MAX/TCP_RTO_MIN);
+	linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);
 
 	if (boundary <= linear_backoff_thresh)
-		timeout = ((2 << boundary) - 1) * TCP_RTO_MIN;
+		timeout = ((2 << boundary) - 1) * rto_base;
 	else
-		timeout = ((2 << linear_backoff_thresh) - 1) * TCP_RTO_MIN +
+		timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
 			  (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
 
 	return (tcp_time_stamp - start_ts) >= timeout;
@@ -167,14 +170,15 @@ static int tcp_write_timeout(struct sock *sk)
 {
 	struct inet_connection_sock *icsk = inet_csk(sk);
 	int retry_until;
-	bool do_reset;
+	bool do_reset, syn_set = 0;
 
 	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
 		if (icsk->icsk_retransmits)
 			dst_negative_advice(sk);
 		retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;
+		syn_set = 1;
 	} else {
-		if (retransmits_timed_out(sk, sysctl_tcp_retries1)) {
+		if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0)) {
 			/* Black hole detection */
 			tcp_mtu_probing(icsk, sk);
 
@@ -187,14 +191,14 @@ static int tcp_write_timeout(struct sock *sk)
 
 			retry_until = tcp_orphan_retries(sk, alive);
 			do_reset = alive ||
-				   !retransmits_timed_out(sk, retry_until);
+				   !retransmits_timed_out(sk, retry_until, 0);
 
 			if (tcp_out_of_resources(sk, do_reset))
 				return 1;
 		}
 	}
 
-	if (retransmits_timed_out(sk, retry_until)) {
+	if (retransmits_timed_out(sk, retry_until, syn_set)) {
 		/* Has it gone just too far? */
 		tcp_write_err(sk);
 		return 1;
@@ -436,7 +440,7 @@ out_reset_timer:
 		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
 	}
 	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
-	if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1))
+	if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0))
 		__sk_dst_reset(sk);
 
 out:;
-- 
1.7.2.2




^ permalink raw reply related

* Re: [PATCH] de2104x: remove experimental status
From: Jeff Garzik @ 2010-09-28 18:52 UTC (permalink / raw)
  To: Ondrej Zary; +Cc: netdev, Kernel development list
In-Reply-To: <201009282046.21279.linux@rainbow-software.org>

On 09/28/2010 02:46 PM, Ondrej Zary wrote:
> It should be ready after 8 years...remove the experimental dependency.
>
> Signed-off-by: Ondrej Zary<linux@rainbow-software.org>
>
> --- linux-2.6.36-rc3-/drivers/net/tulip/Kconfig	2010-08-29 17:36:04.000000000 +0200
> +++ linux-2.6.36-rc3/drivers/net/tulip/Kconfig	2010-09-28 19:49:46.000000000 +0200
> @@ -11,8 +11,8 @@ menuconfig NET_TULIP
>   if NET_TULIP
>
>   config DE2104X
> -	tristate "Early DECchip Tulip (dc2104x) PCI support (EXPERIMENTAL)"
> -	depends on PCI&&  EXPERIMENTAL
> +	tristate "Early DECchip Tulip (dc2104x) PCI support"
> +	depends on PCI
>   	select CRC32
>   	---help---
>   	  This driver is developed for the SMC EtherPower series Ethernet

Well...  it's not the years, it's the quality...  which I think has been 
sufficiently increased.

Acked-by: Jeff Garzik <jgarzik@redhat.com>



^ permalink raw reply

* Re: [PATCH] de2104x: disable media debug messages by default
From: Jeff Garzik @ 2010-09-28 18:51 UTC (permalink / raw)
  To: Ondrej Zary; +Cc: netdev, Kernel development list
In-Reply-To: <201009282018.57547.linux@rainbow-software.org>

On 09/28/2010 02:18 PM, Ondrej Zary wrote:
> Print media debug messages only when HW debug is enabled.
>
> Signed-off-by: Ondrej Zary<linux@rainbow-software.org>
>
> --- linux-2.6.36-rc3-/drivers/net/tulip/de2104x.c	2010-09-28 19:50:51.000000000 +0200
> +++ linux-2.6.36-rc3/drivers/net/tulip/de2104x.c	2010-09-28 20:05:34.000000000 +0200
> @@ -948,8 +948,9 @@ static void de_set_media (struct de_priv
>   	else
>   		macmode&= ~FullDuplex;
>
> -	if (netif_msg_link(de)) {
> +	if (netif_msg_link(de))
>   		dev_info(&de->dev->dev, "set link %s\n", media_name[media]);
> +	if (netif_msg_hw(de)) {
>   		dev_info(&de->dev->dev, "mode 0x%x, sia 0x%x,0x%x,0x%x,0x%x\n",
>   			 dr32(MacMode), dr32(SIAStatus),
>   			 dr32(CSR13), dr32(CSR14), dr32(CSR15));

Acked-by: Jeff Garzik <jgarzik@redhat.com>

^ permalink raw reply

* WiMAX linux firmware pull request
From: Inaky Perez-Gonzalez @ 2010-09-28 18:46 UTC (permalink / raw)
  To: David Woodhouse; +Cc: wimax, netdev


Hello David

I don't know if I forgot to send this request or it fell through the
cracks a fer kversions ago. Anyway, sending again.

Could you please pull

git://git.kernel.org/pub/scm/linux/kernel/git/inaky/linux-firmware-wimax.git

This contains the firmware for the 1.5 driver release for i5x50 and
i6x50 Intel cards, which have been available in
http://linuxwimax.org/Download since. 

Thanks!


^ permalink raw reply

* Re: [PATCH v11 13/17] Add mp(mediate passthru) device.
From: Sridhar Samudrala @ 2010-09-28 18:48 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Michael S. Tsirkin, Ben Hutchings, xiaohui.xin, netdev, kvm,
	linux-kernel, mingo, davem, herbert, jdike
In-Reply-To: <201009281718.15813.arnd@arndb.de>

  On 9/28/2010 8:18 AM, Arnd Bergmann wrote:
> On Tuesday 28 September 2010, Michael S. Tsirkin wrote:
>> On Tue, Sep 28, 2010 at 04:39:59PM +0200, Arnd Bergmann wrote:
>>> Can you be more specific what the problem is? Do you think
>>> it breaks when a guest sends VLAN tagged frames or when macvtap
>>> is connected to a VLAN interface that adds another tag (or
>>> only the combination)?
>> I expect the protocol value to be wrong when guest sends vlan tagged
>> frames as 802.1q frames have a different format.
> Ok, I see. Would that be fixed by using eth_type_trans()? I don't
> see any code in there that tries to deal with the VLAN tag, so
> do we have the same problem in the tun/tap driver?
tun_get_user() does call eth_type_trans(). Not sure why i didn't use it 
in macvtap code.
Need to test it with guest VLAN tagging to make sure it works.
> Also, I wonder how we handle the case where both the guest and
> the host do VLAN tagging. Does the host transparently override
> the guest tag, or does it add a nested tag? More importantly,
> what should it do?
> 	
I would think If both guest and host do VLAN tagging, the tags will be 
nested.

Thanks
Sridhar




^ permalink raw reply

* [PATCH] de2104x: remove experimental status
From: Ondrej Zary @ 2010-09-28 18:46 UTC (permalink / raw)
  To: jgarzik; +Cc: netdev, Kernel development list

It should be ready after 8 years...remove the experimental dependency.

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

--- linux-2.6.36-rc3-/drivers/net/tulip/Kconfig	2010-08-29 17:36:04.000000000 +0200
+++ linux-2.6.36-rc3/drivers/net/tulip/Kconfig	2010-09-28 19:49:46.000000000 +0200
@@ -11,8 +11,8 @@ menuconfig NET_TULIP
 if NET_TULIP
 
 config DE2104X
-	tristate "Early DECchip Tulip (dc2104x) PCI support (EXPERIMENTAL)"
-	depends on PCI && EXPERIMENTAL
+	tristate "Early DECchip Tulip (dc2104x) PCI support"
+	depends on PCI
 	select CRC32
 	---help---
 	  This driver is developed for the SMC EtherPower series Ethernet


-- 
Ondrej Zary

^ permalink raw reply

* Re: [RFC] Online firmware upgrade in non-embedded systems
From: Alexander Clouter @ 2010-09-28 18:45 UTC (permalink / raw)
  To: linux-kernel; +Cc: netdev, linux-mtd
In-Reply-To: <1285696787.2282.45.camel@achroite.uk.solarflarecom.com>

In gmane.linux.kernel Ben Hutchings <bhutchings@solarflare.com> wrote:
> 
> Currently the sfc network driver is optionally combined with an MTD
> driver (CONFIG_SFC_MTD) which exposes all upgradable firmware and
> configuration partitions in flash.  This works nicely in kernels with
> MTD enabled, but since MTD is mainly used in embedded systems with
> on-board flash it is often disabled in distribution kernels and custom
> kernels alike.  This leaves users of sfc unable to upgrade firmware
> without rebuilding the kernel or booting some other distribution.  The
> lack of widespread MTD support is a regular cause of support requests.
> 
----
ac56@proxy0:~$ uname -a
Linux proxy0 2.6.32-5-686 #1 SMP Wed Aug 25 14:28:12 UTC 2010 i686 GNU/Linux
ac56@proxy0:~$ cat /etc/debian_version 
squeeze/sid
ac56@proxy0:~$ grep CONFIG_SFC_MTD /boot/config-2.6.32-5-686 
CONFIG_SFC_MTD=y
ac56@proxy0:~$ grep '^CONFIG_MTD' /boot/config-2.6.32-5-686 | wc
     72      72    1693
----

I tell them "use a real distribution".

Cheers

-- 
Alexander Clouter
.sigmonster says: BOFH excuse #119:
                  evil hackers from Serbia.

^ permalink raw reply

* Re: igmp: Allow mininum interval specification for igmp timers.
From: Christoph Lameter @ 2010-09-28 18:42 UTC (permalink / raw)
  To: David Stevens
  Cc: David Miller, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA,
	netdev-owner-u79uwXL29TY76Z2rM5mHXA, rda-x0S3BwdUo6DQT0dZR+AlfA
In-Reply-To: <OF4DDFA464.A933254C-ON882577AB.00754000-882577AB.00778C77-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

On Mon, 27 Sep 2010, David Stevens wrote:

> > The second patch sets the intervals to X .. X + Rand (interval) and not
> to
> > a table of fixed intervals as you state here. I have pointed this out
> > before.
>
>         Sorry if I've misunderstood something you're proposing, but what
> you describe above would be certainly technically incorrect. There are
> really no circumstances for sending a report greater than <Interval>
> that is protocol-compliant. You can enforce a minimum greater than 0,
> which is a departure from both RFCs, though IGMPv2 uses wishy-washy
> language. The intent for both was to explicitly allow 0, IMO.

There is no igmp interval set by any igmp query yet so this is your usual
unresponsive crappy response to something else that we are not talking
about.

I thought you were talking about the "fixed intervals" that you saw in the
patch. These initial intervals are for unsolicited igmp reports (do I need
to add that statement to every sentence in a thread where we *only*
discuss unsolicited igmp issues?) and those "intervals" are randomized
and not fixed.

> > > as in the original patch. For v2, X=1 or 2 sec and Interval=10
> > > might work well, but for v3, the entire interval is 1 sec and I
> > > think I saw that the set-up time for the fabric may be on the
> > > order of 1 sec.
> >
> > Again there is no knowledge about V2 or V3 without a query and this is
> > during the period when no querier is known yet. You stated elsewhere
> that
> > I can assume V3 by default? So 1 sec?
>
>         Yes, without a querier or the tunable to force it to IGMPv2,
> the default is IGMPv3. It appears there is a bug where IGMPv3 is also
> using a 10sec interval (haven't verified that), but a 1 sec interval

You do not have the linux source code tree available?

from net/ipv4/igmp.c:

#define IGMP_Unsolicited_Report_Interval        (10*HZ)

> as required makes your situation worse, not better. It makes it even
> more likely that all the initial reports will occur before your set-up
> is done.

Right. So can you please give me an approach that considers all these
issues and does not invent problem that do not exist, stays within the
subject discussed and follows the RFCs?

  > > There can be any number of reasons that a short outage could prevent the
> > packets from going through. A buffer overrun (that you mentioned
> > elsewhere) usually causes lots of packets to be lost. Buffer overrun
> > scenarios usually mean that all igmp queries are lost.
>
>         You're arguing against protocol compliance. I didn't define
> the protocol, I only implemented it. And your view is through the
> IB lens, but I don't believe this is an actual problem in any way
> for typical networks. If you wrote a standards-track RFC that modifies
> IGMP for NBMA networks that require a delay or different parameters
> there, I'd have no objection to implementing that. Unilaterally
> changing linux's behavior on all network types without cause for
> departing from RFC on the most common types is another matter.

The RFCs state that the igmp queries have to be repeated at least 3
times. The first patch ensures that a mininum time passes between two igmp
reports (to avoid them getting lost in one go). The second patch doubles
the number of igmp reports and increases the intervals so that we still
have a chance to process the join before the next igmp query is send by
the router (which can be minuates away).

It fixes buggy havior that we see because multicast joins take very long
or fail outright.

 > > There is no solution on the IB layer since there is no notification when
> > the fabric reconfiguration necessary for an multicast group is complete.
>
>         Certainly that's not true; without notification, you can queue for
> first use of a new hardware multicast address and send the queue after an
> appropriate delay (1 sec? If that covers your set-up time). If you had
> positive acknowledgement from the IB network, you'd know exactly when to
> do it, but there's no need to change anything for non-IB networks here.

So you want an arbitrary delay for all new multicast traffic to be
created? I'd rather have a series of staggered attempts so that we can
avoid this setup time.

Also the setup time varies greatly depending on the complexity of the
fabric changes. It can be extremely fast if the multicast group is already
in use by others in the fabric. Adding a delay penalizes everyone
unnecessarily.

Also much of these seems to be contigent on IGMPv3. We are using igmpv2.

> > The querier is of not use since (for the gazillionth of times) this is
> an
> > unsolicited IGMP report. If there is a querier then the unsolicited igmp
> > reports would not be used but the timeout indicated by the querier would
> > be used.
>
>         A querier affects unsolicited reports because it sets both the
> query interval and the robustness value. If you want to send 10 reports,
> you can cause that by having a querier that sets it to that many. The
> initial join would then send 10 reports and the query interval can also
> be as low as you like.

The Linux IGMP subsystem does not support either of those at this point.
When the multicast group is created it has no notion of query intervals
until the first igmp query is received. That is the period of interest
that we are discussing!

>         But the linux code is not just for your particular problem or
> particular configuration. You can solve your problem by adding a querier,
> but I know you're trying to do it without. The mail I was responding to
> referred also to the case of a querier present, which is actually the
> "normal" case for using full IGMP is. I'm saying that for the non-querier
> case, making those per-interface configurable is reasonable because
> they *are* querier-changeable, but you can also use a querier to change
> it _for_the_unsolicited_reports_, as well as making the querier interval
> small enough that you don't have to care at all whether any or all of
> the unsolicited reports are lost.

The network of course has a querier that sents igmp requests in intervals
that could span minutes. We are talking about the period of time after a
join when a multicast group has been created but we have not reached the
time when the router sends an igmp query and where the various bits of
information about the igmp handling can be determined for the multicast
group.

I am not sure that you comprehend the basics of IGMP processing in the
kernel. I see knowledge about IGMP in general but you have a difficult
time to relate that to what the Linux kernel actually does.

Would you please have a look at the source code?
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

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

Print media debug messages only when HW debug is enabled.

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

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


-- 
Ondrej Zary

^ permalink raw reply


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