Patchwork [Lucid-LBM] SRU: Add backported qcserial driver [Maverick-RFC]

login
register
mail settings
Submitter Stefan Bader
Date July 31, 2010, 10:37 a.m.
Message ID <1280572664-12156-1-git-send-email-stefan.bader@canonical.com>
Download mbox | patch
Permalink /patch/60396/
State Accepted
Delegated to: Stefan Bader
Headers show

Comments

Stefan Bader - July 31, 2010, 10:37 a.m.
SRU justification:

Impact: Certain models of 3G hardware (Gobi) which apparently are quite
common, require several changes to work. First, the kernel module needs
to be updated, which requires larger modifications. Second, a new firm-
ware loader is required together with a matching udev rule.

Fix: In order to prevent regressions, the driver backport was done in
LBM and also adds a new binary subpackage (lbm-wwan). This package also
contains the loader and the udev rule.
NOTE! The drivers also requires firmware files which need to be manually
gathered as they are not redistributable.

Testcase: Without this backport no 3G connections are possible. Sevral
users in the bug report had successfully used the driver from the test
package after collecting the required firmware files.

RFC Maverick:
The kernel parts are already in Maverick. But the firmware loader and the
udev rule would need to get packaged somewhere. I am not sure this still
can be done in time, so maybe the userspace part would need to go into
Maverick's LBM. Though it sounds a bit unclean.
For future releases it would be valuable to evaluate whether the firmware
might be acquired semi-automatically via fw-cutter.

-Stefan

From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00 2001
From: Stefan Bader <stefan.bader@canonical.com>
Date: Mon, 21 Jun 2010 18:40:57 +0200
Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial

BugLink: http://bugs.launchpad.net/bugs/554099

This adds a new sub-package which is intended to hold backported WWAN
(wireless wide area network) modules. The start is made by a backported
version of the qcserial driver.

Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
---
 debian/control.d/flavour-control.stub              |   14 +
 debian/rules.d/2-binary-arch.mk                    |   23 +
 updates/Makefile                                   |    1 +
 updates/wwan-drivers/BOM                           |   17 +
 updates/wwan-drivers/Makefile                      |   24 +
 updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules |   33 +
 updates/wwan-drivers/gobi_loader-0.6/Makefile      |   26 +
 updates/wwan-drivers/gobi_loader-0.6/README        |   62 ++
 updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c |  293 +++++++++
 updates/wwan-drivers/qcserial.c                    |  227 +++++++
 updates/wwan-drivers/usb-wwan.h                    |   67 ++
 updates/wwan-drivers/usb_wwan.c                    |  665 ++++++++++++++++++++
 12 files changed, 1452 insertions(+), 0 deletions(-)
 create mode 100644 updates/wwan-drivers/BOM
 create mode 100644 updates/wwan-drivers/Makefile
 create mode 100644 updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules
 create mode 100644 updates/wwan-drivers/gobi_loader-0.6/Makefile
 create mode 100644 updates/wwan-drivers/gobi_loader-0.6/README
 create mode 100644 updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c
 create mode 100644 updates/wwan-drivers/qcserial.c
 create mode 100644 updates/wwan-drivers/usb-wwan.h
 create mode 100644 updates/wwan-drivers/usb_wwan.c
Tim Gardner - Aug. 2, 2010, 1:27 p.m.
On 07/31/2010 04:37 AM, Stefan Bader wrote:
> SRU justification:
>
> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
> common, require several changes to work. First, the kernel module needs
> to be updated, which requires larger modifications. Second, a new firm-
> ware loader is required together with a matching udev rule.
>
> Fix: In order to prevent regressions, the driver backport was done in
> LBM and also adds a new binary subpackage (lbm-wwan). This package also
> contains the loader and the udev rule.
> NOTE! The drivers also requires firmware files which need to be manually
> gathered as they are not redistributable.
>
> Testcase: Without this backport no 3G connections are possible. Sevral
> users in the bug report had successfully used the driver from the test
> package after collecting the required firmware files.
>
> RFC Maverick:
> The kernel parts are already in Maverick. But the firmware loader and the
> udev rule would need to get packaged somewhere. I am not sure this still
> can be done in time, so maybe the userspace part would need to go into
> Maverick's LBM. Though it sounds a bit unclean.
> For future releases it would be valuable to evaluate whether the firmware
> might be acquired semi-automatically via fw-cutter.
>
> -Stefan
>
>  From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00 2001
> From: Stefan Bader<stefan.bader@canonical.com>
> Date: Mon, 21 Jun 2010 18:40:57 +0200
> Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial
>
> BugLink: http://bugs.launchpad.net/bugs/554099
>
> This adds a new sub-package which is intended to hold backported WWAN
> (wireless wide area network) modules. The start is made by a backported
> version of the qcserial driver.
>
> Signed-off-by: Stefan Bader<stefan.bader@canonical.com>
> ---

Acked-by: Tim Gardner <tim.gardner@canonical.com> for Lucid

If there are no kernel components for Maverick, then perhaps the loader 
and udev rule should just be a Universe package. Are you thinking about 
doing it?

rtg
Stefan Bader - Aug. 2, 2010, 1:33 p.m.
On 08/02/2010 03:27 PM, Tim Gardner wrote:
> On 07/31/2010 04:37 AM, Stefan Bader wrote:
>> SRU justification:
>>
>> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
>> common, require several changes to work. First, the kernel module needs
>> to be updated, which requires larger modifications. Second, a new firm-
>> ware loader is required together with a matching udev rule.
>>
>> Fix: In order to prevent regressions, the driver backport was done in
>> LBM and also adds a new binary subpackage (lbm-wwan). This package also
>> contains the loader and the udev rule.
>> NOTE! The drivers also requires firmware files which need to be manually
>> gathered as they are not redistributable.
>>
>> Testcase: Without this backport no 3G connections are possible. Sevral
>> users in the bug report had successfully used the driver from the test
>> package after collecting the required firmware files.
>>
>> RFC Maverick:
>> The kernel parts are already in Maverick. But the firmware loader and the
>> udev rule would need to get packaged somewhere. I am not sure this still
>> can be done in time, so maybe the userspace part would need to go into
>> Maverick's LBM. Though it sounds a bit unclean.
>> For future releases it would be valuable to evaluate whether the firmware
>> might be acquired semi-automatically via fw-cutter.
>>
>> -Stefan
>>
>>  From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00 2001
>> From: Stefan Bader<stefan.bader@canonical.com>
>> Date: Mon, 21 Jun 2010 18:40:57 +0200
>> Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial
>>
>> BugLink: http://bugs.launchpad.net/bugs/554099
>>
>> This adds a new sub-package which is intended to hold backported WWAN
>> (wireless wide area network) modules. The start is made by a backported
>> version of the qcserial driver.
>>
>> Signed-off-by: Stefan Bader<stefan.bader@canonical.com>
>> ---
> 
> Acked-by: Tim Gardner <tim.gardner@canonical.com> for Lucid
> 
> If there are no kernel components for Maverick, then perhaps the loader
> and udev rule should just be a Universe package. Are you thinking about
> doing it?
> 
> rtg

Not yet really. I first wanted to see what options there would be. Would things
like that fit into the linux-firmware package? Or is that restricted to only fw
bits themselves?
Is there still time to come up with a new package for Maverick? When the general
opinion is to have a universe package (e.g. gobi-loader) I can do that.

-Stefan
Tim Gardner - Aug. 2, 2010, 2:05 p.m.
On 08/02/2010 07:33 AM, Stefan Bader wrote:
> On 08/02/2010 03:27 PM, Tim Gardner wrote:
>> On 07/31/2010 04:37 AM, Stefan Bader wrote:
>>> SRU justification:
>>>
>>> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
>>> common, require several changes to work. First, the kernel module needs
>>> to be updated, which requires larger modifications. Second, a new firm-
>>> ware loader is required together with a matching udev rule.
>>>
>>> Fix: In order to prevent regressions, the driver backport was done in
>>> LBM and also adds a new binary subpackage (lbm-wwan). This package also
>>> contains the loader and the udev rule.
>>> NOTE! The drivers also requires firmware files which need to be manually
>>> gathered as they are not redistributable.
>>>
>>> Testcase: Without this backport no 3G connections are possible. Sevral
>>> users in the bug report had successfully used the driver from the test
>>> package after collecting the required firmware files.
>>>
>>> RFC Maverick:
>>> The kernel parts are already in Maverick. But the firmware loader and the
>>> udev rule would need to get packaged somewhere. I am not sure this still
>>> can be done in time, so maybe the userspace part would need to go into
>>> Maverick's LBM. Though it sounds a bit unclean.
>>> For future releases it would be valuable to evaluate whether the firmware
>>> might be acquired semi-automatically via fw-cutter.
>>>
>>> -Stefan
>>>
>>>   From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00 2001
>>> From: Stefan Bader<stefan.bader@canonical.com>
>>> Date: Mon, 21 Jun 2010 18:40:57 +0200
>>> Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial
>>>
>>> BugLink: http://bugs.launchpad.net/bugs/554099
>>>
>>> This adds a new sub-package which is intended to hold backported WWAN
>>> (wireless wide area network) modules. The start is made by a backported
>>> version of the qcserial driver.
>>>
>>> Signed-off-by: Stefan Bader<stefan.bader@canonical.com>
>>> ---
>>
>> Acked-by: Tim Gardner<tim.gardner@canonical.com>  for Lucid
>>
>> If there are no kernel components for Maverick, then perhaps the loader
>> and udev rule should just be a Universe package. Are you thinking about
>> doing it?
>>
>> rtg
>
> Not yet really. I first wanted to see what options there would be. Would things
> like that fit into the linux-firmware package? Or is that restricted to only fw
> bits themselves?
> Is there still time to come up with a new package for Maverick? When the general
> opinion is to have a universe package (e.g. gobi-loader) I can do that.
>
> -Stefan

Currently linux-firmware is just the firmware bits. Given the 
non-redistributable nature of the Gobi firmware bits, I would not 
advocate adding loader and udev support to a package that is composed 
entirely of redistributable firmware.

For Maverick I think a universe package is the way to go. There is still 
about a week left to add a new package (before it gets really painful to 
do so).

rtg
Stefan Bader - Aug. 2, 2010, 5:52 p.m.
On 08/02/2010 04:05 PM, Tim Gardner wrote:
> On 08/02/2010 07:33 AM, Stefan Bader wrote:
>> On 08/02/2010 03:27 PM, Tim Gardner wrote:
>>> On 07/31/2010 04:37 AM, Stefan Bader wrote:
>>>> SRU justification:
>>>>
>>>> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
>>>> common, require several changes to work. First, the kernel module needs
>>>> to be updated, which requires larger modifications. Second, a new firm-
>>>> ware loader is required together with a matching udev rule.
>>>>
>>>> Fix: In order to prevent regressions, the driver backport was done in
>>>> LBM and also adds a new binary subpackage (lbm-wwan). This package also
>>>> contains the loader and the udev rule.
>>>> NOTE! The drivers also requires firmware files which need to be
>>>> manually
>>>> gathered as they are not redistributable.
>>>>
>>>> Testcase: Without this backport no 3G connections are possible. Sevral
>>>> users in the bug report had successfully used the driver from the test
>>>> package after collecting the required firmware files.
>>>>
>>>> RFC Maverick:
>>>> The kernel parts are already in Maverick. But the firmware loader
>>>> and the
>>>> udev rule would need to get packaged somewhere. I am not sure this
>>>> still
>>>> can be done in time, so maybe the userspace part would need to go into
>>>> Maverick's LBM. Though it sounds a bit unclean.
>>>> For future releases it would be valuable to evaluate whether the
>>>> firmware
>>>> might be acquired semi-automatically via fw-cutter.
>>>>
>>>> -Stefan
>>>>
>>>>   From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00
>>>> 2001
>>>> From: Stefan Bader<stefan.bader@canonical.com>
>>>> Date: Mon, 21 Jun 2010 18:40:57 +0200
>>>> Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial
>>>>
>>>> BugLink: http://bugs.launchpad.net/bugs/554099
>>>>
>>>> This adds a new sub-package which is intended to hold backported WWAN
>>>> (wireless wide area network) modules. The start is made by a backported
>>>> version of the qcserial driver.
>>>>
>>>> Signed-off-by: Stefan Bader<stefan.bader@canonical.com>
>>>> ---
>>>
>>> Acked-by: Tim Gardner<tim.gardner@canonical.com>  for Lucid
>>>
>>> If there are no kernel components for Maverick, then perhaps the loader
>>> and udev rule should just be a Universe package. Are you thinking about
>>> doing it?
>>>
>>> rtg
>>
>> Not yet really. I first wanted to see what options there would be.
>> Would things
>> like that fit into the linux-firmware package? Or is that restricted
>> to only fw
>> bits themselves?
>> Is there still time to come up with a new package for Maverick? When
>> the general
>> opinion is to have a universe package (e.g. gobi-loader) I can do that.
>>
>> -Stefan
> 
> Currently linux-firmware is just the firmware bits. Given the
> non-redistributable nature of the Gobi firmware bits, I would not
> advocate adding loader and udev support to a package that is composed
> entirely of redistributable firmware.
> 
> For Maverick I think a universe package is the way to go. There is still
> about a week left to add a new package (before it gets really painful to
> do so).
> 
> rtg

I tried to come up with a packaging which can be checked at

git://kernel.ubuntu.com/smb/gobi-loader.git debian

Though I am not confident this is complete, yet. Somehow I have no clue how to
target it for universe. Whoever has time, some review and feedback would be
appreciated.

-Stefan
Stefan Bader - Aug. 3, 2010, 4:57 p.m.
On 08/02/2010 03:27 PM, Tim Gardner wrote:
> On 07/31/2010 04:37 AM, Stefan Bader wrote:
>> SRU justification:
>>
>> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
>> common, require several changes to work. First, the kernel module needs
>> to be updated, which requires larger modifications. Second, a new firm-
>> ware loader is required together with a matching udev rule.
>>
>> Fix: In order to prevent regressions, the driver backport was done in
>> LBM and also adds a new binary subpackage (lbm-wwan). This package also
>> contains the loader and the udev rule.
>> NOTE! The drivers also requires firmware files which need to be manually
>> gathered as they are not redistributable.
>>
>> Testcase: Without this backport no 3G connections are possible. Sevral
>> users in the bug report had successfully used the driver from the test
>> package after collecting the required firmware files.
>>
>> RFC Maverick:
>> The kernel parts are already in Maverick. But the firmware loader and the
>> udev rule would need to get packaged somewhere. I am not sure this still
>> can be done in time, so maybe the userspace part would need to go into
>> Maverick's LBM. Though it sounds a bit unclean.
>> For future releases it would be valuable to evaluate whether the firmware
>> might be acquired semi-automatically via fw-cutter.
>>
...
> If there are no kernel components for Maverick, then perhaps the loader
> and udev rule should just be a Universe package. Are you thinking about
> doing it?

The package gobi-loader has been accepted for Maverick.
Brad Figg - Aug. 5, 2010, 7:06 p.m.
On 08/02/2010 06:27 AM, Tim Gardner wrote:
> On 07/31/2010 04:37 AM, Stefan Bader wrote:
>> SRU justification:
>>
>> Impact: Certain models of 3G hardware (Gobi) which apparently are quite
>> common, require several changes to work. First, the kernel module needs
>> to be updated, which requires larger modifications. Second, a new firm-
>> ware loader is required together with a matching udev rule.
>>
>> Fix: In order to prevent regressions, the driver backport was done in
>> LBM and also adds a new binary subpackage (lbm-wwan). This package also
>> contains the loader and the udev rule.
>> NOTE! The drivers also requires firmware files which need to be manually
>> gathered as they are not redistributable.
>>
>> Testcase: Without this backport no 3G connections are possible. Sevral
>> users in the bug report had successfully used the driver from the test
>> package after collecting the required firmware files.
>>
>> RFC Maverick:
>> The kernel parts are already in Maverick. But the firmware loader and the
>> udev rule would need to get packaged somewhere. I am not sure this still
>> can be done in time, so maybe the userspace part would need to go into
>> Maverick's LBM. Though it sounds a bit unclean.
>> For future releases it would be valuable to evaluate whether the firmware
>> might be acquired semi-automatically via fw-cutter.
>>
>> -Stefan
>>
>>   From f8de5aa33acc82fd77ac79e734f59f59b540f677 Mon Sep 17 00:00:00 2001
>> From: Stefan Bader<stefan.bader@canonical.com>
>> Date: Mon, 21 Jun 2010 18:40:57 +0200
>> Subject: [PATCH] UBUNTU: Add WWAN sub-package and backported qcserial
>>
>> BugLink: http://bugs.launchpad.net/bugs/554099
>>
>> This adds a new sub-package which is intended to hold backported WWAN
>> (wireless wide area network) modules. The start is made by a backported
>> version of the qcserial driver.
>>
>> Signed-off-by: Stefan Bader<stefan.bader@canonical.com>
>> ---
>
> Acked-by: Tim Gardner<tim.gardner@canonical.com>  for Lucid
>
> If there are no kernel components for Maverick, then perhaps the loader
> and udev rule should just be a Universe package. Are you thinking about
> doing it?
>
> rtg

Acked-by: Brad Figg <brad.figg@canonical.com>
Stefan Bader - Aug. 6, 2010, 12:23 p.m.
Applied to Lucid LBM (update to meta will be done as well)

Patch

diff --git a/debian/control.d/flavour-control.stub b/debian/control.d/flavour-control.stub
index 2838603..3176c8e 100644
--- a/debian/control.d/flavour-control.stub
+++ b/debian/control.d/flavour-control.stub
@@ -49,6 +49,20 @@  Description: Ubuntu supplied Linux modules for version PKGVER ALSA snapshots.
  the linux-backports-modules-alsa-FLAVOUR meta-package, which will ensure that upgrades work
  correctly, and that supporting packages are also installed.
 
+Package: linux-backports-modules-wwan-PKGVER-ABINUM-FLAVOUR
+Architecture: ARCH
+Section: SECTION_IMAGE
+Priority: optional
+Provides:
+Depends: linux-image-PKGVER-ABINUM-FLAVOUR
+Pre-Depends: dpkg (>= 1.10.24)
+Description: Ubuntu supplied Linux modules for version PKGVER on DESC.
+ This package contains WWAN modules supplied by Ubuntu for Linux PKGVER.
+ .
+ You likely do not want to install this package directly. Instead, install
+ the linux-backports-modules-wwan-FLAVOUR meta-package, which will ensure that
+ upgrades work correctly, and that supporting packages are also installed.
+
 #Package: linux-backports-modules-nouveau-PKGVER-ABINUM-FLAVOUR
 #Architecture: ARCH
 #Section: SECTION_IMAGE
diff --git a/debian/rules.d/2-binary-arch.mk b/debian/rules.d/2-binary-arch.mk
index 90f84c0..2561801 100644
--- a/debian/rules.d/2-binary-arch.mk
+++ b/debian/rules.d/2-binary-arch.mk
@@ -63,6 +63,7 @@  $(stampdir)/stamp-build-%: $(stampdir)/stamp-prepare-%
 	@echo "Building $*..."
 	cd $(builddir)/build-$*/$(CWDIR) && $(make_compat)
 	cd $(builddir)/build-$*/alsa-driver && make $(conc_level)
+	cd $(builddir)/build-$*/wwan-drivers && make $(conc_level)
 	$(kmake) $(conc_level) modules
 	@touch $@
 
@@ -71,6 +72,9 @@  install-%: cwpkgdir = $(CURDIR)/debian/linux-backports-modules-wireless-$(releas
 install-%: cwblddir = $(builddir)/build-$*/$(CWDIR)
 install-%: cwmoddir = $(cwpkgdir)/lib/modules/$(release)-$(abinum)-$*
 install-%: cwsrcdir = $(CURDIR)/updates/$(CWDIR)
+install-%: wwpkgdir = $(CURDIR)/debian/linux-backports-modules-wwan-$(release)-$(abinum)-$*
+install-%: wwmoddir = $(wwpkgdir)/lib/modules/$(release)-$(abinum)-$*
+install-%: wwsrcdir = $(CURDIR)/updates/wwan-drivers
 install-%: cspkgdir = $(CURDIR)/debian/linux-backports-modules-alsa-$(release)-$(abinum)-$*
 install-%: csmoddir = $(cspkgdir)/lib/modules/$(release)-$(abinum)-$*
 install-%: nvpkgdir = $(CURDIR)/debian/linux-backports-modules-nouveau-$(release)-$(abinum)-$*
@@ -161,6 +165,24 @@  ifeq ($(do_nouveau_package),true)
 endif
 
 	#
+	# Build the wwan-drivers packages.
+	#
+	install -d $(wwmoddir)/updates/wwan
+	find $(builddir)/build-*/wwan-drivers -type f -name '*.ko' |	       \
+	while read f; do						       \
+		install -v $$f $(wwmoddir)/updates/wwan/;		       \
+		strip --strip-debug $(wwmoddir)/updates/wwan/$$(basename $$f); \
+	done
+	$(MAKE) -C $(wwsrcdir) prefix=$(wwpkgdir) install
+	install -d $(wwpkgdir)/DEBIAN
+	for script in postinst postrm; do				       \
+		sed -e 's/@@KVER@@/$(release)-$(abinum)-$*/g'		       \
+			debian/control-scripts/$$script			       \
+			>$(wwpkgdir)/DEBIAN/$$script;			       \
+		chmod 755 $(wwpkgdir)/DEBIAN/$$script;			       \
+	done
+	
+	#
 	# The flavour specific headers package
 	#
 	install -d $(hdrdir)/include
@@ -186,6 +208,7 @@  package_list += linux-backports-modules-alsa-$(release)-$(abinum)-$*
 ifeq ($(do_nouveau_package),true)
 package_list += linux-backports-modules-nouveau-$(release)-$(abinum)-$*
 endif
+package_list += linux-backports-modules-wwan-$(release)-$(abinum)-$*
 
 binary-modules-%: install-%
 	dh_testdir
diff --git a/updates/Makefile b/updates/Makefile
index e6e25b5..7ab46a7 100644
--- a/updates/Makefile
+++ b/updates/Makefile
@@ -12,3 +12,4 @@  KBUILD_EXTMOD	:= $(src)/alsa-driver
 
 obj-y	+= wireless-staging/
 obj-y	+= thinkpad-acpi/
+obj-y	+= wwan-drivers/
diff --git a/updates/wwan-drivers/BOM b/updates/wwan-drivers/BOM
new file mode 100644
index 0000000..34a2cf5
--- /dev/null
+++ b/updates/wwan-drivers/BOM
@@ -0,0 +1,17 @@ 
+History of files:
+
+ * qcserial.c (v2.6.34:drivers/usb/serial)
+   + 0d4561947b8ddd5d944bdbbdc1ea1d6fd9a06041 (upstream)
+     usb serial: Add generic USB wwan support
+   + 3d7e59ad88fdb6bc50ae9b7e822d4bb5f68b68f9 (upstream)
+     USB: qcserial: Use generic USB wwan code
+   + e07896e62abbf7a741a5cd5b25ba7637bdf91ad0 (upstream)
+     USB: qcserial: Add support for Qualcomm Gobi 2000 devices
+
+ * gobi_loader-0.6 (http://www.codon.org.uk/~mjg59/gobi_loader/):
+   - http://www.codon.org.uk/~mjg59/gobi_loader/download/
+
+The qcserial driver/gobi_loader combination relies on firmware which cannot
+be redistributed (qmss.mbn, apps.mbn, UQCN.mbn). Some hints found on the
+gobi_loader page.
+
diff --git a/updates/wwan-drivers/Makefile b/updates/wwan-drivers/Makefile
new file mode 100644
index 0000000..9455e12
--- /dev/null
+++ b/updates/wwan-drivers/Makefile
@@ -0,0 +1,24 @@ 
+ifeq ($(KERNELRELEASE),)
+GOBI_VERSION = 0.6
+
+all:	gobi_loader
+
+gobi_loader:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION)
+
+.PHONY: install
+install:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION) prefix=$(prefix) install
+	DOCDIR=$(prefix)/usr/share/doc/$$(basename $(prefix));		\
+	install -d $$DOCDIR;						\
+	install -v gobi_loader-$(GOBI_VERSION)/README			\
+		$$DOCDIR/README.gobi_loader
+
+.PHONY: clean
+clean:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION) clean
+else
+#warning hello
+obj-m += qcserial.o
+obj-m += usb_wwan.o
+endif
diff --git a/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules b/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules
new file mode 100644
index 0000000..649a7a1
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules
@@ -0,0 +1,33 @@ 
+# udev rules for firmware loading on qualcomm gobi devices
+
+ACTION=="add", SUBSYSTEM=="tty" KERNEL=="ttyUSB*" GOTO="gobi_rules"
+
+GOTO="gobi_rules_end"
+
+LABEL="gobi_rules"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9211", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="201d", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="04da", ATTRS{idProduct}=="250c", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8171", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1410", ATTRS{idProduct}=="a008", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="1774", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="fff2", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1557", ATTRS{idProduct}=="0a80", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9008", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9201", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9221", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9231", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1f45", ATTRS{idProduct}=="0001", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="16d8", ATTRS{idProduct}=="8001", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9000", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="241d", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9204", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9214", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9224", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9234", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9244", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9264", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9274", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8185", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+
+LABEL="gobi_rules_end"
diff --git a/updates/wwan-drivers/gobi_loader-0.6/Makefile b/updates/wwan-drivers/gobi_loader-0.6/Makefile
new file mode 100644
index 0000000..41cace0
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/Makefile
@@ -0,0 +1,26 @@ 
+VERSION = 0.6
+
+gobi_loader: gobi_loader.c
+	gcc -Wall gobi_loader.c -o gobi_loader
+
+all: gobi_loader
+
+install: gobi_loader
+	install -D gobi_loader ${prefix}/lib/udev/gobi_loader
+	install -D 60-gobi.rules ${prefix}/lib/udev/rules.d/60-gobi.rules
+	mkdir -p ${prefix}/lib/firmware
+	-udevadm control --reload-rules
+
+uninstall:
+	-rm $(prefix)/lib/udev/gobi_loader
+	-rm $(prefix)/etc/udev/rules.d/60-gobi.rules
+
+clean:
+	-rm gobi_loader
+	-rm *~
+
+dist:
+	mkdir gobi_loader-$(VERSION)
+	cp gobi_loader.c README Makefile 60-gobi.rules gobi_loader-$(VERSION)
+	tar zcf gobi_loader-$(VERSION).tar.gz gobi_loader-$(VERSION)
+	rm -rf gobi_loader-$(VERSION)
diff --git a/updates/wwan-drivers/gobi_loader-0.6/README b/updates/wwan-drivers/gobi_loader-0.6/README
new file mode 100644
index 0000000..eef40f7
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/README
@@ -0,0 +1,62 @@ 
+gobi_loader
+
+gobi_loader is a firmware loader for Qualcomm Gobi USB chipsets. These devices
+appear in an uninitialised state when power is applied and require firmware
+to be loaded before they can be used as modems. gobi_loader adds a udev rule
+that will trigger loading of the firmware and make the modem usable.
+
+Installing:
+
+make; make install
+
+You also need the qcserial driver. This is included in kernels 2.6.30 and
+later. Ensure that it has the IDs for your device. If not, add a line like
+
+{USB_DEVICE(0x1234, 0x5678)},
+
+to the id_table structure in drivers/usb/serial/qcserial.c and
+rebuilt. This device is the firmware loading device and is not usable
+as a modem. When loaded, qcserial should create a /dev/ttyUSB
+device. Check that /etc/udev/rules.d/60-gobi.rules has an entry for your device
+- if not, copy one of the existing lines and change the vendor and product IDs.
+Note that a device line is only needed for the firmware loading ID, not the
+modem ID.
+
+Now you need the modem firmware. This can be obtained from a Windows
+install - alternatively it may be possible to download from your
+vendor's site and extracted with wine. You need the amss.mbn and
+apps.mbn files corresponding to your mobile provider. For Gobi 2000
+devices you also need UQCN.mbn. As yet, I don't have a good mapping
+between devices and the appropriate firmware, so you'll need to figure
+this out yourself.  Remember that some mobile providers use CDMA and
+some use GSM - the CDMA firmware will typically be a smaller file than
+the GSM firmware (approximately 5MB for CDMA firmware, approximately
+9MB for GSM firmware). On my install, these files could be found in a
+QDLService/Packages directory.
+
+Please don't ask me for firmware. It's copyright Qualcomm and I can't
+redistribute it.
+
+Copy the appropriate firmware into /lib/firmware/gobi. Unload and
+reload the qcserial driver or reboot your machine. Assuming you
+installed the application and rules correctly, and assuming that
+qcserial and the rules file both contain your modem devices, your
+firmware will now load. The firmware loading device will now detach
+from your system and reattach with a different ID. If you had to add
+the firmware loading device to qcserial.c then you will probably also
+need to add the modem device. However, the modem device does not need
+to be added to the udev rules file.
+
+A /dev/ttyUSB device will now exist for your modem. Recent versions of
+network-manager should automatically pick it up - older versions (and
+any other modem management software) may need more assistence.
+
+Author:
+
+This code was writte by Matthew Garrett <mjg@redhat.com> and is
+released under the terms of version 2 of the GNU General Public
+License. It is based on code written by Alexander Shumakovitch and
+contains crc generation code from the Linux kernel. Gobi 2000 support
+was provided by Anssi Hannula. The code was written by examining USB
+traffic dumps under Windows - the Qualcomm drivers or firmware have
+not been reverse engineered or disassembled in any way.
\ No newline at end of file
diff --git a/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c b/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c
new file mode 100644
index 0000000..690f4ef
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c
@@ -0,0 +1,293 @@ 
+/* Firmware loader for Qualcomm Gobi USB hardware */
+
+/* Copyright 2009 Red Hat <mjg@redhat.com> - heavily based on work done by
+ * Alexander Shumakovitch <shurik@gwu.edu>
+ *
+ * Gobi 2000 support provided by Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * crc-ccitt code derived from the Linux kernel, lib/crc-ccitt.c
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <malloc.h>
+
+char magic1[] = {0x01, 0x51, 0x43, 0x4f, 0x4d, 0x20, 0x68, 0x69,
+		 0x67, 0x68, 0x20, 0x73, 0x70, 0x65, 0x65, 0x64, 0x20, 
+		 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20,
+		 0x68, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04,
+		 0x30, 0xff, 0xff};
+
+//char magic1[] = "QCOM high speed protocol hst\0\0\0\0\x04\x04\x30";
+
+char magic2[] = {0x25, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic3[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic4[] = {0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic5[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic6[] = {0x25, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic7[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic8[] = {0x29, 0xff, 0xff};
+
+/*
+ * This mysterious table is just the CRC of each possible byte. It can be
+ * computed using the standard bit-at-a-time methods. The polynomial can
+ * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12.
+ * Add the implicit x^16, and you have the standard CRC-CCITT.
+ */
+
+unsigned short const crc_ccitt_table[256] = {
+	0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+	0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+	0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+	0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+	0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+	0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+	0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+	0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+	0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+	0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+	0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+	0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+	0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+	0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+	0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+	0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+	0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+	0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+	0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+	0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+	0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+	0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+	0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+	0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+	0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+	0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+	0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+	0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+	0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+	0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+	0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+	0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+unsigned short crc_ccitt_byte(unsigned short crc, const char c)
+{
+        return (crc >> 8) ^ crc_ccitt_table[(crc ^ c) & 0xff];
+}
+
+/**
+ *	crc_ccitt - recompute the CRC for the data buffer
+ *	@crc: previous CRC value
+ *	@buffer: data pointer
+ *	@len: number of bytes in the buffer
+ */
+unsigned short crc_ccitt(short crc, char const *buffer, size_t len)
+{
+	while (len--)
+		crc = crc_ccitt_byte(crc, *buffer++);
+	return crc;
+}
+
+void usage (char **argv) {
+	printf ("usage: %s [-2000] serial_device firmware_dir\n", argv[0]);
+}
+
+int main(int argc, char **argv) {	
+	int serialfd;
+	int fwfd;
+	int len;
+	int err;
+	int gobi2000 = 0;
+	struct termios terminal_data;
+	struct stat file_data;
+	char *fwdata = malloc(1024*1024);
+
+	if (argc < 3 || argc > 4) {
+		usage(argv);
+		return -1;
+	}
+
+	if (!fwdata) {
+		fprintf(stderr, "Failed to allocate memory for firmware\n");
+		return -1;
+	}
+
+	if (argc == 4) {
+		if (!strcmp(argv[1], "-2000")) {
+			gobi2000=1;
+			magic1[33]++;
+			magic1[34]++;
+		} else {
+			usage(argv);
+		}
+	}
+
+	serialfd = open(argv[argc-2], O_RDWR);
+
+	if (serialfd == -1) {
+		perror("Failed to open serial device: ");
+		usage(argv);
+		return -1;
+	}
+
+	err = chdir(argv[argc-1]);
+	if (err) {
+		perror("Failed to change directory: ");
+		usage(argv);
+		return -1;
+	}
+
+	fwfd = open("amss.mbn", O_RDONLY);
+
+	if (fwfd == -1) {
+		perror("Failed to open firmware: ");
+		usage(argv);
+		return -1;
+	}
+
+	fstat(fwfd, &file_data);
+	*(int *)&magic2[2] = file_data.st_size - 8;
+	*(int *)&magic3[7] = file_data.st_size - 8;
+	*(short *)&magic1[sizeof(magic1)-2] =
+		~crc_ccitt(0xffff, magic1, sizeof(magic1)-2);
+	*(short *)&magic2[sizeof(magic2)-2] =
+		~crc_ccitt(0xffff, magic2, sizeof(magic2)-2);
+	*(short *)&magic3[sizeof(magic3)-2] =
+		~crc_ccitt(0xffff, magic3, sizeof(magic3)-2);
+
+	tcgetattr (serialfd, &terminal_data);
+	cfmakeraw (&terminal_data);
+	tcsetattr (serialfd, TCSANOW, &terminal_data);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic1, sizeof(magic1));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic2, sizeof(magic2));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, magic3, sizeof(magic3));
+
+	while (1) {
+		len = read (fwfd, fwdata, 1024*1024);
+		if (len == 1024*1024)
+			write (serialfd, fwdata, 1024*1024);
+		else {
+			write (serialfd, fwdata, len-8);
+			break;
+		}
+		write (serialfd, fwdata, 0);
+	}
+
+	fwfd = open("apps.mbn", O_RDONLY);
+
+	if (fwfd == -1) {
+		perror("Failed to open secondary firmware: ");
+		usage(argv);
+		return -1;
+	}
+
+	fstat(fwfd, &file_data);
+	*(int *)&magic4[2] = file_data.st_size;
+	*(int *)&magic5[7] = file_data.st_size;
+
+	*(short *)&magic4[sizeof(magic4)-2] =
+		~crc_ccitt(0xffff, magic4, sizeof(magic4)-2);
+	*(short *)&magic5[sizeof(magic5)-2] =
+		~crc_ccitt(0xffff, magic5, sizeof(magic5)-2);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic4, sizeof(magic4));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, magic5, sizeof(magic5));
+
+	while (1) {
+		len = read (fwfd, fwdata, 1024*1024);
+		if (len == 1024*1024)
+			write (serialfd, fwdata, 1024*1024);
+		else {
+			write (serialfd, fwdata, len);
+			break;
+		}
+		write (serialfd, fwdata, 0);
+	}
+
+	if (gobi2000) {
+		fwfd = open("UQCN.mbn", O_RDONLY);
+
+		if (fwfd == -1)
+			fwfd = open("uqcn.mbn", O_RDONLY);
+
+		if (fwfd == -1) {
+			perror("Failed to open tertiary firmware: ");
+			usage(argv);
+			return -1;
+		}
+
+		fstat(fwfd, &file_data);
+		*(int *)&magic6[2] = file_data.st_size;
+		*(int *)&magic7[7] = file_data.st_size;
+
+		*(short *)&magic6[sizeof(magic6)-2] =
+			~crc_ccitt(0xffff, magic6, sizeof(magic6)-2);
+		*(short *)&magic7[sizeof(magic7)-2] =
+			~crc_ccitt(0xffff, magic7, sizeof(magic7)-2);
+
+		write (serialfd, "\x7e", 1);
+		write (serialfd, magic6, sizeof(magic6));
+		write (serialfd, "\x7e", 1);
+
+		write (serialfd, magic7, sizeof(magic7));
+
+		while (1) {
+			len = read (fwfd, fwdata, 1024*1024);
+			if (len == 1024*1024)
+				write (serialfd, fwdata, 1024*1024);
+			else {
+				write (serialfd, fwdata, len);
+				break;
+			}
+			write (serialfd, fwdata, 0);
+		}
+	}
+
+	*(short *)&magic8[sizeof(magic8)-2] =
+		~crc_ccitt(0xffff, magic8, sizeof(magic8)-2);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic8, sizeof(magic8));
+	write (serialfd, "\x7e", 1);
+
+	return 0;
+}
diff --git a/updates/wwan-drivers/qcserial.c b/updates/wwan-drivers/qcserial.c
new file mode 100644
index 0000000..04bb759
--- /dev/null
+++ b/updates/wwan-drivers/qcserial.c
@@ -0,0 +1,227 @@ 
+/*
+ * Qualcomm Serial USB driver
+ *
+ *	Copyright (c) 2008 QUALCOMM Incorporated.
+ *	Copyright (c) 2009 Greg Kroah-Hartman <gregkh@suse.de>
+ *	Copyright (c) 2009 Novell Inc.
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License version
+ *	2 as published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/slab.h>
+#include "usb-wwan.h"
+
+#define DRIVER_AUTHOR "Qualcomm Inc"
+#define DRIVER_DESC "Qualcomm USB Serial driver"
+
+static int debug;
+
+static const struct usb_device_id id_table[] = {
+	{USB_DEVICE(0x05c6, 0x9211)},	/* Acer Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9212)},	/* Acer Gobi Modem Device */
+	{USB_DEVICE(0x03f0, 0x1f1d)},	/* HP un2400 Gobi Modem Device */
+	{USB_DEVICE(0x03f0, 0x201d)},	/* HP un2400 Gobi QDL Device */
+	{USB_DEVICE(0x04da, 0x250d)},	/* Panasonic Gobi Modem device */
+	{USB_DEVICE(0x04da, 0x250c)},	/* Panasonic Gobi QDL device */
+	{USB_DEVICE(0x413c, 0x8172)},	/* Dell Gobi Modem device */
+	{USB_DEVICE(0x413c, 0x8171)},	/* Dell Gobi QDL device */
+	{USB_DEVICE(0x1410, 0xa001)},	/* Novatel Gobi Modem device */
+	{USB_DEVICE(0x1410, 0xa008)},	/* Novatel Gobi QDL device */
+	{USB_DEVICE(0x0b05, 0x1776)},	/* Asus Gobi Modem device */
+	{USB_DEVICE(0x0b05, 0x1774)},	/* Asus Gobi QDL device */
+	{USB_DEVICE(0x19d2, 0xfff3)},	/* ONDA Gobi Modem device */
+	{USB_DEVICE(0x19d2, 0xfff2)},	/* ONDA Gobi QDL device */
+	{USB_DEVICE(0x1557, 0x0a80)},	/* OQO Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9001)},   /* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9002)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9202)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9203)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9222)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9008)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9201)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9221)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9231)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x1f45, 0x0001)},	/* Unknown Gobi QDL device */
+	{USB_DEVICE(0x413c, 0x8185)},	/* Dell Gobi 2000 QDL device (N0218, VU936) */
+	{USB_DEVICE(0x413c, 0x8186)},	/* Dell Gobi 2000 Modem device (N0218, VU936) */
+	{USB_DEVICE(0x05c6, 0x9224)},	/* Sony Gobi 2000 QDL device (N0279, VU730) */
+	{USB_DEVICE(0x05c6, 0x9225)},	/* Sony Gobi 2000 Modem device (N0279, VU730) */
+	{USB_DEVICE(0x05c6, 0x9244)},	/* Samsung Gobi 2000 QDL device (VL176) */
+	{USB_DEVICE(0x05c6, 0x9245)},	/* Samsung Gobi 2000 Modem device (VL176) */
+	{USB_DEVICE(0x03f0, 0x241d)},	/* HP Gobi 2000 QDL device (VP412) */
+	{USB_DEVICE(0x03f0, 0x251d)},	/* HP Gobi 2000 Modem device (VP412) */
+	{USB_DEVICE(0x05c6, 0x9214)},	/* Acer Gobi 2000 QDL device (VP413) */
+	{USB_DEVICE(0x05c6, 0x9215)},	/* Acer Gobi 2000 Modem device (VP413) */
+	{USB_DEVICE(0x05c6, 0x9264)},	/* Asus Gobi 2000 QDL device (VR305) */
+	{USB_DEVICE(0x05c6, 0x9265)},	/* Asus Gobi 2000 Modem device (VR305) */
+	{USB_DEVICE(0x05c6, 0x9234)},	/* Top Global Gobi 2000 QDL device (VR306) */
+	{USB_DEVICE(0x05c6, 0x9235)},	/* Top Global Gobi 2000 Modem device (VR306) */
+	{USB_DEVICE(0x05c6, 0x9274)},	/* iRex Technologies Gobi 2000 QDL device (VR307) */
+	{USB_DEVICE(0x05c6, 0x9275)},	/* iRex Technologies Gobi 2000 Modem device (VR307) */
+	{USB_DEVICE(0x1199, 0x9000)},	/* Sierra Wireless Gobi 2000 QDL device (VT773) */
+	{USB_DEVICE(0x1199, 0x9001)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9002)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9003)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9004)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9005)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9006)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9007)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9008)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9009)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x900a)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x16d8, 0x8001)},	/* CMDTech Gobi 2000 QDL device (VU922) */
+	{USB_DEVICE(0x16d8, 0x8002)},	/* CMDTech Gobi 2000 Modem device (VU922) */
+	{USB_DEVICE(0x05c6, 0x9204)},	/* Gobi 2000 QDL device */
+	{USB_DEVICE(0x05c6, 0x9205)},	/* Gobi 2000 Modem device */
+	{ }				/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver qcdriver = {
+	.name			= "qcserial",
+	.probe			= usb_serial_probe,
+	.disconnect		= usb_serial_disconnect,
+	.id_table		= id_table,
+	.suspend		= usb_serial_suspend,
+	.resume			= usb_serial_resume,
+	.supports_autosuspend	= true,
+};
+
+static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_host_interface *intf = serial->interface->cur_altsetting;
+	int retval = -ENODEV;
+	__u8 nintf;
+	__u8 ifnum;
+
+	dbg("%s", __func__);
+
+	nintf = serial->dev->actconfig->desc.bNumInterfaces;
+	dbg("Num Interfaces = %d", nintf);
+	ifnum = intf->desc.bInterfaceNumber;
+	dbg("This Interface = %d", ifnum);
+
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private),
+					 GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->susp_lock);
+
+	switch (nintf) {
+	case 1:
+		/* QDL mode */
+		/* Gobi 2000 has a single altsetting, older ones have two */
+		if (serial->interface->num_altsetting == 2)
+			intf = &serial->interface->altsetting[1];
+		else if (serial->interface->num_altsetting > 2)
+			break;
+
+		if (intf->desc.bNumEndpoints == 2 &&
+		    usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
+		    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
+			dbg("QDL port found");
+
+			if (serial->interface->num_altsetting == 1)
+				return 0;
+
+			retval = usb_set_interface(serial->dev, ifnum, 1);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
+			}
+			return retval;
+		}
+		break;
+
+	case 3:
+	case 4:
+		/* Composite mode */
+		if (ifnum == 2) {
+			dbg("Modem port found");
+			retval = usb_set_interface(serial->dev, ifnum, 0);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
+			}
+			return retval;
+		}
+		break;
+
+	default:
+		dev_err(&serial->dev->dev,
+			"unknown number of interfaces: %d\n", nintf);
+		return -ENODEV;
+	}
+
+	return retval;
+}
+
+static struct usb_serial_driver qcdevice = {
+	.driver = {
+		.owner     = THIS_MODULE,
+		.name      = "qcserial",
+	},
+	.description         = "Qualcomm USB modem",
+	.id_table            = id_table,
+	.usb_driver          = &qcdriver,
+	.num_ports           = 1,
+	.probe               = qcprobe,
+	.open		     = usb_wwan_open,
+	.close		     = usb_wwan_close,
+	.write		     = usb_wwan_write,
+	.write_room	     = usb_wwan_write_room,
+	.chars_in_buffer     = usb_wwan_chars_in_buffer,
+	.attach		     = usb_wwan_startup,
+	.disconnect	     = usb_wwan_disconnect,
+	.release	     = usb_wwan_release,
+#ifdef CONFIG_PM
+	.suspend	     = usb_wwan_suspend,
+	.resume		     = usb_wwan_resume,
+#endif
+};
+
+static int __init qcinit(void)
+{
+	int retval;
+
+	retval = usb_serial_register(&qcdevice);
+	if (retval)
+		return retval;
+
+	retval = usb_register(&qcdriver);
+	if (retval) {
+		usb_serial_deregister(&qcdevice);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __exit qcexit(void)
+{
+	usb_deregister(&qcdriver);
+	usb_serial_deregister(&qcdevice);
+}
+
+module_init(qcinit);
+module_exit(qcexit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
diff --git a/updates/wwan-drivers/usb-wwan.h b/updates/wwan-drivers/usb-wwan.h
new file mode 100644
index 0000000..2be298a
--- /dev/null
+++ b/updates/wwan-drivers/usb-wwan.h
@@ -0,0 +1,67 @@ 
+/*
+ * Definitions for USB serial mobile broadband cards
+ */
+
+#ifndef __LINUX_USB_USB_WWAN
+#define __LINUX_USB_USB_WWAN
+
+extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on);
+extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port);
+extern void usb_wwan_close(struct usb_serial_port *port);
+extern int usb_wwan_startup(struct usb_serial *serial);
+extern void usb_wwan_disconnect(struct usb_serial *serial);
+extern void usb_wwan_release(struct usb_serial *serial);
+extern int usb_wwan_write_room(struct tty_struct *tty);
+extern void usb_wwan_set_termios(struct tty_struct *tty,
+				 struct usb_serial_port *port,
+				 struct ktermios *old);
+extern int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file);
+extern int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+			     unsigned int set, unsigned int clear);
+extern int usb_wwan_send_setup(struct usb_serial_port *port);
+extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+			  const unsigned char *buf, int count);
+extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+#ifdef CONFIG_PM
+extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
+extern int usb_wwan_resume(struct usb_serial *serial);
+#endif
+
+/* per port private data */
+
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+#define OUT_BUFLEN 4096
+
+struct usb_wwan_intf_private {
+	spinlock_t susp_lock;
+	unsigned int suspended:1;
+	int in_flight;
+	int (*send_setup) (struct usb_serial_port *port);
+	void *private;
+};
+
+struct usb_wwan_port_private {
+	/* Input endpoints and buffer for this port */
+	struct urb *in_urbs[N_IN_URB];
+	u8 *in_buffer[N_IN_URB];
+	/* Output endpoints and buffer for this port */
+	struct urb *out_urbs[N_OUT_URB];
+	u8 *out_buffer[N_OUT_URB];
+	unsigned long out_busy;	/* Bit vector of URBs in use */
+	int opened;
+	struct usb_anchor delayed;
+
+	/* Settings for the port */
+	int rts_state;		/* Handshaking pins (outputs) */
+	int dtr_state;
+	int cts_state;		/* Handshaking pins (inputs) */
+	int dsr_state;
+	int dcd_state;
+	int ri_state;
+
+	unsigned long tx_start_time[N_OUT_URB];
+};
+
+#endif /* __LINUX_USB_USB_WWAN */
diff --git a/updates/wwan-drivers/usb_wwan.c b/updates/wwan-drivers/usb_wwan.c
new file mode 100644
index 0000000..0c70b4a
--- /dev/null
+++ b/updates/wwan-drivers/usb_wwan.c
@@ -0,0 +1,665 @@ 
+/*
+  USB Driver layer for GSM modems
+
+  Copyright (C) 2005  Matthias Urlichs <smurf@smurf.noris.de>
+
+  This driver is free software; you can redistribute it and/or modify
+  it under the terms of Version 2 of the GNU General Public License as
+  published by the Free Software Foundation.
+
+  Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+  History: see the git log.
+
+  Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+  This driver exists because the "normal" serial driver doesn't work too well
+  with GSM modems. Issues:
+  - data loss -- one single Receive URB is not nearly enough
+  - controlling the baud rate doesn't make sense
+*/
+
+#define DRIVER_VERSION "v0.7.2"
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+static int debug;
+
+void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+{
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+
+	struct usb_wwan_intf_private *intfdata;
+
+	dbg("%s", __func__);
+
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return;
+
+	portdata = usb_get_serial_port_data(port);
+	mutex_lock(&serial->disc_mutex);
+	portdata->rts_state = on;
+	portdata->dtr_state = on;
+	if (serial->dev)
+		intfdata->send_setup(port);
+	mutex_unlock(&serial->disc_mutex);
+}
+EXPORT_SYMBOL(usb_wwan_dtr_rts);
+
+void usb_wwan_set_termios(struct tty_struct *tty,
+			  struct usb_serial_port *port,
+			  struct ktermios *old_termios)
+{
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+
+	/* Doesn't support option setting */
+	tty_termios_copy_hw(tty->termios, old_termios);
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_set_termios);
+
+int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	unsigned int value;
+	struct usb_wwan_port_private *portdata;
+
+	portdata = usb_get_serial_port_data(port);
+
+	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+	    ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+	    ((portdata->cts_state) ? TIOCM_CTS : 0) |
+	    ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+	    ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+	    ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+	return value;
+}
+EXPORT_SYMBOL(usb_wwan_tiocmget);
+
+int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+		      unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return -EINVAL;
+
+	/* FIXME: what locks portdata fields ? */
+	if (set & TIOCM_RTS)
+		portdata->rts_state = 1;
+	if (set & TIOCM_DTR)
+		portdata->dtr_state = 1;
+
+	if (clear & TIOCM_RTS)
+		portdata->rts_state = 0;
+	if (clear & TIOCM_DTR)
+		portdata->dtr_state = 0;
+	return intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_tiocmset);
+
+/* Write */
+int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+		   const unsigned char *buf, int count)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+	int left, todo;
+	struct urb *this_urb = NULL;	/* spurious */
+	int err;
+	unsigned long flags;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	dbg("%s: write (%d chars)", __func__, count);
+
+	i = 0;
+	left = count;
+	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
+		todo = left;
+		if (todo > OUT_BUFLEN)
+			todo = OUT_BUFLEN;
+
+		this_urb = portdata->out_urbs[i];
+		if (test_and_set_bit(i, &portdata->out_busy)) {
+			if (time_before(jiffies,
+					portdata->tx_start_time[i] + 10 * HZ))
+				continue;
+			usb_unlink_urb(this_urb);
+			continue;
+		}
+		dbg("%s: endpoint %d buf %d", __func__,
+		    usb_pipeendpoint(this_urb->pipe), i);
+
+		err = usb_autopm_get_interface_async(port->serial->interface);
+		if (err < 0)
+			break;
+
+		/* send the data */
+		memcpy(this_urb->transfer_buffer, buf, todo);
+		this_urb->transfer_buffer_length = todo;
+
+		spin_lock_irqsave(&intfdata->susp_lock, flags);
+		if (intfdata->suspended) {
+			usb_anchor_urb(this_urb, &portdata->delayed);
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+		} else {
+			intfdata->in_flight++;
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+			err = usb_submit_urb(this_urb, GFP_ATOMIC);
+			if (err) {
+				dbg("usb_submit_urb %p (write bulk) failed "
+				    "(%d)", this_urb, err);
+				clear_bit(i, &portdata->out_busy);
+				spin_lock_irqsave(&intfdata->susp_lock, flags);
+				intfdata->in_flight--;
+				spin_unlock_irqrestore(&intfdata->susp_lock,
+						       flags);
+				continue;
+			}
+		}
+
+		portdata->tx_start_time[i] = jiffies;
+		buf += todo;
+		left -= todo;
+	}
+
+	count -= left;
+	dbg("%s: wrote (did %d)", __func__, count);
+	return count;
+}
+EXPORT_SYMBOL(usb_wwan_write);
+
+static void usb_wwan_indat_callback(struct urb *urb)
+{
+	int err;
+	int endpoint;
+	struct usb_serial_port *port;
+	struct tty_struct *tty;
+	unsigned char *data = urb->transfer_buffer;
+	int status = urb->status;
+
+	dbg("%s: %p", __func__, urb);
+
+	endpoint = usb_pipeendpoint(urb->pipe);
+	port = urb->context;
+
+	if (status) {
+		dbg("%s: nonzero status: %d on endpoint %02x.",
+		    __func__, status, endpoint);
+	} else {
+		tty = tty_port_tty_get(&port->port);
+		if (urb->actual_length) {
+			tty_insert_flip_string(tty, data, urb->actual_length);
+			tty_flip_buffer_push(tty);
+		} else
+			dbg("%s: empty read urb received", __func__);
+		tty_kref_put(tty);
+
+		/* Resubmit urb so we continue receiving */
+		if (status != -ESHUTDOWN) {
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err && err != -EPERM)
+				printk(KERN_ERR "%s: resubmit read urb failed. "
+				       "(%d)", __func__, err);
+			else
+				usb_mark_last_busy(port->serial->dev);
+		}
+
+	}
+	return;
+}
+
+static void usb_wwan_outdat_callback(struct urb *urb)
+{
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+
+	dbg("%s", __func__);
+
+	port = urb->context;
+	intfdata = port->serial->private;
+
+	usb_serial_port_softint(port);
+	usb_autopm_put_interface_async(port->serial->interface);
+	portdata = usb_get_serial_port_data(port);
+	spin_lock(&intfdata->susp_lock);
+	intfdata->in_flight--;
+	spin_unlock(&intfdata->susp_lock);
+
+	for (i = 0; i < N_OUT_URB; ++i) {
+		if (portdata->out_urbs[i] == urb) {
+			smp_mb__before_clear_bit();
+			clear_bit(i, &portdata->out_busy);
+			break;
+		}
+	}
+}
+
+int usb_wwan_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		if (this_urb && !test_bit(i, &portdata->out_busy))
+			data_len += OUT_BUFLEN;
+	}
+
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_write_room);
+
+int usb_wwan_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		/* FIXME: This locking is insufficient as this_urb may
+		   go unused during the test */
+		if (this_urb && test_bit(i, &portdata->out_busy))
+			data_len += this_urb->transfer_buffer_length;
+	}
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+
+int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	struct usb_serial *serial = port->serial;
+	int i, err;
+	struct urb *urb;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = serial->private;
+
+	dbg("%s", __func__);
+
+	/* Start reading from the IN endpoint */
+	for (i = 0; i < N_IN_URB; i++) {
+		urb = portdata->in_urbs[i];
+		if (!urb)
+			continue;
+		err = usb_submit_urb(urb, GFP_KERNEL);
+		if (err) {
+			dbg("%s: submit urb %d failed (%d) %d",
+			    __func__, i, err, urb->transfer_buffer_length);
+		}
+	}
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+
+	serial->interface->needs_remote_wakeup = 1;
+	spin_lock_irq(&intfdata->susp_lock);
+	portdata->opened = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	usb_autopm_put_interface(serial->interface);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_open);
+
+void usb_wwan_close(struct usb_serial_port *port)
+{
+	int i;
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+	portdata = usb_get_serial_port_data(port);
+
+	if (serial->dev) {
+		/* Stop reading/writing urbs */
+		spin_lock_irq(&intfdata->susp_lock);
+		portdata->opened = 0;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		for (i = 0; i < N_IN_URB; i++)
+			usb_kill_urb(portdata->in_urbs[i]);
+		for (i = 0; i < N_OUT_URB; i++)
+			usb_kill_urb(portdata->out_urbs[i]);
+		usb_autopm_get_interface(serial->interface);
+		serial->interface->needs_remote_wakeup = 0;
+	}
+}
+EXPORT_SYMBOL(usb_wwan_close);
+
+/* Helper functions used by usb_wwan_setup_urbs */
+static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint,
+				      int dir, void *ctx, char *buf, int len,
+				      void (*callback) (struct urb *))
+{
+	struct urb *urb;
+
+	if (endpoint == -1)
+		return NULL;	/* endpoint not needed */
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */
+	if (urb == NULL) {
+		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
+		return NULL;
+	}
+
+	/* Fill URB using supplied data. */
+	usb_fill_bulk_urb(urb, serial->dev,
+			  usb_sndbulkpipe(serial->dev, endpoint) | dir,
+			  buf, len, callback, ctx);
+
+	return urb;
+}
+
+/* Setup urbs */
+static void usb_wwan_setup_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* Do indat endpoints first */
+		for (j = 0; j < N_IN_URB; ++j) {
+			portdata->in_urbs[j] = usb_wwan_setup_urb(serial,
+								  port->
+								  bulk_in_endpointAddress,
+								  USB_DIR_IN,
+								  port,
+								  portdata->
+								  in_buffer[j],
+								  IN_BUFLEN,
+								  usb_wwan_indat_callback);
+		}
+
+		/* outdat endpoints */
+		for (j = 0; j < N_OUT_URB; ++j) {
+			portdata->out_urbs[j] = usb_wwan_setup_urb(serial,
+								   port->
+								   bulk_out_endpointAddress,
+								   USB_DIR_OUT,
+								   port,
+								   portdata->
+								   out_buffer
+								   [j],
+								   OUT_BUFLEN,
+								   usb_wwan_outdat_callback);
+		}
+	}
+}
+
+int usb_wwan_startup(struct usb_serial *serial)
+{
+	int i, j, err;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	u8 *buffer;
+
+	dbg("%s", __func__);
+
+	/* Now setup per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
+		if (!portdata) {
+			dbg("%s: kmalloc for usb_wwan_port_private (%d) failed!.",
+			    __func__, i);
+			return 1;
+		}
+		init_usb_anchor(&portdata->delayed);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			buffer = (u8 *) __get_free_page(GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error;
+			portdata->in_buffer[j] = buffer;
+		}
+
+		for (j = 0; j < N_OUT_URB; j++) {
+			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error2;
+			portdata->out_buffer[j] = buffer;
+		}
+
+		usb_set_serial_port_data(port, portdata);
+
+		if (!port->interrupt_in_urb)
+			continue;
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+		if (err)
+			dbg("%s: submit irq_in urb failed %d", __func__, err);
+	}
+	usb_wwan_setup_urbs(serial);
+	return 0;
+
+bail_out_error2:
+	for (j = 0; j < N_OUT_URB; j++)
+		kfree(portdata->out_buffer[j]);
+bail_out_error:
+	for (j = 0; j < N_IN_URB; j++)
+		if (portdata->in_buffer[j])
+			free_page((unsigned long)portdata->in_buffer[j]);
+	kfree(portdata);
+	return 1;
+}
+EXPORT_SYMBOL(usb_wwan_startup);
+
+static void stop_read_write_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	/* Stop reading/writing urbs */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+		for (j = 0; j < N_IN_URB; j++)
+			usb_kill_urb(portdata->in_urbs[j]);
+		for (j = 0; j < N_OUT_URB; j++)
+			usb_kill_urb(portdata->out_urbs[j]);
+	}
+}
+
+void usb_wwan_disconnect(struct usb_serial *serial)
+{
+	dbg("%s", __func__);
+
+	stop_read_write_urbs(serial);
+}
+EXPORT_SYMBOL(usb_wwan_disconnect);
+
+void usb_wwan_release(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	/* Now free them */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			usb_free_urb(portdata->in_urbs[j]);
+			free_page((unsigned long)
+				  portdata->in_buffer[j]);
+			portdata->in_urbs[j] = NULL;
+		}
+		for (j = 0; j < N_OUT_URB; j++) {
+			usb_free_urb(portdata->out_urbs[j]);
+			kfree(portdata->out_buffer[j]);
+			portdata->out_urbs[j] = NULL;
+		}
+	}
+
+	/* Now free per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		kfree(usb_get_serial_port_data(port));
+	}
+}
+EXPORT_SYMBOL(usb_wwan_release);
+
+#ifdef CONFIG_PM
+int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message)
+{
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	int b;
+
+	dbg("%s entered", __func__);
+
+	if (message.event & PM_EVENT_AUTO) {
+		spin_lock_irq(&intfdata->susp_lock);
+		b = intfdata->in_flight;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	stop_read_write_urbs(serial);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_suspend);
+
+static void play_delayed(struct usb_serial_port *port)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err;
+
+	portdata = usb_get_serial_port_data(port);
+	data = port->serial->private;
+	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (!err)
+			data->in_flight++;
+	}
+}
+
+int usb_wwan_resume(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err = 0;
+
+	dbg("%s entered", __func__);
+	/* get the interrupt URBs resubmitted unconditionally */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		if (!port->interrupt_in_urb) {
+			dbg("%s: No interrupt URB for port %d", __func__, i);
+			continue;
+		}
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
+		if (err < 0) {
+			err("%s: Error %d for interrupt URB of port%d",
+			    __func__, err, i);
+			goto err_out;
+		}
+	}
+
+	for (i = 0; i < serial->num_ports; i++) {
+		/* walk all ports */
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* skip closed ports */
+		spin_lock_irq(&intfdata->susp_lock);
+		if (!portdata->opened) {
+			spin_unlock_irq(&intfdata->susp_lock);
+			continue;
+		}
+
+		for (j = 0; j < N_IN_URB; j++) {
+			urb = portdata->in_urbs[j];
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err < 0) {
+				err("%s: Error %d for bulk URB %d",
+				    __func__, err, i);
+				spin_unlock_irq(&intfdata->susp_lock);
+				goto err_out;
+			}
+		}
+		play_delayed(port);
+		spin_unlock_irq(&intfdata->susp_lock);
+	}
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 0;
+	spin_unlock_irq(&intfdata->susp_lock);
+err_out:
+	return err;
+}
+EXPORT_SYMBOL(usb_wwan_resume);
+#endif
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages");