@@ -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
@@ -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
@@ -12,3 +12,4 @@ KBUILD_EXTMOD := $(src)/alsa-driver
obj-y += wireless-staging/
obj-y += thinkpad-acpi/
+obj-y += wwan-drivers/
new file mode 100644
@@ -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.
+
new file mode 100644
@@ -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
new file mode 100644
@@ -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"
new file mode 100644
@@ -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)
new file mode 100644
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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");
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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");