From patchwork Sat Jul 31 10:37:44 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Bader X-Patchwork-Id: 60396 X-Patchwork-Delegate: stefan.bader@canonical.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from chlorine.canonical.com (chlorine.canonical.com [91.189.94.204]) by ozlabs.org (Postfix) with ESMTP id 8E0FFB6EE8 for ; Sat, 31 Jul 2010 20:38:07 +1000 (EST) Received: from localhost ([127.0.0.1] helo=chlorine.canonical.com) by chlorine.canonical.com with esmtp (Exim 4.69) (envelope-from ) id 1Of9Rw-0007fK-Ru; Sat, 31 Jul 2010 11:37:52 +0100 Received: from adelie.canonical.com ([91.189.90.139]) by chlorine.canonical.com with esmtp (Exim 4.69) (envelope-from ) id 1Of9Ru-0007fE-Pd for kernel-team@lists.ubuntu.com; Sat, 31 Jul 2010 11:37:50 +0100 Received: from hutte.canonical.com ([91.189.90.181]) by adelie.canonical.com with esmtp (Exim 4.69 #1 (Debian)) id 1Of9Ru-0003wH-NZ; Sat, 31 Jul 2010 11:37:50 +0100 Received: from p5b2e6dc8.dip.t-dialin.net ([91.46.109.200] helo=canonical.com) by hutte.canonical.com with esmtpsa (TLS-1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.69) (envelope-from ) id 1Of9Rq-0003Wn-1Y; Sat, 31 Jul 2010 11:37:50 +0100 From: Stefan Bader To: kernel-team@lists.ubuntu.com, leann.ogasawara@canonical.com, apw@canonical.com, tim.gardner@canonical.com Subject: [Lucid-LBM] SRU: Add backported qcserial driver [Maverick-RFC] Date: Sat, 31 Jul 2010 12:37:44 +0200 Message-Id: <1280572664-12156-1-git-send-email-stefan.bader@canonical.com> X-Mailer: git-send-email 1.7.0.4 To: kernel-team@lists.ubuntu.com X-BeenThere: kernel-team@lists.ubuntu.com X-Mailman-Version: 2.1.9 Precedence: list List-Id: Kernel team discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: kernel-team-bounces@lists.ubuntu.com Errors-To: kernel-team-bounces@lists.ubuntu.com 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 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 Acked-by: Tim Gardner for Lucid Acked-by: Brad Figg --- 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 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 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 - heavily based on work done by + * Alexander Shumakovitch + * + * Gobi 2000 support provided by Anssi Hannula + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * 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 +#include +#include +#include +#include +#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 + + 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 + + History: see the git log. + + Work sponsored by: Sigos GmbH, Germany + + 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 " +#define DRIVER_DESC "USB Driver for GSM modems" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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");