From 5ded927eaaa7ac0651cfabff5b9ce13ef6235b66 Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Wed, 22 May 2024 16:41:32 +0800
Subject: [PATCH] Add kernel build shell script and patch

---
 kernel/patches/loragw/linux-at91-linux4sam-6.1.patch | 9156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 kernel/patches/gen_patch.sh                          |  149 
 kernel/build.sh                                      |  210 +
 3 files changed, 9,515 insertions(+), 0 deletions(-)

diff --git a/kernel/build.sh b/kernel/build.sh
new file mode 100755
index 0000000..78b6c67
--- /dev/null
+++ b/kernel/build.sh
@@ -0,0 +1,210 @@
+#!/bin/bash
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries build prefix install path
+PRFX_PATH=$PRJ_PATH/install
+
+# binaries finally install path if needed
+#INST_PATH=/tftp
+
+# download taballs path
+TARBALL_PATH=$PRJ_PATH/tarballs
+
+# config file path
+CONF_FILE=$TOP_PATH/config.json
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions  |
+#+-------------------------+
+
+function pr_error() {
+	echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+	echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+	echo -e "\033[40;32m $1 \033[0m"
+}
+
+# decompress a packet to destination path
+function do_unpack()
+{
+	tarball=$1
+	dstpath=`pwd`
+
+	if [[ $# == 2 ]] ; then
+		dstpath=$2
+	fi
+
+	pr_info "decompress $tarball => $dstpath"
+
+	mkdir -p $dstpath
+	case $tarball in
+		*.tar.gz)
+			tar -xzf $tarball -C $dstpath
+			;;
+
+		*.tar.bz2)
+			tar -xjf $tarball -C $dstpath
+			;;
+
+		*.tar.xz)
+			tar -xJf $tarball -C $dstpath
+			;;
+
+		*.tar.zst)
+			tar -I zstd -xf $tarball -C $dstpath
+			;;
+
+		*.tar)
+			tar -xf $tarball -C $dstpath
+			;;
+
+		*.zip)
+			unzip -qo $tarball -d $dstpath
+			;;
+
+		*)
+			pr_error "decompress Unsupport packet: $tarball"
+			return 1;
+			;;
+	esac
+}
+
+# parser configure file and export environment variable
+function export_env()
+{
+	export BOARD=`jq -r ".bsp.board" $CONF_FILE | tr 'A-Z' 'a-z'`
+	export BSP_VER=`jq -r ".bsp.version" $CONF_FILE | tr 'A-Z' 'a-z'`
+	export BSP_URL=`jq -r ".bsp.giturl" $CONF_FILE`
+	export CROSS_COMPILE=`jq -r ".bsp.crosstool" $CONF_FILE`
+
+	export DEF_CONFIG=${BOARD}_defconfig
+	export IMG_KER=linuxrom-${BOARD}.itb
+
+	export JOBS=`cat /proc/cpuinfo | grep processor | wc -l`
+	export ARCH=arm
+
+	export SRCS="linux-at91"
+}
+
+function do_fetch()
+{
+	cd $PRJ_PATH
+
+	for src in $SRCS
+	do
+		if [ -d $src ] ; then
+			pr_info "$src source code fetched already"
+			continue
+		fi
+
+		pr_info "start fetch $src source code"
+		mkdir -p $TARBALL_PATH
+
+		# Download source code packet
+		if [ ! -s $TARBALL_PATH/$src.tar.xz ] ; then
+			wget $BSP_URL/at91/bsp/$BSP_VER/$src.tar.xz -P $TARBALL_PATH
+		fi
+
+		# decompress source code packet
+		do_unpack $TARBALL_PATH/$src.tar.xz
+
+		# do patch if patch file exist
+		patch_file=$PRJ_PATH/patches/$BOARD/$src-$BSP_VER.patch
+		if [ -s $patch_file ] ; then
+			pr_warn "do patch for $src now..."
+			cd $src
+			patch -p1 < $patch_file
+			cd -
+		fi
+	done
+}
+
+function build_kernel()
+{
+	SRC=linux-at91
+
+	pr_warn "start build $SRC"
+	cd $PRJ_PATH/${SRC}
+
+	if [ ! -f .config ] ; then
+		make $DEF_CONFIG ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE}
+	fi
+
+	make -j${JOBS} ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE}
+
+	pr_info "mkimage -f linuxrom-${BOARD}.its ${IMG_KER}"
+	mkimage -f linuxrom-${BOARD}.its ${IMG_KER} > /dev/null
+	chmod a+x ${IMG_KER}
+
+	set -x
+	cp ${IMG_KER} $PRFX_PATH/
+	set +x
+}
+
+function do_build()
+{
+	cd $PRJ_PATH
+	mkdir -p $PRFX_PATH
+
+	build_kernel
+}
+
+function do_install()
+{
+	cd $PRJ_PATH
+
+	echo ""
+	pr_info "linux kernel installed to '$PRFX_PATH'"
+	ls $PRFX_PATH && echo ""
+
+	if [[ -n "$INST_PATH" && -w $INST_PATH ]] ; then
+		pr_info "install linux images to '$INST_PATH'"
+		cp $PRFX_PATH/* $INST_PATH
+	fi
+}
+
+function do_clean()
+{
+	for d in $SRCS
+	do
+		rm -rf $PRJ_PATH/$d
+	done
+
+	rm -rf $PRJ_PATH/tarballs
+	rm -rf $PRFX_PATH
+}
+
+#+-------------------------+
+#| Shell script body entry |
+#+-------------------------+
+
+cd $PRJ_PATH
+
+export_env
+
+if [[ $# == 1 && $1 == -c ]] ;then
+	pr_warn "start clean linux kernel"
+	do_clean
+	exit;
+fi
+
+pr_warn "start build linux kernelel for ${BOARD}"
+
+do_fetch
+
+do_build
+
+do_install
diff --git a/kernel/patches/gen_patch.sh b/kernel/patches/gen_patch.sh
new file mode 100755
index 0000000..385439f
--- /dev/null
+++ b/kernel/patches/gen_patch.sh
@@ -0,0 +1,149 @@
+#!/bin/bash
+# Description: This shell script used to generate patch file
+#      Author: guowenxue <guowenxue@gmail.com>
+#     Version: 1.0.0  
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# config file path
+CONF_FILE=$TOP_PATH/config.json
+
+# download taballs path
+TARBALL_PATH=$PRJ_PATH/tarballs
+
+# shell script will exit once get command error
+#set -e
+
+#+-------------------------+
+#| Shell script functions  |
+#+-------------------------+
+
+function pr_error() {
+    echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+    echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+    echo -e "\033[40;32m $1 \033[0m"
+}
+
+# decompress a packet to destination path
+function do_unpack()
+{
+    tarball=$1
+    dstpath=`pwd`
+
+    if [[ $# == 2 ]] ; then
+        dstpath=$2
+    fi
+
+    pr_info "decompress $tarball => $dstpath"
+
+    mkdir -p $dstpath
+    case $tarball in
+        *.tar.gz)
+            tar -xzf $tarball -C $dstpath
+            ;;
+
+        *.tar.bz2)
+            tar -xjf $tarball -C $dstpath
+            ;;
+
+        *.tar.xz)
+            tar -xJf $tarball -C $dstpath
+            ;;
+
+        *.tar.zst)
+            tar -I zstd -xf $tarball -C $dstpath
+            ;;
+
+        *.tar)
+            tar -xf $tarball -C $dstpath
+            ;;
+
+        *.zip)
+            unzip -qo $tarball -d $dstpath
+            ;;
+
+        *)
+            pr_error "decompress Unsupport packet: $tarball"
+            return 1;
+            ;;
+    esac
+}
+
+# parser configure file and export environment variable
+function export_env()
+{
+    export SRC=$(basename $1)
+
+    export BOARD=`jq -r ".bsp.board" $CONF_FILE | tr 'A-Z' 'a-z'`
+    export BSP_VER=`jq -r ".bsp.version" $CONF_FILE | tr 'A-Z' 'a-z'`
+    export CROSS_COMPILE=`jq -r ".bsp.crosstool" $CONF_FILE`
+
+	export DEF_CONFIG=${BOARD}_defconfig
+	export IMG_KER=linuxrom-${BOARD}.itb
+
+	export ARCH=arm
+    export PATCH=$SRC-$BSP_VER.patch
+}
+
+function do_clean()
+{
+	cd $SRC
+
+	if [ -f .config ] ; then
+    	pr_info "save default configure to $DEF_CONFIG"
+		make savedefconfig ARCH=$ARCH CROSS_COMPILE=${CROSS_COMPILE}
+		mv defconfig arch/arm/configs/${DEF_CONFIG}
+	fi
+
+	rm -f $IMG_KER
+	make distclean ARCH=$ARCH CROSS_COMPILE=${CROSS_COMPILE} 
+	cd $PRJ_PATH
+}
+
+function do_patch()
+{
+    if [ ! -d $PRJ_PATH/$SRC ] ;  then
+        pr_error "\nERROR: source code $SRC not exist, exit now\n\n"
+        exit;
+    fi
+
+	# rename new source code
+	mv ${SRC} ${SRC}-${BOARD}
+
+	# decompress orignal soruce code packet
+	do_unpack ${TARBALL_PATH}/${SRC}.tar.xz
+
+    pr_info "generate patch file $PATCH"
+
+	# generate patch file
+    diff -Nuar -x "include-prefixes" -x ".gitignore" ${SRC} ${SRC}-${BOARD}  > $PATCH
+
+	# remove orignal soruce code
+	rm -rf ${SRC}
+
+	# recover new source code
+	mv ${SRC}-${BOARD} ${SRC} 
+}
+
+if [ $# != 1 ] ; then
+    pr_info "$0 usage example:"
+    pr_info "$0 at91bootstrap"
+    pr_info "$0 u-boot-at91"
+    exit
+fi
+
+export_env $1
+
+do_clean
+
+do_patch
diff --git a/kernel/patches/loragw/linux-at91-linux4sam-6.1.patch b/kernel/patches/loragw/linux-at91-linux4sam-6.1.patch
new file mode 100644
index 0000000..3bb90aa
--- /dev/null
+++ b/kernel/patches/loragw/linux-at91-linux4sam-6.1.patch
@@ -0,0 +1,9156 @@
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x35ek.dts linux-at91-loragw/arch/arm/boot/dts/at91sam9x35ek.dts
+--- linux-at91/arch/arm/boot/dts/at91sam9x35ek.dts	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x35ek.dts	2024-05-22 15:51:24.600628467 +0800
+@@ -22,24 +22,24 @@
+ 				status = "okay";
+ 			};
+ 			hlcdc: hlcdc@f8038000 {
+-				status = "okay";
++				status = "disabled";
+ 			};
+ 		};
+ 	};
+ 
+ 	backlight: backlight {
+-		status = "okay";
++		status = "disabled";
+ 	};
+ 
+ 	bl_reg: backlight_regulator {
+-		status = "okay";
++		status = "disabled";
+ 	};
+ 
+ 	panel: panel {
+-		status = "okay";
++		status = "disabled";
+ 	};
+ 
+ 	panel_reg: panel_regulator {
+-		status = "okay";
++		status = "disabled";
+ 	};
+ };
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x5cm.dtsi linux-at91-loragw/arch/arm/boot/dts/at91sam9x5cm.dtsi
+--- linux-at91/arch/arm/boot/dts/at91sam9x5cm.dtsi	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x5cm.dtsi	2024-05-22 15:51:24.600628467 +0800
+@@ -42,10 +42,17 @@
+ 						atmel,pins = <AT91_PIOB 18 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>; /* PB18 multidrive, conflicts with led */
+ 					};
+ 				};
++
++                gpio_keys_0 { /* add by guowenxue, 2019.12.10 */
++					pinctrl_key_gpio: gpio_keys_0 {
++                        atmel,pins = <AT91_PIOB 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP_DEGLITCH>; /* PB15 */
++					};
++                };
++
+ 			};
+ 
+ 			rtc@fffffeb0 {
+-				status = "okay";
++				status = "disabled";
+ 			};
+ 		};
+ 
+@@ -78,58 +85,82 @@
+ 						#address-cells = <1>;
+ 						#size-cells = <1>;
+ 
+-						at91bootstrap@0 {
+-							label = "at91bootstrap";
+-							reg = <0x0 0x40000>;
++						bootloader@0 {
++							label = "bootloader - 1MB";
++							reg = <0x0 0x100000>;
+ 						};
+ 
+-						uboot@40000 {
+-							label = "u-boot";
+-							reg = <0x40000 0xc0000>;
++						linux@100000 {
++                            label = "kernel     - 7MB";
++							reg = <0x100000 0x700000>;
+ 						};
+ 
+-						ubootenvred@100000 {
+-							label = "U-Boot Env Redundant";
+-							reg = <0x100000 0x40000>;
++						rootfs@8000000 {
++                            label = "rootfs     - 100MB";
++							reg = <0x800000 0x6400000>;
+ 						};
+ 
+-						ubootenv@140000 {
+-							label = "U-Boot Env";
+-							reg = <0x140000 0x40000>;
+-						};
+-
+-						dtb@180000 {
+-							label = "device tree";
+-							reg = <0x180000 0x80000>;
+-						};
+-
+-						kernel@200000 {
+-							label = "kernel";
+-							reg = <0x200000 0x600000>;
+-						};
++                        apps@6000000 {
++                            label = "apps       - 100MB";
++							reg = <0x6C00000 0x6400000>;
++                        };
+ 
+-						rootfs@800000 {
+-							label = "rootfs";
+-							reg = <0x800000 0x0f800000>;
+-						};
++                        data@B000000 {
++                            label = "data       - 48MB";
++                            reg = <0xD000000 0x3000000>;
++                        };
+ 					};
+ 				};
+ 			};
+ 		};
+ 	};
+ 
++    i2c-gpio-0 {  /* add ISL1208 RTC on GPIO I2C by guowenuxe, 2019.12.10 */
++        status = "okay"; 
++
++        rtc@6f {
++            compatible = "isil,isl1208";
++            reg = <0x6f>;
++            status="okay";
++        };
++    };
++
++    gpio_keys { /* add gpio keys by guowenxue, 2019.12.10 */
++        compatible = "gpio-keys";
++
++        pinctrl-names = "default";
++        pinctrl-0 = <&pinctrl_key_gpio>;
++
++        initkey {
++            label = "initkey";
++            gpios = <&pioB 15 GPIO_ACTIVE_LOW>;
++            linux,code = <0x100>;
++            wakeup-source;
++        };
++    };
++
+ 	leds {
+ 		compatible = "gpio-leds";
+ 
+-		pb18 {
+-			label = "pb18";
+-			gpios = <&pioB 18 GPIO_ACTIVE_LOW>;
++		run {
++			label = "sys";
++			gpios = <&pioB 11 GPIO_ACTIVE_LOW>;
+ 			linux,default-trigger = "heartbeat";
+ 		};
+ 
+-		pd21 {
+-			label = "pd21";
+-			gpios = <&pioD 21 GPIO_ACTIVE_HIGH>;
++		lora {
++			label = "lora";
++			gpios = <&pioB 18 GPIO_ACTIVE_LOW>;
++		};
++
++		eth {
++			label = "eth";
++			gpios = <&pioB 8 GPIO_ACTIVE_LOW>;
++		};
++
++		gprs {
++			label = "gprs";
++			gpios = <&pioB 14 GPIO_ACTIVE_LOW>;
+ 		};
+ 	};
+ 
+@@ -139,7 +170,7 @@
+ 		linux,open-drain;
+ 		pinctrl-names = "default";
+ 		pinctrl-0 = <&pinctrl_1wire_cm>;
+-		status = "okay";
++		status = "disabled";
+ 	};
+ 
+ };
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x5.dtsi linux-at91-loragw/arch/arm/boot/dts/at91sam9x5.dtsi
+--- linux-at91/arch/arm/boot/dts/at91sam9x5.dtsi	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x5.dtsi	2024-05-22 15:51:24.600628467 +0800
+@@ -713,8 +713,8 @@
+ 				i2c_gpio0 {
+ 					pinctrl_i2c_gpio0: i2c_gpio0-0 {
+ 						atmel,pins =
+-							<AT91_PIOA 30 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE	/* PA30 gpio multidrive I2C0 data */
+-							 AT91_PIOA 31 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>;	/* PA31 gpio multidrive I2C0 clock */
++							<AT91_PIOA 27 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE	/* PA27 gpio multidrive I2C0 data, guowenxue */
++							 AT91_PIOA 28 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>;/* PA28 gpio multidrive I2C0 clock,guowenxue */
+ 					};
+ 				};
+ 
+@@ -1282,8 +1282,8 @@
+ 
+ 	i2c-gpio-0 {
+ 		compatible = "i2c-gpio";
+-		gpios = <&pioA 30 GPIO_ACTIVE_HIGH /* sda */
+-			 &pioA 31 GPIO_ACTIVE_HIGH /* scl */
++		gpios = <&pioA 27 GPIO_ACTIVE_HIGH /* sda guowenxue */
++			 &pioA 28 GPIO_ACTIVE_HIGH /* scl */
+ 			>;
+ 		i2c-gpio,sda-open-drain;
+ 		i2c-gpio,scl-open-drain;
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x5ek.dtsi linux-at91-loragw/arch/arm/boot/dts/at91sam9x5ek.dtsi
+--- linux-at91/arch/arm/boot/dts/at91sam9x5ek.dtsi	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x5ek.dtsi	2024-05-22 15:51:24.600628467 +0800
+@@ -24,7 +24,7 @@
+ 					&pinctrl_board_mmc0
+ 					&pinctrl_mmc0_slot0_clk_cmd_dat0
+ 					&pinctrl_mmc0_slot0_dat1_3>;
+-				status = "okay";
++				status = "disabled";
+ 				slot@0 {
+ 					reg = <0>;
+ 					bus-width = <4>;
+@@ -37,7 +37,7 @@
+ 					&pinctrl_board_mmc1
+ 					&pinctrl_mmc1_slot0_clk_cmd_dat0
+ 					&pinctrl_mmc1_slot0_dat1_3>;
+-				status = "okay";
++				status = "disabled";
+ 				slot@0 {
+ 					reg = <0>;
+ 					bus-width = <4>;
+@@ -63,7 +63,7 @@
+ 			};
+ 
+ 			i2c0: i2c@f8010000 {
+-				status = "okay";
++				status = "disabled";
+ 
+ 				wm8731: wm8731@1a {
+ 					compatible = "wm8731";
+@@ -74,7 +74,7 @@
+ 			adc0: adc@f804c000 {
+ 				atmel,adc-ts-wires = <4>;
+ 				atmel,adc-ts-pressure-threshold = <10000>;
+-				status = "okay";
++				status = "disabled";
+ 			};
+ 
+ 			pinctrl@fffff400 {
+@@ -127,12 +127,23 @@
+ 				};
+ 			};
+ 
++
++			spi1: spi@f0004000 { /* add by guowenxue for LoRaWAN module(SX1301), 2019.12.11 */
++				status = "okay"; 
++				cs-gpios = <&pioA 30 0>, <0>, <0>, <0>; 
++				spidev@0 {
++                    compatible = "semtech,sx1301";
++					spi-max-frequency = <8000000>;
++					reg = <0>;
++				};
++			};
++
+ 			watchdog@fffffe40 {
+ 				status = "okay";
+ 			};
+ 
+ 			ssc0: ssc@f0010000 {
+-				status = "okay";
++				status = "disabled";
+ 			};
+ 		};
+ 
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/configs/loragw_defconfig linux-at91-loragw/arch/arm/configs/loragw_defconfig
+--- linux-at91/arch/arm/configs/loragw_defconfig	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/arch/arm/configs/loragw_defconfig	2024-05-22 16:40:37.467522135 +0800
+@@ -0,0 +1,439 @@
++# CONFIG_SWAP is not set
++CONFIG_SYSVIPC=y
++CONFIG_LOG_BUF_SHIFT=16
++CONFIG_CGROUPS=y
++CONFIG_CGROUP_CPUACCT=y
++CONFIG_EMBEDDED=y
++CONFIG_SLAB=y
++CONFIG_ARCH_MULTI_V4T=y
++CONFIG_ARCH_MULTI_V5=y
++# CONFIG_ARCH_MULTI_V7 is not set
++CONFIG_ARCH_AT91=y
++CONFIG_SOC_AT91SAM9=y
++CONFIG_AEABI=y
++CONFIG_UACCESS_WITH_MEMCPY=y
++# CONFIG_ATAGS is not set
++CONFIG_ZBOOT_ROM_TEXT=0x0
++CONFIG_ZBOOT_ROM_BSS=0x0
++CONFIG_CMDLINE="console=ttyS0,115200 initrd=0x21100000,25165824 root=/dev/ram0 rw"
++CONFIG_KEXEC=y
++CONFIG_CPU_IDLE=y
++# CONFIG_ARM_AT91_CPUIDLE is not set
++CONFIG_PM_DEBUG=y
++CONFIG_PM_ADVANCED_DEBUG=y
++CONFIG_MODULES=y
++CONFIG_MODULE_UNLOAD=y
++# CONFIG_BLK_DEV_BSG is not set
++# CONFIG_IOSCHED_DEADLINE is not set
++# CONFIG_IOSCHED_CFQ is not set
++# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
++CONFIG_NET=y
++CONFIG_PACKET=y
++CONFIG_UNIX=y
++CONFIG_INET=y
++CONFIG_IP_MULTICAST=y
++CONFIG_IP_ADVANCED_ROUTER=y
++CONFIG_IP_MULTIPLE_TABLES=y
++CONFIG_IP_MROUTE=y
++# CONFIG_INET_XFRM_MODE_TRANSPORT is not set
++# CONFIG_INET_XFRM_MODE_TUNNEL is not set
++# CONFIG_INET_XFRM_MODE_BEET is not set
++# CONFIG_INET_DIAG is not set
++# CONFIG_INET6_XFRM_MODE_TRANSPORT is not set
++# CONFIG_INET6_XFRM_MODE_TUNNEL is not set
++# CONFIG_INET6_XFRM_MODE_BEET is not set
++CONFIG_IPV6_SIT_6RD=y
++CONFIG_NETFILTER=y
++CONFIG_BRIDGE_NETFILTER=y
++CONFIG_NF_CONNTRACK=y
++CONFIG_NF_CONNTRACK_EVENTS=y
++CONFIG_NF_CONNTRACK_TIMEOUT=y
++CONFIG_NF_CONNTRACK_TIMESTAMP=y
++CONFIG_NF_CONNTRACK_LABELS=y
++CONFIG_NF_CT_NETLINK=y
++CONFIG_NF_TABLES=y
++CONFIG_NF_TABLES_SET=y
++CONFIG_NF_TABLES_INET=y
++CONFIG_NFT_NUMGEN=y
++CONFIG_NFT_CT=y
++CONFIG_NFT_COUNTER=y
++CONFIG_NFT_LOG=y
++CONFIG_NFT_LIMIT=y
++CONFIG_NFT_MASQ=y
++CONFIG_NFT_REDIR=y
++CONFIG_NFT_NAT=y
++CONFIG_NFT_TUNNEL=y
++CONFIG_NFT_OBJREF=y
++CONFIG_NFT_QUEUE=y
++CONFIG_NFT_QUOTA=y
++CONFIG_NFT_REJECT=y
++CONFIG_NFT_COMPAT=y
++CONFIG_NFT_HASH=y
++CONFIG_NFT_SOCKET=y
++CONFIG_NFT_OSF=y
++CONFIG_NFT_TPROXY=y
++CONFIG_NF_FLOW_TABLE_INET=y
++CONFIG_NF_FLOW_TABLE=y
++CONFIG_NETFILTER_XT_SET=y
++CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
++CONFIG_NETFILTER_XT_TARGET_HMARK=y
++CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y
++CONFIG_NETFILTER_XT_TARGET_LOG=y
++CONFIG_NETFILTER_XT_TARGET_MARK=y
++CONFIG_NETFILTER_XT_TARGET_NFLOG=y
++CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
++CONFIG_NETFILTER_XT_TARGET_TEE=y
++CONFIG_NETFILTER_XT_TARGET_TCPMSS=y
++CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
++CONFIG_NETFILTER_XT_MATCH_BPF=y
++CONFIG_NETFILTER_XT_MATCH_CGROUP=y
++CONFIG_NETFILTER_XT_MATCH_COMMENT=y
++CONFIG_NETFILTER_XT_MATCH_CPU=y
++CONFIG_NETFILTER_XT_MATCH_DCCP=y
++CONFIG_NETFILTER_XT_MATCH_DEVGROUP=y
++CONFIG_NETFILTER_XT_MATCH_DSCP=y
++CONFIG_NETFILTER_XT_MATCH_ESP=y
++CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y
++CONFIG_NETFILTER_XT_MATCH_IPCOMP=y
++CONFIG_NETFILTER_XT_MATCH_IPRANGE=y
++CONFIG_NETFILTER_XT_MATCH_L2TP=y
++CONFIG_NETFILTER_XT_MATCH_LENGTH=y
++CONFIG_NETFILTER_XT_MATCH_LIMIT=y
++CONFIG_NETFILTER_XT_MATCH_MAC=y
++CONFIG_NETFILTER_XT_MATCH_MARK=y
++CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
++CONFIG_NETFILTER_XT_MATCH_NFACCT=y
++CONFIG_NETFILTER_XT_MATCH_OSF=y
++CONFIG_NETFILTER_XT_MATCH_OWNER=y
++CONFIG_NETFILTER_XT_MATCH_PHYSDEV=y
++CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
++CONFIG_NETFILTER_XT_MATCH_QUOTA=y
++CONFIG_NETFILTER_XT_MATCH_RATEEST=y
++CONFIG_NETFILTER_XT_MATCH_REALM=y
++CONFIG_NETFILTER_XT_MATCH_RECENT=y
++CONFIG_NETFILTER_XT_MATCH_SCTP=y
++CONFIG_NETFILTER_XT_MATCH_SOCKET=y
++CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
++CONFIG_NETFILTER_XT_MATCH_STRING=y
++CONFIG_NETFILTER_XT_MATCH_TCPMSS=y
++CONFIG_NETFILTER_XT_MATCH_TIME=y
++CONFIG_NETFILTER_XT_MATCH_U32=y
++CONFIG_IP_SET=y
++CONFIG_IP_SET_BITMAP_IP=y
++CONFIG_IP_SET_BITMAP_IPMAC=y
++CONFIG_IP_SET_BITMAP_PORT=y
++CONFIG_IP_SET_HASH_IP=y
++CONFIG_IP_SET_HASH_IPMARK=y
++CONFIG_IP_SET_HASH_IPPORT=y
++CONFIG_IP_SET_HASH_IPPORTIP=y
++CONFIG_IP_SET_HASH_IPPORTNET=y
++CONFIG_IP_SET_HASH_IPMAC=y
++CONFIG_IP_SET_HASH_MAC=y
++CONFIG_IP_SET_HASH_NETPORTNET=y
++CONFIG_IP_SET_HASH_NET=y
++CONFIG_IP_SET_HASH_NETNET=y
++CONFIG_IP_SET_HASH_NETPORT=y
++CONFIG_IP_SET_HASH_NETIFACE=y
++CONFIG_IP_SET_LIST_SET=y
++CONFIG_IP_VS=y
++CONFIG_IP_VS_IPV6=y
++CONFIG_IP_VS_PROTO_TCP=y
++CONFIG_IP_VS_PROTO_UDP=y
++CONFIG_IP_VS_PROTO_ESP=y
++CONFIG_IP_VS_PROTO_AH=y
++CONFIG_IP_VS_PROTO_SCTP=y
++CONFIG_IP_VS_WRR=y
++CONFIG_IP_VS_LC=y
++CONFIG_IP_VS_WLC=y
++CONFIG_IP_VS_FO=y
++CONFIG_IP_VS_OVF=y
++CONFIG_IP_VS_LBLC=y
++CONFIG_IP_VS_LBLCR=y
++CONFIG_IP_VS_DH=y
++CONFIG_IP_VS_SH=y
++CONFIG_IP_VS_MH=y
++CONFIG_IP_VS_SED=y
++CONFIG_IP_VS_NQ=y
++CONFIG_IP_VS_NFCT=y
++CONFIG_NFT_CHAIN_ROUTE_IPV4=y
++CONFIG_NF_TABLES_ARP=y
++CONFIG_NF_FLOW_TABLE_IPV4=y
++CONFIG_NF_LOG_ARP=y
++CONFIG_NFT_CHAIN_NAT_IPV4=y
++CONFIG_NFT_MASQ_IPV4=y
++CONFIG_NFT_REDIR_IPV4=y
++CONFIG_IP_NF_IPTABLES=y
++CONFIG_IP_NF_MATCH_AH=y
++CONFIG_IP_NF_MATCH_ECN=y
++CONFIG_IP_NF_MATCH_TTL=y
++CONFIG_IP_NF_FILTER=y
++CONFIG_IP_NF_TARGET_REJECT=y
++CONFIG_IP_NF_TARGET_SYNPROXY=y
++CONFIG_IP_NF_NAT=y
++CONFIG_IP_NF_TARGET_MASQUERADE=y
++CONFIG_IP_NF_TARGET_NETMAP=y
++CONFIG_IP_NF_TARGET_REDIRECT=y
++CONFIG_IP_NF_MANGLE=y
++CONFIG_IP_NF_TARGET_CLUSTERIP=y
++CONFIG_IP_NF_TARGET_ECN=y
++CONFIG_IP_NF_TARGET_TTL=y
++CONFIG_IP_NF_RAW=y
++CONFIG_IP_NF_ARPTABLES=y
++CONFIG_IP_NF_ARPFILTER=y
++CONFIG_IP_NF_ARP_MANGLE=y
++CONFIG_NF_TABLES_BRIDGE=y
++CONFIG_BRIDGE_NF_EBTABLES=y
++CONFIG_BRIDGE_EBT_BROUTE=y
++CONFIG_BRIDGE_EBT_T_FILTER=y
++CONFIG_BRIDGE_EBT_T_NAT=y
++CONFIG_BRIDGE_EBT_802_3=y
++CONFIG_BRIDGE_EBT_AMONG=y
++CONFIG_BRIDGE_EBT_ARP=y
++CONFIG_BRIDGE_EBT_IP=y
++CONFIG_BRIDGE_EBT_IP6=y
++CONFIG_BRIDGE_EBT_LIMIT=y
++CONFIG_BRIDGE_EBT_MARK=y
++CONFIG_BRIDGE_EBT_PKTTYPE=y
++CONFIG_BRIDGE_EBT_STP=y
++CONFIG_BRIDGE_EBT_VLAN=y
++CONFIG_BRIDGE_EBT_ARPREPLY=y
++CONFIG_BRIDGE_EBT_DNAT=y
++CONFIG_BRIDGE_EBT_MARK_T=y
++CONFIG_BRIDGE_EBT_REDIRECT=y
++CONFIG_BRIDGE_EBT_SNAT=y
++CONFIG_BRIDGE_EBT_LOG=y
++CONFIG_BRIDGE_EBT_NFLOG=y
++CONFIG_BRIDGE=y
++CONFIG_CFG80211=y
++CONFIG_CFG80211_WEXT=y
++CONFIG_MAC80211=y
++CONFIG_RFKILL=y
++CONFIG_RFKILL_INPUT=y
++CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
++CONFIG_DEVTMPFS=y
++CONFIG_DEVTMPFS_MOUNT=y
++# CONFIG_STANDALONE is not set
++# CONFIG_PREVENT_FIRMWARE_BUILD is not set
++CONFIG_MTD=y
++CONFIG_MTD_CMDLINE_PARTS=y
++CONFIG_MTD_BLOCK=y
++CONFIG_MTD_NAND=y
++CONFIG_MTD_NAND_ECC_BCH=y
++CONFIG_MTD_NAND_ATMEL=y
++CONFIG_MTD_UBI=y
++CONFIG_MTD_UBI_FASTMAP=y
++CONFIG_BLK_DEV_LOOP=y
++CONFIG_BLK_DEV_LOOP_MIN_COUNT=4
++CONFIG_BLK_DEV_RAM=y
++CONFIG_BLK_DEV_RAM_COUNT=4
++CONFIG_BLK_DEV_RAM_SIZE=8192
++CONFIG_ATMEL_TCLIB=y
++CONFIG_ATMEL_SSC=y
++CONFIG_EEPROM_AT24=y
++CONFIG_SCSI=y
++CONFIG_BLK_DEV_SD=y
++# CONFIG_SCSI_LOWLEVEL is not set
++CONFIG_NETDEVICES=y
++# CONFIG_NET_VENDOR_ALACRITECH is not set
++# CONFIG_NET_VENDOR_AMAZON is not set
++# CONFIG_NET_VENDOR_AQUANTIA is not set
++# CONFIG_NET_VENDOR_ARC is not set
++# CONFIG_NET_VENDOR_AURORA is not set
++# CONFIG_NET_VENDOR_BROADCOM is not set
++CONFIG_MACB=y
++# CONFIG_NET_VENDOR_CAVIUM is not set
++# CONFIG_NET_VENDOR_CIRRUS is not set
++# CONFIG_NET_VENDOR_CORTINA is not set
++# CONFIG_NET_VENDOR_EZCHIP is not set
++# CONFIG_NET_VENDOR_FARADAY is not set
++# CONFIG_NET_VENDOR_HISILICON is not set
++# CONFIG_NET_VENDOR_HUAWEI is not set
++# CONFIG_NET_VENDOR_INTEL is not set
++# CONFIG_NET_VENDOR_MARVELL is not set
++# CONFIG_NET_VENDOR_MELLANOX is not set
++# CONFIG_NET_VENDOR_MICREL is not set
++# CONFIG_NET_VENDOR_MICROCHIP is not set
++# CONFIG_NET_VENDOR_MICROSEMI is not set
++# CONFIG_NET_VENDOR_NATSEMI is not set
++# CONFIG_NET_VENDOR_NETRONOME is not set
++# CONFIG_NET_VENDOR_NI is not set
++# CONFIG_NET_VENDOR_QUALCOMM is not set
++# CONFIG_NET_VENDOR_RENESAS is not set
++# CONFIG_NET_VENDOR_ROCKER is not set
++# CONFIG_NET_VENDOR_SAMSUNG is not set
++# CONFIG_NET_VENDOR_SEEQ is not set
++# CONFIG_NET_VENDOR_SOLARFLARE is not set
++# CONFIG_NET_VENDOR_SMSC is not set
++# CONFIG_NET_VENDOR_SOCIONEXT is not set
++# CONFIG_NET_VENDOR_STMICRO is not set
++# CONFIG_NET_VENDOR_SYNOPSYS is not set
++# CONFIG_NET_VENDOR_VIA is not set
++# CONFIG_NET_VENDOR_WIZNET is not set
++CONFIG_DAVICOM_PHY=y
++CONFIG_MICREL_PHY=y
++CONFIG_PPP=y
++CONFIG_PPP_BSDCOMP=y
++CONFIG_PPP_DEFLATE=y
++CONFIG_PPP_FILTER=y
++CONFIG_PPP_MPPE=y
++CONFIG_PPP_MULTILINK=y
++CONFIG_PPPOE=y
++CONFIG_PPP_ASYNC=y
++CONFIG_PPP_SYNC_TTY=y
++# CONFIG_USB_NET_AX8817X is not set
++# CONFIG_USB_NET_AX88179_178A is not set
++# CONFIG_USB_NET_CDC_NCM is not set
++# CONFIG_USB_NET_NET1080 is not set
++# CONFIG_USB_NET_CDC_SUBSET is not set
++# CONFIG_USB_NET_ZAURUS is not set
++CONFIG_USB_NET_QMI_WWAN=y
++# CONFIG_WLAN_VENDOR_ADMTEK is not set
++# CONFIG_WLAN_VENDOR_ATH is not set
++# CONFIG_WLAN_VENDOR_ATMEL is not set
++# CONFIG_WLAN_VENDOR_BROADCOM is not set
++# CONFIG_WLAN_VENDOR_CISCO is not set
++# CONFIG_WLAN_VENDOR_INTEL is not set
++# CONFIG_WLAN_VENDOR_INTERSIL is not set
++# CONFIG_WLAN_VENDOR_MARVELL is not set
++CONFIG_MT7601U=y
++CONFIG_MT76x0U=y
++CONFIG_MT76x2U=y
++CONFIG_RT2X00=y
++CONFIG_RT2800USB=y
++# CONFIG_RT2800USB_RT33XX is not set
++CONFIG_RT2800USB_RT53XX=y
++CONFIG_RT2800USB_RT55XX=y
++CONFIG_RT2800USB_UNKNOWN=y
++CONFIG_RTL8187=y
++CONFIG_RTL8192CU=y
++CONFIG_RTL8XXXU=y
++CONFIG_RTL8XXXU_UNTESTED=y
++# CONFIG_WLAN_VENDOR_RSI is not set
++# CONFIG_WLAN_VENDOR_ST is not set
++# CONFIG_WLAN_VENDOR_TI is not set
++# CONFIG_WLAN_VENDOR_ZYDAS is not set
++# CONFIG_WLAN_VENDOR_QUANTENNA is not set
++CONFIG_USB_NET_RNDIS_WLAN=y
++CONFIG_INPUT_EVDEV=y
++# CONFIG_KEYBOARD_ATKBD is not set
++CONFIG_KEYBOARD_GPIO=y
++CONFIG_KEYBOARD_GPIO_POLLED=y
++# CONFIG_INPUT_MOUSE is not set
++CONFIG_INPUT_TOUCHSCREEN=y
++# CONFIG_SERIO is not set
++CONFIG_LEGACY_PTY_COUNT=4
++CONFIG_SERIAL_ATMEL=y
++CONFIG_SERIAL_ATMEL_CONSOLE=y
++CONFIG_HW_RANDOM=y
++CONFIG_I2C_CHARDEV=y
++CONFIG_I2C_AT91=y
++CONFIG_I2C_GPIO=y
++CONFIG_SPI=y
++CONFIG_SPI_MEM=y
++CONFIG_SPI_ATMEL=y
++CONFIG_SPI_GPIO=y
++CONFIG_SPI_SPIDEV=y
++CONFIG_GPIO_SYSFS=y
++CONFIG_GPIO_SYSCON=y
++CONFIG_POWER_RESET=y
++CONFIG_POWER_SUPPLY=y
++# CONFIG_HWMON is not set
++CONFIG_WATCHDOG=y
++CONFIG_AT91SAM9X_WATCHDOG=y
++CONFIG_MFD_ATMEL_HLCDC=y
++CONFIG_REGULATOR=y
++CONFIG_REGULATOR_FIXED_VOLTAGE=y
++CONFIG_MEDIA_SUPPORT=y
++CONFIG_MEDIA_CAMERA_SUPPORT=y
++CONFIG_MEDIA_USB_SUPPORT=y
++CONFIG_USB_VIDEO_CLASS=y
++# CONFIG_USB_GSPCA is not set
++CONFIG_V4L_PLATFORM_DRIVERS=y
++# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set
++CONFIG_DRM=y
++CONFIG_DRM_ATMEL_HLCDC=y
++CONFIG_DRM_PANEL_SIMPLE=y
++CONFIG_FB_ATMEL=y
++# CONFIG_LCD_CLASS_DEVICE is not set
++CONFIG_BACKLIGHT_ATMEL_LCDC=y
++# CONFIG_BACKLIGHT_GENERIC is not set
++CONFIG_BACKLIGHT_PWM=y
++CONFIG_SOUND=y
++CONFIG_SND=y
++CONFIG_SND_OSSEMUL=y
++CONFIG_SND_MIXER_OSS=y
++CONFIG_SND_PCM_OSS=y
++# CONFIG_SND_SPI is not set
++CONFIG_SND_USB_AUDIO=y
++CONFIG_USB=y
++CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
++CONFIG_USB_EHCI_HCD=y
++CONFIG_USB_OHCI_HCD=y
++CONFIG_USB_ACM=y
++CONFIG_USB_STORAGE=y
++CONFIG_USB_SERIAL=y
++CONFIG_USB_SERIAL_GENERIC=y
++CONFIG_USB_SERIAL_CH341=y
++CONFIG_USB_SERIAL_CP210X=y
++CONFIG_USB_SERIAL_FTDI_SIO=y
++CONFIG_USB_SERIAL_PL2303=y
++CONFIG_USB_SERIAL_OPTION=y
++CONFIG_USB_GADGET=y
++CONFIG_USB_ATMEL_USBA=m
++CONFIG_USB_CONFIGFS=y
++CONFIG_USB_CONFIGFS_ACM=y
++CONFIG_USB_CONFIGFS_MASS_STORAGE=y
++CONFIG_USB_MASS_STORAGE=m
++CONFIG_USB_G_SERIAL=m
++CONFIG_MMC=y
++CONFIG_MMC_ATMELMCI=y
++CONFIG_NEW_LEDS=y
++CONFIG_LEDS_CLASS=y
++CONFIG_LEDS_GPIO=y
++CONFIG_LEDS_PWM=y
++CONFIG_LEDS_TRIGGERS=y
++CONFIG_LEDS_TRIGGER_TIMER=y
++CONFIG_LEDS_TRIGGER_HEARTBEAT=y
++CONFIG_LEDS_TRIGGER_CPU=y
++CONFIG_LEDS_TRIGGER_GPIO=y
++CONFIG_RTC_CLASS=y
++CONFIG_RTC_DRV_ISL1208=y
++CONFIG_DMADEVICES=y
++CONFIG_AT_HDMAC=y
++# CONFIG_IOMMU_SUPPORT is not set
++CONFIG_IIO=y
++CONFIG_AT91_ADC=y
++CONFIG_PWM=y
++CONFIG_PWM_ATMEL=y
++CONFIG_PWM_ATMEL_HLCDC_PWM=y
++CONFIG_PWM_ATMEL_TCB=y
++CONFIG_EXT3_FS=y
++CONFIG_FANOTIFY=y
++CONFIG_OVERLAY_FS=y
++CONFIG_VFAT_FS=y
++CONFIG_NTFS_FS=y
++CONFIG_NTFS_RW=y
++CONFIG_TMPFS=y
++CONFIG_JFFS2_FS=y
++CONFIG_UBIFS_FS=y
++CONFIG_UBIFS_FS_ADVANCED_COMPR=y
++CONFIG_NFS_FS=y
++CONFIG_NFS_V4=y
++CONFIG_NLS_CODEPAGE_437=y
++CONFIG_NLS_CODEPAGE_850=y
++CONFIG_NLS_CODEPAGE_936=y
++CONFIG_NLS_CODEPAGE_950=y
++CONFIG_NLS_ISO8859_1=y
++CONFIG_NLS_UTF8=y
++CONFIG_CRYPTO_ECHAINIV=y
++CONFIG_CRYPTO_USER_API_HASH=y
++CONFIG_CRYPTO_USER_API_SKCIPHER=y
++CONFIG_CRYPTO_USER_API_RNG=y
++CONFIG_CRYPTO_USER_API_AEAD=y
++# CONFIG_CRYPTO_HW is not set
++CONFIG_STRIP_ASM_SYMS=y
++CONFIG_DEBUG_FS=y
++# CONFIG_SCHED_DEBUG is not set
++# CONFIG_DEBUG_BUGVERBOSE is not set
++# CONFIG_FTRACE is not set
++CONFIG_DEBUG_USER=y
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/GobiUSBNet.c linux-at91-loragw/drivers/net/usb/ec20/GobiUSBNet.c
+--- linux-at91/drivers/net/usb/ec20/GobiUSBNet.c	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/GobiUSBNet.c	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,1700 @@
++/*===========================================================================
++FILE:
++   GobiUSBNet.c
++
++DESCRIPTION:
++   Qualcomm USB Network device for Gobi 3000
++   
++FUNCTIONS:
++   GobiNetSuspend
++   GobiNetResume
++   GobiNetDriverBind
++   GobiNetDriverUnbind
++   GobiUSBNetURBCallback
++   GobiUSBNetTXTimeout
++   GobiUSBNetAutoPMThread
++   GobiUSBNetStartXmit
++   GobiUSBNetOpen
++   GobiUSBNetStop
++   GobiUSBNetProbe
++   GobiUSBNetModInit
++   GobiUSBNetModExit
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++//---------------------------------------------------------------------------
++// Include Files
++//---------------------------------------------------------------------------
++
++#include "Structs.h"
++#include "QMIDevice.h"
++#include "QMI.h"
++#include <linux/etherdevice.h>
++#include <linux/ethtool.h>
++#include <linux/module.h>
++#include <net/arp.h>
++#include <linux/ip.h>
++//#include <linux/udp.h>
++
++//-----------------------------------------------------------------------------
++// Definitions
++//-----------------------------------------------------------------------------
++
++// Version Information
++#define DRIVER_VERSION "Quectel_WCDMA&LTE_Linux&Android_GobiNet_Driver_V1.3.0"
++#define DRIVER_AUTHOR "Qualcomm Innovation Center"
++#define DRIVER_DESC "GobiNet"
++
++// Debug flag
++int debug = 0;
++
++// Allow user interrupts
++int interruptible = 1;
++
++// Number of IP packets which may be queued up for transmit
++int txQueueLength = 100;
++
++// Class should be created during module init, so needs to be global
++static struct class * gpClass;
++
++static const unsigned char ec20_mac[ETH_ALEN] = {0x02, 0x50, 0xf3, 0x00, 0x00, 0x00};
++//static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
++
++//#define QUECTEL_WWAN_MULTI_PACKAGES
++
++#ifdef QUECTEL_WWAN_MULTI_PACKAGES
++static uint __read_mostly rx_packets = 10;
++module_param( rx_packets, uint, S_IRUGO | S_IWUSR );
++
++#define USB_CDC_SET_MULTI_PACKAGE_COMMAND (0x5C)
++#define QUEC_NET_MSG_SPEC		(0x80)
++#define QUEC_NET_MSG_ID_IP_DATA		(0x00)
++
++struct multi_package_config {
++	__le32 enable;
++	__le32 package_max_len;
++	__le32 package_max_count_in_queue;
++	__le32 timeout;
++} __packed;
++
++struct quec_net_package_header {
++	unsigned char msg_spec;
++	unsigned char msg_id;
++	unsigned short payload_len;
++	unsigned char reserve[16];
++} __packed;
++#endif
++
++#ifdef CONFIG_BRIDGE
++static int __read_mostly bridge_mode = 0;
++static uint __read_mostly bridge_ipv4 = 0;
++module_param( bridge_mode, int, S_IRUGO | S_IWUSR );
++module_param( bridge_ipv4, uint, S_IRUGO | S_IWUSR );
++
++static int bridge_arp_reply(struct net_device *dev, struct sk_buff *skb) {
++    struct arphdr *parp;
++    u8 *arpptr, *sha;
++    __be32 sip, tip, ipv4;
++    struct sk_buff *reply = NULL;
++        
++    parp = arp_hdr(skb);
++
++    if (parp->ar_hrd == htons(ARPHRD_ETHER)  && parp->ar_pro == htons(ETH_P_IP)
++        && parp->ar_op == htons(ARPOP_REQUEST) && parp->ar_hln == 6 && parp->ar_pln == 4) {
++        arpptr = (u8 *)parp + sizeof(struct arphdr);
++        sha = arpptr;
++        arpptr += dev->addr_len;	/* sha */
++        memcpy(&sip, arpptr, sizeof(sip));
++        arpptr += sizeof(sip);
++        arpptr += dev->addr_len;	/* tha */
++        memcpy(&tip, arpptr, sizeof(tip));
++
++        ipv4 = ((bridge_ipv4 & 0xff)<<24) | ((bridge_ipv4 & 0xff00)<<8)
++            | ((bridge_ipv4 & 0xff0000) >> 8) |((bridge_ipv4 & 0xff000000) >> 24);
++        DBG("sip = 0x%08x, tip=%08x, gw=%08x\n", sip, tip, ipv4);
++        if (((u8 *)&tip)[0] == ((u8 *)&ipv4)[0] && ((u8 *)&tip)[1] == ((u8 *)&ipv4)[1] && ((u8 *)&tip)[2] == ((u8 *)&ipv4)[2] && ((u8 *)&tip)[3] != ((u8 *)&ipv4)[3])
++            reply = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, ec20_mac, sha);
++
++        if (reply) {
++            skb_reset_mac_header(reply);
++            __skb_pull(reply, skb_network_offset(reply));
++            reply->ip_summed = CHECKSUM_UNNECESSARY;
++            reply->pkt_type = PACKET_HOST;
++
++            netif_rx_ni(reply);
++        }
++        return 1;
++    }
++
++    return 0;
++}
++#endif
++
++#ifdef CONFIG_PM
++/*===========================================================================
++METHOD:
++   GobiNetSuspend (Public Method)
++
++DESCRIPTION:
++   Stops QMI traffic while device is suspended
++
++PARAMETERS
++   pIntf          [ I ] - Pointer to interface
++   powerEvent     [ I ] - Power management event
++
++RETURN VALUE:
++   int - 0 for success
++         negative errno for failure
++===========================================================================*/
++int GobiNetSuspend(
++   struct usb_interface *     pIntf,
++   pm_message_t               powerEvent )
++{
++   struct usbnet * pDev;
++   sGobiUSBNet * pGobiDev;
++   
++   if (pIntf == 0)
++   {
++      return -ENOMEM;
++   }
++   
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
++   pDev = usb_get_intfdata( pIntf );
++#else
++   pDev = (struct usbnet *)pIntf->dev.platform_data;
++#endif
++
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get netdevice\n" );
++      return -ENXIO;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return -ENXIO;
++   }
++
++   // Is this autosuspend or system suspend?
++   //    do we allow remote wakeup?
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++   if (pDev->udev->auto_pm == 0)
++#else
++   if (1)
++#endif
++#else
++   if ((powerEvent.event & PM_EVENT_AUTO) == 0)
++#endif
++   {
++      DBG( "device suspended to power level %d\n", 
++           powerEvent.event );
++      GobiSetDownReason( pGobiDev, DRIVER_SUSPENDED );
++   }
++   else
++   {
++      DBG( "device autosuspend\n" );
++   }
++     
++   if (powerEvent.event & PM_EVENT_SUSPEND)
++   {
++      // Stop QMI read callbacks
++      KillRead( pGobiDev );
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
++      pDev->udev->reset_resume = 0;
++#endif
++      
++      // Store power state to avoid duplicate resumes
++      pIntf->dev.power.power_state.event = powerEvent.event;
++   }
++   else
++   {
++      // Other power modes cause QMI connection to be lost
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
++      pDev->udev->reset_resume = 1;
++#endif
++   }
++   
++   // Run usbnet's suspend function
++   return usbnet_suspend( pIntf, powerEvent );
++}
++   
++/*===========================================================================
++METHOD:
++   GobiNetResume (Public Method)
++
++DESCRIPTION:
++   Resume QMI traffic or recreate QMI device
++
++PARAMETERS
++   pIntf          [ I ] - Pointer to interface
++
++RETURN VALUE:
++   int - 0 for success
++         negative errno for failure
++===========================================================================*/
++int GobiNetResume( struct usb_interface * pIntf )
++{
++   struct usbnet * pDev;
++   sGobiUSBNet * pGobiDev;
++   int nRet;
++   int oldPowerState;
++   
++   if (pIntf == 0)
++   {
++      return -ENOMEM;
++   }
++   
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
++   pDev = usb_get_intfdata( pIntf );
++#else
++   pDev = (struct usbnet *)pIntf->dev.platform_data;
++#endif
++
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get netdevice\n" );
++      return -ENXIO;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return -ENXIO;
++   }
++
++   oldPowerState = pIntf->dev.power.power_state.event;
++   pIntf->dev.power.power_state.event = PM_EVENT_ON;
++   DBG( "resuming from power mode %d\n", oldPowerState );
++
++   if (oldPowerState & PM_EVENT_SUSPEND)
++   {
++      // It doesn't matter if this is autoresume or system resume
++      GobiClearDownReason( pGobiDev, DRIVER_SUSPENDED );
++   
++      nRet = usbnet_resume( pIntf );
++      if (nRet != 0)
++      {
++         DBG( "usbnet_resume error %d\n", nRet );
++         return nRet;
++      }
++
++      // Restart QMI read callbacks
++      nRet = StartRead( pGobiDev );
++      if (nRet != 0)
++      {
++         DBG( "StartRead error %d\n", nRet );
++         return nRet;
++      }
++
++#ifdef CONFIG_PM
++   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))   
++      // Kick Auto PM thread to process any queued URBs
++      complete( &pGobiDev->mAutoPM.mThreadDoWork );
++    #endif
++#endif /* CONFIG_PM */
++   }
++   else
++   {
++      DBG( "nothing to resume\n" );
++      return 0;
++   }
++   
++   return nRet;
++}
++#endif /* CONFIG_PM */
++
++/*===========================================================================
++METHOD:
++   GobiNetDriverBind (Public Method)
++
++DESCRIPTION:
++   Setup in and out pipes
++
++PARAMETERS
++   pDev           [ I ] - Pointer to usbnet device
++   pIntf          [ I ] - Pointer to interface
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++static int GobiNetDriverBind( 
++   struct usbnet *         pDev, 
++   struct usb_interface *  pIntf )
++{
++   int numEndpoints;
++   int endpointIndex;
++   struct usb_host_endpoint * pEndpoint = NULL;
++   struct usb_host_endpoint * pIn = NULL;
++   struct usb_host_endpoint * pOut = NULL;
++  
++   // Verify one altsetting
++   if (pIntf->num_altsetting != 1)
++   {
++      DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting );
++      return -ENODEV;
++   }
++
++   // Verify correct interface (4 for UC20)
++   if ( !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data))
++   {
++      DBG( "invalid interface %d\n", 
++           pIntf->cur_altsetting->desc.bInterfaceNumber );
++      return -ENODEV;
++   }
++   
++   if ( pIntf->cur_altsetting->desc.bInterfaceClass != 0xff)
++   {
++      struct usb_interface_descriptor *desc = &pIntf->cur_altsetting->desc;
++      const char *qcfg_usbnet = "UNKNOW";
++      
++      if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x0e) {
++         qcfg_usbnet = "MBIM";
++      } else if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x06) {
++         qcfg_usbnet = "ECM";
++      } else if (desc->bInterfaceClass == 0xe0 && desc->bInterfaceSubClass == 1 && desc->bInterfaceProtocol == 3) {
++         qcfg_usbnet = "RNDIS";
++      }
++
++      INFO( "usbnet is %s not NDIS/RMNET!\n", qcfg_usbnet);
++
++      return -ENODEV;
++   }
++   
++   // Collect In and Out endpoints
++   numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints;
++   for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++)
++   {
++      pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex;
++      if (pEndpoint == NULL)
++      {
++         DBG( "invalid endpoint %u\n", endpointIndex );
++         return -ENODEV;
++      }
++
++      if (usb_endpoint_dir_in( &pEndpoint->desc ) == true
++      &&  usb_endpoint_xfer_int( &pEndpoint->desc ) == false)
++      {
++         pIn = pEndpoint;
++      }
++      else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true)
++      {
++         pOut = pEndpoint;
++      }
++   }
++   
++   if (pIn == NULL || pOut == NULL)
++   {
++      DBG( "invalid endpoints\n" );
++      return -ENODEV;
++   }
++
++   if (usb_set_interface( pDev->udev, 
++                          pIntf->cur_altsetting->desc.bInterfaceNumber,
++                          0 ) != 0)
++   {
++      DBG( "unable to set interface\n" );
++      return -ENODEV;
++   }
++
++   pDev->in = usb_rcvbulkpipe( pDev->udev,
++                   pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
++   pDev->out = usb_sndbulkpipe( pDev->udev,
++                   pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
++
++#ifdef QUECTEL_WWAN_MULTI_PACKAGES
++    if (rx_packets && pDev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
++        struct multi_package_config rx_config = {
++            .enable = cpu_to_le32(1),
++            .package_max_len = cpu_to_le32((1500 + sizeof(struct quec_net_package_header)) * rx_packets),
++            .package_max_count_in_queue = cpu_to_le32(rx_packets), 
++            .timeout = cpu_to_le32(10*1000), //10ms
++        };
++        int ret = 0;
++        
++    	ret = usb_control_msg(
++    		interface_to_usbdev(pIntf),
++    		usb_sndctrlpipe(interface_to_usbdev(pIntf), 0),
++    		USB_CDC_SET_MULTI_PACKAGE_COMMAND,
++    		0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
++    		1,
++    		pIntf->cur_altsetting->desc.bInterfaceNumber,
++    		&rx_config, sizeof(rx_config), 100);
++
++        DBG( "Quectel EC21&EC25 rx_packets=%d, ret=%d\n", rx_packets, ret);        
++        if (ret == sizeof(rx_config)) {
++           pDev->rx_urb_size = le32_to_cpu(rx_config.package_max_len);
++        } else {
++            rx_packets = 0;
++        }
++    }
++#endif
++
++#if 1 //def DATA_MODE_RP
++    /* make MAC addr easily distinguishable from an IP header */
++    if ((pDev->net->dev_addr[0] & 0xd0) == 0x40) {
++        /*clear this bit wil make usbnet apdater named as usbX(instead if ethX)*/
++        pDev->net->dev_addr[0] |= 0x02;	/* set local assignment bit */
++        pDev->net->dev_addr[0] &= 0xbf;	/* clear "IP" bit */
++    }
++#endif
++                   
++   DBG( "in %x, out %x\n", 
++        pIn->desc.bEndpointAddress, 
++        pOut->desc.bEndpointAddress );
++
++   // In later versions of the kernel, usbnet helps with this
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
++   pIntf->dev.platform_data = (void *)pDev;
++#endif
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   GobiNetDriverUnbind (Public Method)
++
++DESCRIPTION:
++   Deregisters QMI device (Registration happened in the probe function)
++
++PARAMETERS
++   pDev           [ I ] - Pointer to usbnet device
++   pIntfUnused    [ I ] - Pointer to interface
++
++RETURN VALUE:
++   None
++===========================================================================*/
++static void GobiNetDriverUnbind( 
++   struct usbnet *         pDev, 
++   struct usb_interface *  pIntf)
++{
++   sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
++
++   // Should already be down, but just in case...
++   netif_carrier_off( pDev->net );
++
++   DeregisterQMIDevice( pGobiDev );
++   
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
++   kfree( pDev->net->netdev_ops );
++   pDev->net->netdev_ops = NULL;
++#endif
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
++   pIntf->dev.platform_data = NULL;
++#endif
++
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 ))
++   pIntf->needs_remote_wakeup = 0;
++#endif
++
++   if (atomic_dec_and_test(&pGobiDev->refcount))
++      kfree( pGobiDev );
++   else
++      DBG("memory leak!\n");
++}
++
++#if 1 //def DATA_MODE_RP
++/*===========================================================================
++METHOD:
++   GobiNetDriverTxFixup (Public Method)
++
++DESCRIPTION:
++   Handling data format mode on transmit path
++
++PARAMETERS
++   pDev           [ I ] - Pointer to usbnet device
++   pSKB           [ I ] - Pointer to transmit packet buffer
++   flags          [ I ] - os flags
++
++RETURN VALUE:
++   None
++===========================================================================*/
++struct sk_buff *GobiNetDriverTxFixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
++{
++    sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
++
++    if (!pGobiDev) {
++        DBG( "failed to get QMIDevice\n" );
++        dev_kfree_skb_any(skb);
++        return NULL;       
++    }
++
++    if (!pGobiDev->mbRawIPMode)
++        return skb;
++
++#ifdef CONFIG_BRIDGE
++    if (bridge_mode) {
++        struct ethhdr *ehdr = eth_hdr(skb);
++        const struct iphdr *iph;
++        
++//debug = 1;
++//        DBG("ethhdr: ");
++//        PrintHex(ehdr, sizeof(struct ethhdr));
++        
++        if (ehdr->h_proto == htons(ETH_P_ARP)) {
++            bridge_arp_reply(dev->net, skb);
++            dev_kfree_skb_any(skb);
++            return NULL;
++        }
++
++        iph = ip_hdr(skb);
++        //DBG("iphdr: ");
++        //PrintHex((void *)iph, sizeof(struct iphdr));
++
++// 1	0.000000000	0.0.0.0	255.255.255.255	DHCP	362	DHCP Request  - Transaction ID 0xe7643ad7        
++        if (ehdr->h_proto == htons(ETH_P_IP) && iph->protocol == IPPROTO_UDP && iph->saddr == 0x00000000 && iph->daddr == 0xFFFFFFFF) {
++            //DBG("udphdr: ");
++            //PrintHex(udp_hdr(skb), sizeof(struct udphdr));
++            
++            //if (udp_hdr(skb)->dest == htons(67)) //DHCP Request
++            {
++                memcpy(pGobiDev->mHostMAC, ehdr->h_source, ETH_ALEN);
++                DBG("PC Mac Address: ");
++                PrintHex(pGobiDev->mHostMAC, ETH_ALEN);   
++            }
++        }
++
++#if 0
++//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv4mcast_7f:ff:fa (01:00:5e:7f:ff:fa)
++//126	85.213727000	10.184.164.175	239.255.255.250	SSDP	175	M-SEARCH * HTTP/1.1 
++//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv6mcast_16 (33:33:00:00:00:16)
++//160	110.305488000	fe80::6819:38ad:fcdc:2444	ff02::16	ICMPv6	90	Multicast Listener Report Message v2        
++        if (memcmp(ehdr->h_dest, ec20_mac, ETH_ALEN) && memcmp(ehdr->h_dest, broadcast_addr, ETH_ALEN)) {
++            DBG("Drop h_dest: ");
++            PrintHex(ehdr, sizeof(struct ethhdr));
++            dev_kfree_skb_any(skb);
++            return NULL;
++        }
++#endif
++        
++        if (memcmp(ehdr->h_source, pGobiDev->mHostMAC, ETH_ALEN)) {
++            DBG("Drop h_source: ");
++            PrintHex(ehdr, sizeof(struct ethhdr));
++            dev_kfree_skb_any(skb);
++            return NULL;
++        }
++        
++//debug = 0;
++    }  
++#endif
++        
++    // Skip Ethernet header from message
++    if (skb_pull(skb, ETH_HLEN)) {
++        return skb;
++    } else {
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
++        dev_err(&dev->intf->dev,  "Packet Dropped ");
++#elif (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++        dev_err(dev->net->dev.parent,  "Packet Dropped ");
++#else
++        INFO("Packet Dropped ");
++#endif
++    }
++    
++   // Filter the packet out, release it
++   dev_kfree_skb_any(skb);
++   return NULL;
++}
++
++/*===========================================================================
++METHOD:
++   GobiNetDriverRxFixup (Public Method)
++
++DESCRIPTION:
++   Handling data format mode on receive path
++
++PARAMETERS
++   pDev           [ I ] - Pointer to usbnet device
++   pSKB           [ I ] - Pointer to received packet buffer
++
++RETURN VALUE:
++   None
++===========================================================================*/
++static int GobiNetDriverRxFixup(struct usbnet *dev, struct sk_buff *skb)
++{
++    __be16 proto;
++    sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
++
++    if (!pGobiDev->mbRawIPMode)
++        return 1;
++
++    /* This check is no longer done by usbnet */
++    if (skb->len < dev->net->hard_header_len)
++        return 0;
++
++    switch (skb->data[0] & 0xf0) {
++    case 0x40:
++    	proto = htons(ETH_P_IP);
++    	break;
++    case 0x60:
++    	proto = htons(ETH_P_IPV6);
++    	break;
++    case 0x00:
++    	if (is_multicast_ether_addr(skb->data))
++    		return 1;
++    	/* possibly bogus destination - rewrite just in case */
++    	skb_reset_mac_header(skb);
++    	goto fix_dest;
++    default:
++    	/* pass along other packets without modifications */
++    	return 1;
++    }
++    if (skb_headroom(skb) < ETH_HLEN && pskb_expand_head(skb, ETH_HLEN, 0, GFP_ATOMIC)) {
++        DBG("%s: couldn't pskb_expand_head\n", __func__);
++        return 0;
++    }
++    skb_push(skb, ETH_HLEN);
++    skb_reset_mac_header(skb);
++    eth_hdr(skb)->h_proto = proto;
++    memcpy(eth_hdr(skb)->h_source, ec20_mac, ETH_ALEN);
++fix_dest:
++#ifdef CONFIG_BRIDGE
++   if (bridge_mode) {
++      memcpy(eth_hdr(skb)->h_dest, pGobiDev->mHostMAC, ETH_ALEN);
++      //memcpy(eth_hdr(skb)->h_dest, broadcast_addr, ETH_ALEN);
++    } else
++#endif
++    memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
++
++#ifdef CONFIG_BRIDGE
++#if 0
++    if (bridge_mode) {
++        struct ethhdr *ehdr = eth_hdr(skb);
++debug = 1;
++        DBG(": ");
++        PrintHex(ehdr, sizeof(struct ethhdr));
++debug = 0;
++    }
++#endif
++#endif
++       
++    return 1;
++}
++
++#ifdef QUECTEL_WWAN_MULTI_PACKAGES
++static int GobiNetDriverRxPktsFixup(struct usbnet *dev, struct sk_buff *skb)
++{
++    sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
++
++    if (!pGobiDev->mbRawIPMode)
++        return 1;
++
++    /* This check is no longer done by usbnet */
++    if (skb->len < dev->net->hard_header_len)
++        return 0;
++
++    if (!rx_packets) {
++        return GobiNetDriverRxFixup(dev, skb);
++    }
++
++    while (likely(skb->len)) {
++        struct sk_buff* new_skb;
++        struct quec_net_package_header package_header;
++
++        if (skb->len < sizeof(package_header))
++            return 0;
++
++        memcpy(&package_header, skb->data, sizeof(package_header));
++        package_header.payload_len = be16_to_cpu(package_header.payload_len);
++
++        if (package_header.msg_spec != QUEC_NET_MSG_SPEC || package_header.msg_id != QUEC_NET_MSG_ID_IP_DATA)
++            return 0;
++
++        if (skb->len < (package_header.payload_len + sizeof(package_header)))
++            return 0;
++
++        skb_pull(skb, sizeof(package_header));
++
++        if (skb->len == package_header.payload_len)
++            return GobiNetDriverRxFixup(dev, skb);
++
++        new_skb = skb_clone(skb, GFP_ATOMIC);
++        if (new_skb) {
++            skb_trim(new_skb, package_header.payload_len);
++            if (GobiNetDriverRxFixup(dev, new_skb))
++                usbnet_skb_return(dev, new_skb);
++            else
++                return 0;
++        }
++
++        skb_pull(skb, package_header.payload_len);
++    }
++
++    return 0;
++}
++#endif
++#endif
++
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++#ifdef CONFIG_PM
++/*===========================================================================
++METHOD:
++   GobiUSBNetURBCallback (Public Method)
++
++DESCRIPTION:
++   Write is complete, cleanup and signal that we're ready for next packet
++
++PARAMETERS
++   pURB     [ I ] - Pointer to sAutoPM struct
++
++RETURN VALUE:
++   None
++===========================================================================*/
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void GobiUSBNetURBCallback( struct urb * pURB )
++#else
++void GobiUSBNetURBCallback(struct urb *pURB, struct pt_regs *regs)
++#endif
++{
++   unsigned long activeURBflags;
++   sAutoPM * pAutoPM = (sAutoPM *)pURB->context;
++   if (pAutoPM == NULL)
++   {
++      // Should never happen
++      DBG( "bad context\n" );
++      return;
++   }
++
++   if (pURB->status != 0)
++   {
++      // Note that in case of an error, the behaviour is no different
++      DBG( "urb finished with error %d\n", pURB->status );
++   }
++
++   // Remove activeURB (memory to be freed later)
++   spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++
++   // EAGAIN used to signify callback is done
++   pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN );
++
++   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++
++   complete( &pAutoPM->mThreadDoWork );
++   
++#ifdef URB_FREE_BUFFER_BY_SELF
++    if (pURB->transfer_flags & URB_FREE_BUFFER)
++        kfree(pURB->transfer_buffer);
++#endif
++   usb_free_urb( pURB );
++}
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetTXTimeout (Public Method)
++
++DESCRIPTION:
++   Timeout declared by the net driver.  Stop all transfers
++
++PARAMETERS
++   pNet     [ I ] - Pointer to net device
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void GobiUSBNetTXTimeout( struct net_device * pNet )
++{
++   struct sGobiUSBNet * pGobiDev;
++   sAutoPM * pAutoPM;
++   sURBList * pURBListEntry;
++   unsigned long activeURBflags, URBListFlags;
++   struct usbnet * pDev = netdev_priv( pNet );
++   struct urb * pURB;
++
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get usbnet device\n" );
++      return;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return;
++   }
++   pAutoPM = &pGobiDev->mAutoPM;
++
++   DBG( "\n" );
++
++   // Grab a pointer to active URB
++   spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++   pURB = pAutoPM->mpActiveURB;
++   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++   // Stop active URB
++   if (pURB != NULL)
++   {
++      usb_kill_urb( pURB );
++   }
++
++   // Cleanup URB List
++   spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
++
++   pURBListEntry = pAutoPM->mpURBList;
++   while (pURBListEntry != NULL)
++   {
++      pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
++      atomic_dec( &pAutoPM->mURBListLen );
++      usb_free_urb( pURBListEntry->mpURB );
++      kfree( pURBListEntry );
++      pURBListEntry = pAutoPM->mpURBList;
++   }
++
++   spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++
++   complete( &pAutoPM->mThreadDoWork );
++
++   return;
++}
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetAutoPMThread (Public Method)
++
++DESCRIPTION:
++   Handle device Auto PM state asynchronously
++   Handle network packet transmission asynchronously
++
++PARAMETERS
++   pData     [ I ] - Pointer to sAutoPM struct
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++static int GobiUSBNetAutoPMThread( void * pData )
++{
++   unsigned long activeURBflags, URBListFlags;
++   sURBList * pURBListEntry;
++   int status;
++   struct usb_device * pUdev;
++   sAutoPM * pAutoPM = (sAutoPM *)pData;
++   struct urb * pURB;
++
++   if (pAutoPM == NULL)
++   {
++      DBG( "passed null pointer\n" );
++      return -EINVAL;
++   }
++   
++   pUdev = interface_to_usbdev( pAutoPM->mpIntf );
++
++   DBG( "traffic thread started\n" );
++
++   while (pAutoPM->mbExit == false)
++   {
++      // Wait for someone to poke us
++      wait_for_completion_interruptible( &pAutoPM->mThreadDoWork );
++
++      // Time to exit?
++      if (pAutoPM->mbExit == true)
++      {
++         // Stop activeURB
++         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++         pURB = pAutoPM->mpActiveURB;
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++
++         // EAGAIN used to signify callback is done
++         if (IS_ERR( pAutoPM->mpActiveURB )
++                 &&  PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
++         {
++             pURB = NULL;
++         }
++
++         if (pURB != NULL)
++         {
++            usb_kill_urb( pURB );
++         }
++         // Will be freed in callback function
++
++         // Cleanup URB List
++         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
++
++         pURBListEntry = pAutoPM->mpURBList;
++         while (pURBListEntry != NULL)
++         {
++            pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
++            atomic_dec( &pAutoPM->mURBListLen );
++            usb_free_urb( pURBListEntry->mpURB );
++            kfree( pURBListEntry );
++            pURBListEntry = pAutoPM->mpURBList;
++         }
++
++         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++
++         break;
++      }
++      
++      // Is our URB active?
++      spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++
++      // EAGAIN used to signify callback is done
++      if (IS_ERR( pAutoPM->mpActiveURB ) 
++      &&  PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
++      {
++         pAutoPM->mpActiveURB = NULL;
++
++         // Restore IRQs so task can sleep
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++         
++         // URB is done, decrement the Auto PM usage count
++         usb_autopm_put_interface( pAutoPM->mpIntf );
++
++         // Lock ActiveURB again
++         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++      }
++
++      if (pAutoPM->mpActiveURB != NULL)
++      {
++         // There is already a URB active, go back to sleep
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++         continue;
++      }
++      
++      // Is there a URB waiting to be submitted?
++      spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
++      if (pAutoPM->mpURBList == NULL)
++      {
++         // No more URBs to submit, go back to sleep
++         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++         continue;
++      }
++
++      // Pop an element
++      pURBListEntry = pAutoPM->mpURBList;
++      pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
++      atomic_dec( &pAutoPM->mURBListLen );
++      spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++
++      // Set ActiveURB
++      pAutoPM->mpActiveURB = pURBListEntry->mpURB;
++      spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++
++      // Tell autopm core we need device woken up
++      status = usb_autopm_get_interface( pAutoPM->mpIntf );
++      if (status < 0)
++      {
++         DBG( "unable to autoresume interface: %d\n", status );
++
++         // likely caused by device going from autosuspend -> full suspend
++         if (status == -EPERM)
++         {
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++            pUdev->auto_pm = 0;
++#else
++             pUdev = pUdev;
++#endif
++#endif
++            GobiNetSuspend( pAutoPM->mpIntf, PMSG_SUSPEND );
++         }
++
++         // Add pURBListEntry back onto pAutoPM->mpURBList
++         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
++         pURBListEntry->mpNext = pAutoPM->mpURBList;
++         pAutoPM->mpURBList = pURBListEntry;
++         atomic_inc( &pAutoPM->mURBListLen );
++         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++         
++         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++         pAutoPM->mpActiveURB = NULL;
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++         
++         // Go back to sleep
++         continue;
++      }
++
++      // Submit URB
++      status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL );
++      if (status < 0)
++      {
++         // Could happen for a number of reasons
++         DBG( "Failed to submit URB: %d.  Packet dropped\n", status );
++         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
++         usb_free_urb( pAutoPM->mpActiveURB );
++         pAutoPM->mpActiveURB = NULL;
++         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
++         usb_autopm_put_interface( pAutoPM->mpIntf );
++
++         // Loop again
++         complete( &pAutoPM->mThreadDoWork );
++      }
++      
++      kfree( pURBListEntry );
++   }   
++   
++   DBG( "traffic thread exiting\n" );
++   pAutoPM->mpThread = NULL;
++   return 0;
++}      
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetStartXmit (Public Method)
++
++DESCRIPTION:
++   Convert sk_buff to usb URB and queue for transmit
++
++PARAMETERS
++   pNet     [ I ] - Pointer to net device
++
++RETURN VALUE:
++   NETDEV_TX_OK on success
++   NETDEV_TX_BUSY on error
++===========================================================================*/
++int GobiUSBNetStartXmit( 
++   struct sk_buff *     pSKB,
++   struct net_device *  pNet )
++{
++   unsigned long URBListFlags;
++   struct sGobiUSBNet * pGobiDev;
++   sAutoPM * pAutoPM;
++   sURBList * pURBListEntry, ** ppURBListEnd;
++   void * pURBData;
++   struct usbnet * pDev = netdev_priv( pNet );
++   
++   //DBG( "\n" );
++   
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get usbnet device\n" );
++      return NETDEV_TX_BUSY;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return NETDEV_TX_BUSY;
++   }
++   pAutoPM = &pGobiDev->mAutoPM;
++   
++   if( NULL == pSKB )
++   {
++       DBG( "Buffer is NULL \n" );
++       return NETDEV_TX_BUSY;
++   }
++
++   if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
++   {
++      // Should not happen
++      DBG( "device is suspended\n" );
++      dump_stack();
++      return NETDEV_TX_BUSY;
++   }
++   
++   if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION ))
++   {
++      //netif_carrier_off( pGobiDev->mpNetDev->net );
++      //DBG( "device is disconnected\n" );
++      //dump_stack();
++      return NETDEV_TX_BUSY;
++   }
++   
++   // Convert the sk_buff into a URB
++
++   // Check if buffer is full
++   if ( atomic_read( &pAutoPM->mURBListLen ) >= txQueueLength)
++   {
++      DBG( "not scheduling request, buffer is full\n" );
++      return NETDEV_TX_BUSY;
++   }
++
++   // Allocate URBListEntry
++   pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
++   if (pURBListEntry == NULL)
++   {
++      DBG( "unable to allocate URBList memory\n" );
++      return NETDEV_TX_BUSY;
++   }
++   pURBListEntry->mpNext = NULL;
++
++   // Allocate URB
++   pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC );
++   if (pURBListEntry->mpURB == NULL)
++   {
++      DBG( "unable to allocate URB\n" );
++      // release all memory allocated by now 
++      if (pURBListEntry)
++         kfree( pURBListEntry );
++      return NETDEV_TX_BUSY;
++   }
++
++#if 1 //def DATA_MODE_RP
++   GobiNetDriverTxFixup(pDev, pSKB, GFP_ATOMIC);	
++#endif
++
++   // Allocate URB transfer_buffer
++   pURBData = kmalloc( pSKB->len, GFP_ATOMIC );
++   if (pURBData == NULL)
++   {
++      DBG( "unable to allocate URB data\n" );
++      // release all memory allocated by now
++      if (pURBListEntry)
++      {
++         usb_free_urb( pURBListEntry->mpURB );
++         kfree( pURBListEntry );
++      }
++      return NETDEV_TX_BUSY;
++   }
++   // Fill with SKB's data
++   memcpy( pURBData, pSKB->data, pSKB->len );
++
++   usb_fill_bulk_urb( pURBListEntry->mpURB,
++                      pGobiDev->mpNetDev->udev,
++                      pGobiDev->mpNetDev->out,
++                      pURBData,
++                      pSKB->len,
++                      GobiUSBNetURBCallback,
++                      pAutoPM );
++
++   /* Handle the need to send a zero length packet and release the
++    * transfer buffer
++    */
++    pURBListEntry->mpURB->transfer_flags |= (URB_ZERO_PACKET | URB_FREE_BUFFER);
++
++   // Aquire lock on URBList
++   spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
++   
++   // Add URB to end of list
++   ppURBListEnd = &pAutoPM->mpURBList;
++   while ((*ppURBListEnd) != NULL)
++   {
++      ppURBListEnd = &(*ppURBListEnd)->mpNext;
++   }
++   *ppURBListEnd = pURBListEntry;
++   atomic_inc( &pAutoPM->mURBListLen );
++
++   spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
++
++   complete( &pAutoPM->mThreadDoWork );
++
++   // Start transfer timer
++   pNet->trans_start = jiffies;
++   // Free SKB
++   if (pSKB)
++      dev_kfree_skb_any( pSKB );
++
++   return NETDEV_TX_OK;
++}
++#endif
++static int (*local_usbnet_start_xmit) (struct sk_buff *skb, struct net_device *net);
++#endif
++
++static int GobiUSBNetStartXmit2( struct sk_buff *pSKB, struct net_device *pNet ){
++   struct sGobiUSBNet * pGobiDev;
++   struct usbnet * pDev = netdev_priv( pNet );
++   
++   //DBG( "\n" );
++   
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get usbnet device\n" );
++      return NETDEV_TX_BUSY;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return NETDEV_TX_BUSY;
++   }
++   
++   if( NULL == pSKB )
++   {
++       DBG( "Buffer is NULL \n" );
++       return NETDEV_TX_BUSY;
++   }
++
++   if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
++   {
++      // Should not happen
++      DBG( "device is suspended\n" );
++      dump_stack();
++      return NETDEV_TX_BUSY;
++   }
++   
++   if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION ))
++   {
++      //netif_carrier_off( pGobiDev->mpNetDev->net );
++      //DBG( "device is disconnected\n" );
++      //dump_stack();
++      return NETDEV_TX_BUSY;
++   }
++
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++   return local_usbnet_start_xmit(pSKB, pNet);
++#else
++   return usbnet_start_xmit(pSKB, pNet);
++#endif
++}
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetOpen (Public Method)
++
++DESCRIPTION:
++   Wrapper to usbnet_open, correctly handling autosuspend
++   Start AutoPM thread (if CONFIG_PM is defined)
++
++PARAMETERS
++   pNet     [ I ] - Pointer to net device
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int GobiUSBNetOpen( struct net_device * pNet )
++{
++   int status = 0;
++   struct sGobiUSBNet * pGobiDev;
++   struct usbnet * pDev = netdev_priv( pNet );
++ 
++   if (pDev == NULL)
++   {
++      DBG( "failed to get usbnet device\n" );
++      return -ENXIO;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return -ENXIO;
++   }
++
++   DBG( "\n" );
++
++#ifdef CONFIG_PM
++   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++   // Start the AutoPM thread
++   pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf;
++   pGobiDev->mAutoPM.mbExit = false;
++   pGobiDev->mAutoPM.mpURBList = NULL;
++   pGobiDev->mAutoPM.mpActiveURB = NULL;
++   spin_lock_init( &pGobiDev->mAutoPM.mURBListLock );
++   spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock );
++   atomic_set( &pGobiDev->mAutoPM.mURBListLen, 0 );
++   init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
++   
++   pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread, 
++                                               &pGobiDev->mAutoPM, 
++                                               "GobiUSBNetAutoPMThread" );
++   if (IS_ERR( pGobiDev->mAutoPM.mpThread ))
++   {
++      DBG( "AutoPM thread creation error\n" );
++      return PTR_ERR( pGobiDev->mAutoPM.mpThread );
++   }
++   #endif
++#endif /* CONFIG_PM */
++
++   // Allow traffic
++   GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED );
++
++   // Pass to usbnet_open if defined
++   if (pGobiDev->mpUSBNetOpen != NULL)
++   {
++      status = pGobiDev->mpUSBNetOpen( pNet );
++#ifdef CONFIG_PM
++      // If usbnet_open was successful enable Auto PM
++      if (status == 0)
++      {
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
++         usb_autopm_enable( pGobiDev->mpIntf );
++#else
++         usb_autopm_put_interface( pGobiDev->mpIntf );
++#endif
++      }
++#endif /* CONFIG_PM */
++   }
++   else
++   {
++      DBG( "no USBNetOpen defined\n" );
++   }
++   
++   return status;
++}
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetStop (Public Method)
++
++DESCRIPTION:
++   Wrapper to usbnet_stop, correctly handling autosuspend
++   Stop AutoPM thread (if CONFIG_PM is defined)
++
++PARAMETERS
++   pNet     [ I ] - Pointer to net device
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int GobiUSBNetStop( struct net_device * pNet )
++{
++   struct sGobiUSBNet * pGobiDev;
++   struct usbnet * pDev = netdev_priv( pNet );
++
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get netdevice\n" );
++      return -ENXIO;
++   }
++   
++   pGobiDev = (sGobiUSBNet *)pDev->data[0];
++   if (pGobiDev == NULL)
++   {
++      DBG( "failed to get QMIDevice\n" );
++      return -ENXIO;
++   }
++
++   // Stop traffic
++   GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
++
++#ifdef CONFIG_PM
++   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++   // Tell traffic thread to exit
++   pGobiDev->mAutoPM.mbExit = true;
++   complete( &pGobiDev->mAutoPM.mThreadDoWork );
++   
++   // Wait for it to exit
++   while( pGobiDev->mAutoPM.mpThread != NULL )
++   {
++      msleep( 100 );
++   }
++   DBG( "thread stopped\n" );
++   #endif
++#endif /* CONFIG_PM */
++
++   // Pass to usbnet_stop, if defined
++   if (pGobiDev->mpUSBNetStop != NULL)
++   {
++      return pGobiDev->mpUSBNetStop( pNet );
++   }
++   else
++   {
++      return 0;
++   }
++}
++
++/*=========================================================================*/
++// Struct driver_info
++/*=========================================================================*/
++static const struct driver_info GobiNetInfo = 
++{
++   .description   = "GobiNet Ethernet Device",
++#ifdef CONFIG_ANDROID
++   .flags         = FLAG_ETHER | FLAG_POINTTOPOINT, //usb0
++#else
++   .flags         = FLAG_ETHER,
++#endif
++   .bind          = GobiNetDriverBind,
++   .unbind        = GobiNetDriverUnbind,
++#if 1 //def DATA_MODE_RP
++#ifdef QUECTEL_WWAN_MULTI_PACKAGES
++   .rx_fixup      = GobiNetDriverRxPktsFixup,
++#else
++   .rx_fixup      = GobiNetDriverRxFixup,
++#endif
++   .tx_fixup      = GobiNetDriverTxFixup,
++#endif
++   .data          = (1 << 4),
++};
++
++/*=========================================================================*/
++// Qualcomm Gobi 3000 VID/PIDs
++/*=========================================================================*/
++#define GOBI_FIXED_INTF(vend, prod) \
++    { \
++          USB_DEVICE( vend, prod ), \
++          .driver_info = (unsigned long)&GobiNetInfo, \
++    }
++static const struct usb_device_id GobiVIDPIDTable [] =
++{
++    GOBI_FIXED_INTF( 0x05c6, 0x9003 ), // Quectel UC20
++    GOBI_FIXED_INTF( 0x05c6, 0x9215 ), // Quectel EC20
++    GOBI_FIXED_INTF( 0x2c7c, 0x0125 ),  // Quectel EC25
++    GOBI_FIXED_INTF( 0x2c7c, 0x0121 ), // Quectel EC21
++    GOBI_FIXED_INTF( 0x2c7c, 0x0306 ), // Quectel EP06
++    GOBI_FIXED_INTF( 0x2c7c, 0x0435 ), // Quectel AG35
++    GOBI_FIXED_INTF( 0x2c7c, 0x0296 ), // Quectel BG96
++    GOBI_FIXED_INTF( 0x2c7c, 0x0191 ), // Quectel EG91
++    GOBI_FIXED_INTF( 0x2c7c, 0x0195 ), // Quectel EG95	
++   //Terminating entry
++   { }
++};
++
++MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetProbe (Public Method)
++
++DESCRIPTION:
++   Run usbnet_probe
++   Setup QMI device
++
++PARAMETERS
++   pIntf        [ I ] - Pointer to interface
++   pVIDPIDs     [ I ] - Pointer to VID/PID table
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int GobiUSBNetProbe( 
++   struct usb_interface *        pIntf, 
++   const struct usb_device_id *  pVIDPIDs )
++{
++   int status;
++   struct usbnet * pDev;
++   sGobiUSBNet * pGobiDev;
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
++   struct net_device_ops * pNetDevOps;
++#endif   
++
++   status = usbnet_probe( pIntf, pVIDPIDs );
++   if (status < 0)
++   {
++      DBG( "usbnet_probe failed %d\n", status );
++	  return status; 
++   }
++
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 ))
++   pIntf->needs_remote_wakeup = 1;
++#endif
++
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
++   pDev = usb_get_intfdata( pIntf );
++#else
++   pDev = (struct usbnet *)pIntf->dev.platform_data;
++#endif
++
++   if (pDev == NULL || pDev->net == NULL)
++   {
++      DBG( "failed to get netdevice\n" );
++      usbnet_disconnect( pIntf );
++      return -ENXIO;
++   }
++
++   pGobiDev = kzalloc( sizeof( sGobiUSBNet ), GFP_KERNEL );
++   if (pGobiDev == NULL)
++   {
++      DBG( "falied to allocate device buffers" );
++      usbnet_disconnect( pIntf );
++      return -ENOMEM;
++   }
++   
++   atomic_set(&pGobiDev->refcount, 1);
++
++   pDev->data[0] = (unsigned long)pGobiDev;
++   
++   pGobiDev->mpNetDev = pDev;
++
++   // Clearing endpoint halt is a magic handshake that brings 
++   // the device out of low power (airplane) mode
++   usb_clear_halt( pGobiDev->mpNetDev->udev, pDev->out );
++
++   // Overload PM related network functions
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++   pGobiDev->mpUSBNetOpen = pDev->net->open;
++   pDev->net->open = GobiUSBNetOpen;
++   pGobiDev->mpUSBNetStop = pDev->net->stop;
++   pDev->net->stop = GobiUSBNetStop;
++#if defined(CONFIG_PM) && (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
++   pDev->net->hard_start_xmit = GobiUSBNetStartXmit;
++   pDev->net->tx_timeout = GobiUSBNetTXTimeout;
++#else  //quectel donot send dhcp request before ndis connect for uc20
++    local_usbnet_start_xmit = pDev->net->hard_start_xmit;
++    pDev->net->hard_start_xmit = GobiUSBNetStartXmit2;
++#endif
++#else
++   pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL );
++   if (pNetDevOps == NULL)
++   {
++      DBG( "falied to allocate net device ops" );
++      usbnet_disconnect( pIntf );
++      return -ENOMEM;
++   }
++   memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) );
++   
++   pGobiDev->mpUSBNetOpen = pNetDevOps->ndo_open;
++   pNetDevOps->ndo_open = GobiUSBNetOpen;
++   pGobiDev->mpUSBNetStop = pNetDevOps->ndo_stop;
++   pNetDevOps->ndo_stop = GobiUSBNetStop;
++#if 1 //quectel donot send dhcp request before ndis connect for uc20
++   pNetDevOps->ndo_start_xmit = GobiUSBNetStartXmit2;
++#else
++   pNetDevOps->ndo_start_xmit = usbnet_start_xmit;
++#endif
++   pNetDevOps->ndo_tx_timeout = usbnet_tx_timeout;
++
++   pDev->net->netdev_ops = pNetDevOps;
++#endif
++
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
++   memset( &(pGobiDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) );
++#else
++   memset( &(pGobiDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) );
++#endif
++
++   pGobiDev->mpIntf = pIntf;
++   memset( &(pGobiDev->mMEID), '0', 14 );
++   
++   DBG( "Mac Address:\n" );
++   PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 );
++
++   pGobiDev->mbQMIValid = false;
++   memset( &pGobiDev->mQMIDev, 0, sizeof( sQMIDev ) );
++   pGobiDev->mQMIDev.mbCdevIsInitialized = false;
++
++   pGobiDev->mQMIDev.mpDevClass = gpClass;
++   
++#ifdef CONFIG_PM
++   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++   init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
++   #endif
++#endif /* CONFIG_PM */
++   spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock );
++
++   // Default to device down
++   pGobiDev->mDownReason = 0;
++
++//#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,11,0 ))
++   GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
++   GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
++//#endif
++
++   // Register QMI
++   pGobiDev->mbMdm9x07 |= (pDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c));
++   pGobiDev->mbRawIPMode = pGobiDev->mbMdm9x07;
++#ifdef CONFIG_BRIDGE
++   memcpy(pGobiDev->mHostMAC, pDev->net->dev_addr, 6);
++#endif
++   status = RegisterQMIDevice( pGobiDev );
++   if (status != 0)
++   {
++      // usbnet_disconnect() will call GobiNetDriverUnbind() which will call
++      // DeregisterQMIDevice() to clean up any partially created QMI device
++      usbnet_disconnect( pIntf );
++      return status;
++   }
++   
++   // Success
++   return 0;
++}
++
++static struct usb_driver GobiNet =
++{
++   .name       = "GobiNet",
++   .id_table   = GobiVIDPIDTable,
++   .probe      = GobiUSBNetProbe,
++   .disconnect = usbnet_disconnect,
++#ifdef CONFIG_PM
++   .suspend    = GobiNetSuspend,
++   .resume     = GobiNetResume,
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))   
++   .supports_autosuspend = true,
++#endif   
++#endif /* CONFIG_PM */
++};
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetModInit (Public Method)
++
++DESCRIPTION:
++   Initialize module
++   Create device class
++   Register out usb_driver struct
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++static int __init GobiUSBNetModInit( void )
++{
++   gpClass = class_create( THIS_MODULE, "GobiQMI" );
++   if (IS_ERR( gpClass ) == true)
++   {
++      DBG( "error at class_create %ld\n",
++           PTR_ERR( gpClass ) );
++      return -ENOMEM;
++   }
++
++   // This will be shown whenever driver is loaded
++   printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
++
++   return usb_register( &GobiNet );
++}
++module_init( GobiUSBNetModInit );
++
++/*===========================================================================
++METHOD:
++   GobiUSBNetModExit (Public Method)
++
++DESCRIPTION:
++   Deregister module
++   Destroy device class
++
++RETURN VALUE:
++   void
++===========================================================================*/
++static void __exit GobiUSBNetModExit( void )
++{
++   usb_deregister( &GobiNet );
++
++   class_destroy( gpClass );
++}
++module_exit( GobiUSBNetModExit );
++
++MODULE_VERSION( DRIVER_VERSION );
++MODULE_AUTHOR( DRIVER_AUTHOR );
++MODULE_DESCRIPTION( DRIVER_DESC );
++MODULE_LICENSE("Dual BSD/GPL");
++ 
++#ifdef bool
++#undef bool
++#endif
++
++module_param( debug, int, S_IRUGO | S_IWUSR );
++MODULE_PARM_DESC( debug, "Debuging enabled or not" );
++
++module_param( interruptible, int, S_IRUGO | S_IWUSR );
++MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" );
++module_param( txQueueLength, int, S_IRUGO | S_IWUSR );
++MODULE_PARM_DESC( txQueueLength, 
++                  "Number of IP packets which may be queued up for transmit" );
++
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/Kconfig linux-at91-loragw/drivers/net/usb/ec20/Kconfig
+--- linux-at91/drivers/net/usb/ec20/Kconfig	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/Kconfig	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,13 @@
++#
++# Quectel EC20 GobiNet driver configuration
++# Add by guowenxue 2020.12.4
++# Reference to:  <<Quectel_WCDMA&LTE_Linux&Android_GobiNet_Driver_V1.3.0.pdf>>
++#                 Chapter 3.4 and 5.3
++#
++config EC20_GOBINET
++        bool "Quectel EC20 Cat4 module GobiNet driver"
++        depends on USB_USBNET
++        default y
++        help
++          Say Y here if you have Quectel EC20 Cat4 4G module and wanna use GobiNet driver here.
++          If unsure, say N.
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/Makefile linux-at91-loragw/drivers/net/usb/ec20/Makefile
+--- linux-at91/drivers/net/usb/ec20/Makefile	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/Makefile	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,4 @@
++
++obj-$(CONFIG_EC20_GOBINET)   += GobiNet.o
++GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o
++
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMI.c linux-at91-loragw/drivers/net/usb/ec20/QMI.c
+--- linux-at91/drivers/net/usb/ec20/QMI.c	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/QMI.c	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,1386 @@
++/*===========================================================================
++FILE:
++   QMI.c
++
++DESCRIPTION:
++   Qualcomm QMI driver code
++   
++FUNCTIONS:
++   Generic QMUX functions
++      ParseQMUX
++      FillQMUX
++   
++   Generic QMI functions
++      GetTLV
++      ValidQMIMessage
++      GetQMIMessageID
++
++   Fill Buffers with QMI requests
++      QMICTLGetClientIDReq
++      QMICTLReleaseClientIDReq
++      QMICTLReadyReq
++      QMIWDSSetEventReportReq
++      QMIWDSGetPKGSRVCStatusReq
++      QMIDMSGetMEIDReq
++      QMIWDASetDataFormatReq
++      QMICTLSetDataFormatReq
++      QMICTLSyncReq
++      
++   Parse data from QMI responses
++      QMICTLGetClientIDResp
++      QMICTLReleaseClientIDResp
++      QMIWDSEventResp
++      QMIDMSGetMEIDResp
++      QMIWDASetDataFormatResp
++      QMICTLSyncResp
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++//---------------------------------------------------------------------------
++// Include Files
++//---------------------------------------------------------------------------
++#include <asm/unaligned.h>
++#include <linux/kernel.h>
++#include "Structs.h"
++#include "QMI.h"
++
++/*=========================================================================*/
++// Get sizes of buffers needed by QMI requests
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   QMUXHeaderSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMUXHeaderSize( void )
++{
++   return sizeof( sQMUX );
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLGetClientIDReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMICTLGetClientIDReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMICTLGetClientIDReqSize( void )
++{
++   return sizeof( sQMUX ) + 10;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLReleaseClientIDReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq
++ 
++RETURN VALUE:
++   u16 - size of header
++===========================================================================*/
++u16 QMICTLReleaseClientIDReqSize( void )
++{
++   return sizeof( sQMUX ) + 11;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLReadyReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMICTLReadyReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMICTLReadyReqSize( void )
++{
++   return sizeof( sQMUX ) + 6;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSSetEventReportReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMIWDSSetEventReportReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMIWDSSetEventReportReqSize( void )
++{
++   return sizeof( sQMUX ) + 15;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSGetPKGSRVCStatusReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMIWDSGetPKGSRVCStatusReqSize( void )
++{
++   return sizeof( sQMUX ) + 7;
++}
++
++u16 QMIWDSSetQMUXBindMuxDataPortSize( void )
++{
++   return sizeof( sQMUX ) + 29;
++}
++
++/*===========================================================================
++METHOD:
++   QMIDMSGetMEIDReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMIDMSGetMEIDReqSize( void )
++{
++   return sizeof( sQMUX ) + 7;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDASetDataFormatReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
++
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16 QMIWDASetDataFormatReqSize( void )
++{
++   return sizeof( sQMUX ) + 25;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLSetDataFormatReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMICTLSetDataFormatReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16  QMICTLSetDataFormatReqSize( void )
++{
++   return sizeof( sQMUX ) + 15; 
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLSyncReqSize (Public Method)
++
++DESCRIPTION:
++   Get size of buffer needed for QMUX + QMICTLSyncReq
++ 
++RETURN VALUE:
++   u16 - size of buffer
++===========================================================================*/
++u16  QMICTLSyncReqSize( void )
++{
++   return sizeof( sQMUX ) + 6; 
++}
++
++/*=========================================================================*/
++// Generic QMUX functions
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   ParseQMUX (Public Method)
++
++DESCRIPTION:
++   Remove QMUX headers from a buffer
++
++PARAMETERS
++   pClientID       [ O ] - On success, will point to Client ID
++   pBuffer         [ I ] - Full Message passed in
++   buffSize        [ I ] - Size of pBuffer
++
++RETURN VALUE:
++   int - Positive for size of QMUX header
++         Negative errno for error
++===========================================================================*/
++int ParseQMUX(
++   u16 *    pClientID,
++   void *   pBuffer,
++   u16      buffSize )
++{
++   sQMUX * pQMUXHeader;
++   
++   if (pBuffer == 0 || buffSize < 12)
++   {
++      return -ENOMEM;
++   }
++
++   // QMUX Header
++   pQMUXHeader = (sQMUX *)pBuffer;
++
++   if (pQMUXHeader->mTF != 1
++   ||  le16_to_cpu(get_unaligned(&pQMUXHeader->mLength)) != buffSize - 1
++   ||  pQMUXHeader->mCtrlFlag != 0x80 )
++   {
++      return -EINVAL;
++   }
++
++   // Client ID   
++   *pClientID = (pQMUXHeader->mQMIClientID << 8) + pQMUXHeader->mQMIService;
++   
++   return sizeof( sQMUX );
++}
++
++/*===========================================================================
++METHOD:
++   FillQMUX (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMUX headers
++
++PARAMETERS
++   clientID        [ I ] - Client ID
++   pBuffer         [ O ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer (must be at least 6)
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int FillQMUX(
++   u16      clientID,
++   void *   pBuffer,
++   u16      buffSize )
++{
++   sQMUX * pQMUXHeader;
++
++   if (pBuffer == 0 ||  buffSize < sizeof( sQMUX ))
++   {
++      return -ENOMEM;
++   }
++
++   // QMUX Header
++   pQMUXHeader = (sQMUX *)pBuffer;
++
++   pQMUXHeader->mTF = 1;
++   put_unaligned(cpu_to_le16(buffSize - 1), &pQMUXHeader->mLength);
++   //DBG("pQMUXHeader->mLength = 0x%x, buffSize - 1 = 0x%x\n",pQMUXHeader->mLength, buffSize - 1);
++   pQMUXHeader->mCtrlFlag = 0;
++
++   // Service and Client ID   
++   pQMUXHeader->mQMIService = clientID & 0xff;
++   pQMUXHeader->mQMIClientID = clientID >> 8;
++
++   return 0;
++}
++
++/*=========================================================================*/
++// Generic QMI functions
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   GetTLV (Public Method)
++
++DESCRIPTION:
++   Get data buffer of a specified TLV from a QMI message
++
++   QMI Message shall NOT include SDU
++   
++PARAMETERS
++   pQMIMessage    [ I ] - QMI Message buffer
++   messageLen     [ I ] - Size of QMI Message buffer
++   type           [ I ] - Desired Type
++   pOutDataBuf    [ O ] - Buffer to be filled with TLV
++   messageLen     [ I ] - Size of QMI Message buffer
++
++RETURN VALUE:
++   u16 - Size of TLV for success
++         Negative errno for error
++===========================================================================*/
++int GetTLV(
++   void *   pQMIMessage,
++   u16      messageLen,
++   u8       type,
++   void *   pOutDataBuf,
++   u16      bufferLen )
++{
++   u16 pos;
++   u16 tlvSize = 0;
++   u16 cpyCount;
++   
++   if (pQMIMessage == 0 || pOutDataBuf == 0)
++   {
++      return -ENOMEM;
++   }   
++   
++   for (pos = 4; 
++        pos + 3 < messageLen; 
++        pos += tlvSize + 3)
++   {
++      tlvSize = le16_to_cpu( get_unaligned(((u16 *)(pQMIMessage + pos + 1) )) );
++      if (*(u8 *)(pQMIMessage + pos) == type)
++      {
++         if (bufferLen < tlvSize)
++         {
++            return -ENOMEM;
++         }
++                
++         for (cpyCount = 0; cpyCount < tlvSize; cpyCount++)
++         {
++            *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount));
++         }
++         
++         return tlvSize;
++      }
++   }
++   
++   return -ENOMSG;
++}
++
++/*===========================================================================
++METHOD:
++   ValidQMIMessage (Public Method)
++
++DESCRIPTION:
++   Check mandatory TLV in a QMI message
++
++   QMI Message shall NOT include SDU
++
++PARAMETERS
++   pQMIMessage    [ I ] - QMI Message buffer
++   messageLen     [ I ] - Size of QMI Message buffer
++
++RETURN VALUE:
++   int - 0 for success (no error)
++         Negative errno for error
++         Positive for QMI error code
++===========================================================================*/
++int ValidQMIMessage(
++   void *   pQMIMessage,
++   u16      messageLen )
++{
++   char mandTLV[4];
++
++   if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4)
++   {
++      // Found TLV
++      if (*(u16 *)&mandTLV[0] != 0)
++      {
++         return le16_to_cpu( get_unaligned(&mandTLV[2]) );
++      }
++      else
++      {
++         return 0;
++      }
++   }
++   else
++   {
++      return -ENOMSG;
++   }
++}      
++
++/*===========================================================================
++METHOD:
++   GetQMIMessageID (Public Method)
++
++DESCRIPTION:
++   Get the message ID of a QMI message
++   
++   QMI Message shall NOT include SDU
++
++PARAMETERS
++   pQMIMessage    [ I ] - QMI Message buffer
++   messageLen     [ I ] - Size of QMI Message buffer
++
++RETURN VALUE:
++   int - Positive for message ID
++         Negative errno for error
++===========================================================================*/
++int GetQMIMessageID(
++   void *   pQMIMessage,
++   u16      messageLen )
++{
++   if (messageLen < 2)
++   {
++      return -ENODATA;
++   }
++   else
++   {
++      return le16_to_cpu( get_unaligned((u16 *)pQMIMessage) );
++   }
++}
++
++/*=========================================================================*/
++// Fill Buffers with QMI requests
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   QMICTLGetClientIDReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI CTL Get Client ID Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++   serviceType     [ I ] - Service type requested
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMICTLGetClientIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID,
++   u8       serviceType )
++{
++   if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI CTL GET CLIENT ID
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00;
++   // Transaction ID
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
++   // Message ID
++   put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
++   // Size of TLV's
++   put_unaligned(cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
++      // QMI Service Type
++      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
++      // Size
++   put_unaligned(cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
++      // QMI svc type
++      *(u8 *)(pBuffer + sizeof( sQMUX ) + 9)  = serviceType;
++
++   // success
++   return sizeof( sQMUX ) + 10;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLReleaseClientIDReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI CTL Release Client ID Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++   clientID        [ I ] - Service type requested
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMICTLReleaseClientIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID,
++   u16      clientID )
++{
++   if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   DBG(  "buffSize: 0x%x, transactionID: 0x%x, clientID: 0x%x,\n",
++         buffSize, transactionID, clientID );
++
++   // QMI CTL RELEASE CLIENT ID REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID;
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0023), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
++   // Size of TLV's
++   put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
++      // Release client ID
++      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
++      // Size
++   put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
++      // QMI svs type / Client ID
++   put_unaligned(cpu_to_le16(clientID), (u16 *)(pBuffer + sizeof( sQMUX ) + 9));
++      
++   // success
++   return sizeof( sQMUX ) + 11;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLReadyReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI CTL Get Version Info Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMICTLReadyReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   DBG("buffSize: 0x%x, transactionID: 0x%x\n", buffSize, transactionID);
++
++   // QMI CTL GET VERSION INFO REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0021), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
++   // Size of TLV's
++   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
++
++   // success
++   return sizeof( sQMUX ) + 6;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSSetEventReportReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI WDS Set Event Report Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMIWDSSetEventReportReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI WDS SET EVENT REPORT REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
++   // Size of TLV's
++   put_unaligned(cpu_to_le16(0x0008), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
++      // Report channel rate TLV
++      *(u8 *)(pBuffer + sizeof( sQMUX ) + 7)  = 0x11;
++      // Size
++   put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
++      // Stats period
++      *(u8 *)(pBuffer + sizeof( sQMUX ) + 10)  = 0x01;
++      // Stats mask
++   put_unaligned( cpu_to_le32(0x000000ff), (u32 *)(pBuffer + sizeof( sQMUX ) + 11) );
++
++   // success
++   return sizeof( sQMUX ) + 15;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSGetPKGSRVCStatusReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI WDS Get PKG SRVC Status Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMIWDSGetPKGSRVCStatusReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI WDS Get PKG SRVC Status REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
++   // Message ID
++   put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
++   // Size of TLV's
++   put_unaligned(cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
++
++   // success
++   return sizeof( sQMUX ) + 7;
++}
++
++u16 QMIWDSSetQMUXBindMuxDataPortReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMIWDSSetQMUXBindMuxDataPortSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI WDS Set QMUX Bind Mux Data Port REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
++   // Message ID
++   put_unaligned(cpu_to_le16(0x00a2), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
++   // Size of TLV's
++   put_unaligned(cpu_to_le16(0x0016), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
++
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  7) = 0x10;
++   put_unaligned(cpu_to_le16(0x08), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
++   put_unaligned(cpu_to_le32(0x02), (u32 *)(pBuffer + sizeof( sQMUX ) + 10)); // ep_type
++   put_unaligned(cpu_to_le32(0x04), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); // iface_id
++   
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  18) = 0x11;
++   put_unaligned(cpu_to_le16(0x01), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  21) = 0x81;  // MuxId
++   
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  22) = 0x13;
++   put_unaligned(cpu_to_le16(0x04), (u16 *)(pBuffer + sizeof( sQMUX ) + 23));
++   put_unaligned(cpu_to_le32(0x01), (u32 *)(pBuffer + sizeof( sQMUX ) + 25));
++       
++   // success
++   return sizeof( sQMUX ) + 29;
++}
++
++/*===========================================================================
++METHOD:
++   QMIDMSGetMEIDReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI DMS Get Serial Numbers Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMIDMSGetMEIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI DMS GET SERIAL NUMBERS REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0025), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
++   // Size of TLV's
++   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
++
++   // success
++   return sizeof( sQMUX ) + 7;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDASetDataFormatReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI WDA Set Data Format Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMIWDASetDataFormatReq(
++   void *   pBuffer,
++   u16      buffSize,
++   bool     bRawIPMode,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // QMI WDA SET DATA FORMAT REQ
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++
++   // Transaction ID
++   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
++
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0020), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
++
++   // Size of TLV's
++   put_unaligned( cpu_to_le16(0x0012), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
++
++   /* TLVType QOS Data Format 1 byte  */
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  7) = 0x10; // type data format
++
++   /* TLVLength  2 bytes - see spec */
++   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); 
++
++   /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
++#ifdef QOS_MODE
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 1; /* QOS header */
++#else
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0; /* no-QOS header */
++#endif
++
++   /* TLVType Link-Layer Protocol  (Optional) 1 byte */
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x11;
++
++   /* TLVLength 2 bytes */
++   put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 12));
++
++   /* LinkProt: 0x1 - ETH; 0x2 - rawIP  4 bytes */
++if (bRawIPMode) { //#ifdef DATA_MODE_RP
++   /* Set RawIP mode */
++   put_unaligned( cpu_to_le32(0x00000002), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
++   DBG("Request RawIP Data Format\n");
++} else { //#else
++   /* Set Ethernet  mode */
++   put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
++   DBG("Request Ethernet Data Format\n");
++} //#endif
++
++   /* TLVType Uplink Data Aggression Protocol - 1 byte */
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x13;
++
++   /* TLVLength 2 bytes */
++   put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
++
++   /* TLV Data */
++   put_unaligned( cpu_to_le32(0x00000000), (u32 *)(pBuffer + sizeof( sQMUX ) + 21));
++
++   // success
++   return QMIWDASetDataFormatReqSize();
++}
++
++
++
++/*===========================================================================
++METHOD:
++   QMICTLSetDataFormatReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI CTL Set Data Format Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMICTLSetDataFormatReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMICTLSetDataFormatReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   /* QMI CTL Set Data Format Request */
++   /* Request */
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00; // QMICTL_FLAG_REQUEST
++   
++   /* Transaction ID 1 byte */
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
++
++   /* QMICTLType  2 bytes */
++   put_unaligned( cpu_to_le16(0x0026), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
++
++   /* Length  2 bytes  of 2 TLVs  each - see spec */
++   put_unaligned( cpu_to_le16(0x0009), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
++
++   /* TLVType Data Format (Mandatory)  1 byte  */
++   *(u8 *)(pBuffer + sizeof( sQMUX ) +  6) = 0x01; // type data format
++
++   /* TLVLength  2 bytes - see spec */
++   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7)); 
++
++   /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
++#ifdef QOS_MODE
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 1; /* QOS header */
++#else
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 0; /* no-QOS header */
++#endif
++
++    /* TLVType Link-Layer Protocol  (Optional) 1 byte */
++    *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = TLV_TYPE_LINK_PROTO;
++
++    /* TLVLength 2 bytes */
++    put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 11));
++
++   /* LinkProt: 0x1 - ETH; 0x2 - rawIP  2 bytes */
++#ifdef DATA_MODE_RP
++   /* Set RawIP mode */
++   put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
++   DBG("Request RawIP Data Format\n");
++#else
++   /* Set Ethernet  mode */
++   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
++   DBG("Request Ethernet Data Format\n");
++#endif
++
++   /* success */
++   return sizeof( sQMUX ) + 15;
++
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLSyncReq (Public Method)
++
++DESCRIPTION:
++   Fill buffer with QMI CTL Sync Request
++
++PARAMETERS
++   pBuffer         [ 0 ] - Buffer to be filled
++   buffSize        [ I ] - Size of pBuffer
++   transactionID   [ I ] - Transaction ID
++
++RETURN VALUE:
++   int - Positive for resulting size of pBuffer
++         Negative errno for error
++===========================================================================*/
++int QMICTLSyncReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID )
++{
++   if (pBuffer == 0 || buffSize < QMICTLSyncReqSize() )
++   {
++      return -ENOMEM;
++   }
++
++   // Request
++   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
++   // Transaction ID
++   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
++   // Message ID
++   put_unaligned( cpu_to_le16(0x0027), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
++   // Size of TLV's
++   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
++
++  // success
++  return sizeof( sQMUX ) + 6;
++}
++
++/*=========================================================================*/
++// Parse data from QMI responses
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   QMICTLGetClientIDResp (Public Method)
++
++DESCRIPTION:
++   Parse the QMI CTL Get Client ID Resp
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++   pClientID       [ 0 ] - Recieved client ID
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMICTLGetClientIDResp(
++   void * pBuffer,
++   u16    buffSize,
++   u16 *  pClientID )
++{
++   int result;
++   
++   // Ignore QMUX and SDU
++   //    QMI CTL SDU is 2 bytes, not 3
++   u8 offset = sizeof( sQMUX ) + 2;
++
++   if (pBuffer == 0 || buffSize < offset)
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   if (result != 0x22)
++   {
++      return -EFAULT;
++   }
++
++   result = ValidQMIMessage( pBuffer, buffSize );
++   if (result != 0)
++   {
++      return -EFAULT;
++   }
++
++   result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 );
++   if (result != 2)
++   {
++      return -EFAULT;
++   }
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLReleaseClientIDResp (Public Method)
++
++DESCRIPTION:
++   Verify the QMI CTL Release Client ID Resp is valid
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMICTLReleaseClientIDResp(
++   void *   pBuffer,
++   u16      buffSize )
++{
++   int result;
++   
++   // Ignore QMUX and SDU
++   //    QMI CTL SDU is 2 bytes, not 3
++   u8 offset = sizeof( sQMUX ) + 2;
++
++   if (pBuffer == 0 || buffSize < offset)
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   if (result != 0x23)
++   {
++      return -EFAULT;
++   }
++
++   result = ValidQMIMessage( pBuffer, buffSize );
++   if (result != 0)
++   {
++      return -EFAULT;
++   }
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSEventResp (Public Method)
++
++DESCRIPTION:
++   Parse the QMI WDS Set Event Report Resp/Indication or
++      QMI WDS Get PKG SRVC Status Resp/Indication
++
++   Return parameters will only be updated if value was received
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++   pTXOk           [ O ] - Number of transmitted packets without errors
++   pRXOk           [ O ] - Number of recieved packets without errors
++   pTXErr          [ O ] - Number of transmitted packets with framing errors
++   pRXErr          [ O ] - Number of recieved packets with framing errors
++   pTXOfl          [ O ] - Number of transmitted packets dropped due to overflow
++   pRXOfl          [ O ] - Number of recieved packets dropped due to overflow
++   pTXBytesOk      [ O ] - Number of transmitted bytes without errors
++   pRXBytesOk      [ O ] - Number of recieved bytes without errors
++   pbLinkState     [ 0 ] - Is the link active?
++   pbReconfigure   [ 0 ] - Must interface be reconfigured? (reset IP address)
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMIWDSEventResp(
++   void *   pBuffer,
++   u16      buffSize,
++   u32 *    pTXOk,
++   u32 *    pRXOk,
++   u32 *    pTXErr,
++   u32 *    pRXErr,
++   u32 *    pTXOfl,
++   u32 *    pRXOfl,
++   u64 *    pTXBytesOk,
++   u64 *    pRXBytesOk,
++   bool *   pbLinkState,
++   bool *   pbReconfigure )
++{
++   int result;
++   u8 pktStatusRead[2];
++
++   // Ignore QMUX and SDU
++   u8 offset = sizeof( sQMUX ) + 3;
++
++   if (pBuffer == 0 
++   || buffSize < offset
++   || pTXOk == 0
++   || pRXOk == 0
++   || pTXErr == 0
++   || pRXErr == 0
++   || pTXOfl == 0
++   || pRXOfl == 0
++   || pTXBytesOk == 0
++   || pRXBytesOk == 0
++   || pbLinkState == 0
++   || pbReconfigure == 0 )
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   // Note: Indications.  No Mandatory TLV required
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   // QMI WDS Set Event Report Resp
++   if (result == 0x01)
++   {
++      // TLV's are not mandatory
++      GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 );
++      put_unaligned( le32_to_cpu(*pTXOk), pTXOk);
++      GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 );
++      put_unaligned( le32_to_cpu(*pRXOk), pRXOk);
++      GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 );
++      put_unaligned( le32_to_cpu(*pTXErr), pTXErr);
++      GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 );
++      put_unaligned( le32_to_cpu(*pRXErr), pRXErr);
++      GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 );
++      put_unaligned( le32_to_cpu(*pTXOfl), pTXOfl);
++      GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 );
++      put_unaligned( le32_to_cpu(*pRXOfl), pRXOfl);
++      GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 );
++      put_unaligned( le64_to_cpu(*pTXBytesOk), pTXBytesOk);
++      GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 );
++      put_unaligned( le64_to_cpu(*pRXBytesOk), pRXBytesOk);
++   }
++   // QMI WDS Get PKG SRVC Status Resp
++   else if (result == 0x22)
++   {
++      result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 );
++      // 1 or 2 bytes may be received
++      if (result >= 1)
++      {
++         if (pktStatusRead[0] == 0x02)
++         {
++            *pbLinkState = true;
++         }
++         else
++         {
++            *pbLinkState = false;
++         }
++      }
++      if (result == 2)
++      {
++         if (pktStatusRead[1] == 0x01)
++         {
++            *pbReconfigure = true;
++         }
++         else
++         {
++            *pbReconfigure = false;
++         }
++      }
++      
++      if (result < 0)
++      {
++         return result;
++      }
++   }
++   else
++   {
++      return -EFAULT;
++   }
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMIDMSGetMEIDResp (Public Method)
++
++DESCRIPTION:
++   Parse the QMI DMS Get Serial Numbers Resp
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++   pMEID           [ O ] - Device MEID
++   meidSize        [ I ] - Size of MEID buffer (at least 14)
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMIDMSGetMEIDResp(
++   void *   pBuffer,
++   u16      buffSize,
++   char *   pMEID,
++   int      meidSize )
++{
++   int result;
++
++   // Ignore QMUX and SDU
++   u8 offset = sizeof( sQMUX ) + 3;
++
++   if (pBuffer == 0 || buffSize < offset || meidSize < 14)
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   if (result != 0x25)
++   {
++      return -EFAULT;
++   }
++
++   result = ValidQMIMessage( pBuffer, buffSize );
++   if (result != 0)
++   {
++      return -EFAULT;
++   }
++
++   result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 );
++   if (result != 14)
++   {
++      return -EFAULT;
++   }
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDASetDataFormatResp (Public Method)
++
++DESCRIPTION:
++   Parse the QMI WDA Set Data Format Response
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMIWDASetDataFormatResp(
++   void *   pBuffer,
++   u16      buffSize, bool bRawIPMode )
++{
++
++   int result;
++
++   u8 pktLinkProtocol[4];
++
++   // Ignore QMUX and SDU
++   // QMI SDU is 3 bytes
++   u8 offset = sizeof( sQMUX ) + 3;
++
++   if (pBuffer == 0 || buffSize < offset)
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   if (result != 0x20)
++   {
++      return -EFAULT;
++   }
++
++   /* Check response message result TLV */
++   result = ValidQMIMessage( pBuffer, buffSize );
++   if (result != 0)
++   {
++      DBG("EFAULT: Data Format Mode Bad Response\n"); 
++//      return -EFAULT;
++      return 0;
++   }
++
++   /* Check response message link protocol */
++   result = GetTLV( pBuffer, buffSize, 0x11,
++                     &pktLinkProtocol[0], 4);
++   if (result != 4)
++   {
++      DBG("EFAULT: Wrong TLV format\n"); 
++      return 0;
++      
++   }
++
++if (bRawIPMode) { ////#ifdef DATA_MODE_RP
++   if (pktLinkProtocol[0] != 2)
++   {
++      DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n"); 
++      return pktLinkProtocol[0];
++   }
++   DBG("Data Format Set to RawIP\n");
++} else { ////#else
++   if (pktLinkProtocol[0] != 1)
++   {
++      DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n"); 
++      return pktLinkProtocol[0];
++   }
++   DBG("Data Format Set to Ethernet Mode \n");
++} //#endif
++
++   return pktLinkProtocol[0];
++}
++
++/*===========================================================================
++METHOD:
++   QMICTLSyncResp (Public Method)
++
++DESCRIPTION:
++   Validate the QMI CTL Sync Response
++
++PARAMETERS
++   pBuffer         [ I ] - Buffer to be parsed
++   buffSize        [ I ] - Size of pBuffer
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for error
++===========================================================================*/
++int QMICTLSyncResp(
++   void *pBuffer,
++   u16  buffSize )
++{
++   int result;
++
++   // Ignore QMUX (2 bytes for QMI CTL) and SDU
++   u8 offset = sizeof( sQMUX ) + 2;
++
++   if (pBuffer == 0 || buffSize < offset)
++   {
++      return -ENOMEM;
++   }
++
++   pBuffer = pBuffer + offset;
++   buffSize -= offset;
++
++   result = GetQMIMessageID( pBuffer, buffSize );
++   if (result != 0x27)
++   {
++      return -EFAULT;
++   }
++
++   result = ValidQMIMessage( pBuffer, buffSize );
++
++   return result;
++}
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMIDevice.c linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.c
+--- linux-at91/drivers/net/usb/ec20/QMIDevice.c	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.c	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,4096 @@
++/*===========================================================================
++FILE:
++   QMIDevice.c
++
++DESCRIPTION:
++   Functions related to the QMI interface device
++   
++FUNCTIONS:
++   Generic functions
++      IsDeviceValid
++      PrintHex
++      GobiSetDownReason
++      GobiClearDownReason
++      GobiTestDownReason
++
++   Driver level asynchronous read functions
++      ResubmitIntURB
++      ReadCallback
++      IntCallback
++      StartRead
++      KillRead
++
++   Internal read/write functions
++      ReadAsync
++      UpSem
++      ReadSync
++      WriteSyncCallback
++      WriteSync
++
++   Internal memory management functions
++      GetClientID
++      ReleaseClientID
++      FindClientMem
++      AddToReadMemList
++      PopFromReadMemList
++      AddToNotifyList
++      NotifyAndPopNotifyList
++      AddToURBList
++      PopFromURBList
++
++   Internal userspace wrapper functions
++      UserspaceunlockedIOCTL
++
++   Userspace wrappers
++      UserspaceOpen
++      UserspaceIOCTL
++      UserspaceClose
++      UserspaceRead
++      UserspaceWrite
++      UserspacePoll
++
++   Initializer and destructor
++      RegisterQMIDevice
++      DeregisterQMIDevice
++
++   Driver level client management
++      QMIReady
++      QMIWDSCallback
++      SetupQMIWDSCallback
++      QMIDMSGetMEID
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++//---------------------------------------------------------------------------
++// Include Files
++//---------------------------------------------------------------------------
++#include <asm/unaligned.h>
++#include "QMIDevice.h"
++#include <linux/module.h>
++
++#if (__GNUC__ > 7 && defined(_ASM_X86_ATOMIC_H))|| \
++        (__GNUC__ == 7 && defined(_ASM_X86_ATOMIC_H) && (__GNUC_MINOR__ > 0 || \
++            (__GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ > 0)))
++#define gobi_atomic_read(x) atomic_read((const atomic_t *)x)
++#else
++#define gobi_atomic_read(x) atomic_read((atomic_t *)x)
++#endif
++
++//-----------------------------------------------------------------------------
++// Definitions
++//-----------------------------------------------------------------------------
++
++extern int debug;
++extern int interruptible;
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
++static int s_interval;
++#endif
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 ))
++#include <linux/devfs_fs_kernel.h>
++static char devfs_name[32];
++int device_create(struct class *class,  struct device *parent, dev_t devt, const char *fmt, ...)
++{
++   va_list vargs;
++   struct class_device *class_dev;
++   int err;
++
++   va_start(vargs, fmt);
++   vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs);
++   va_end(vargs);
++
++   class_dev = class_device_create(class, devt, parent, "%s", devfs_name);
++   if (IS_ERR(class_dev)) {
++      err = PTR_ERR(class_dev);
++      goto out;
++   }
++
++   err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name);
++   if (err) {
++      class_device_destroy(class, devt);
++      goto out;
++   }
++
++   return 0;
++   
++out:
++   return err;   
++}
++
++void device_destroy(struct class *class, dev_t devt)
++{
++   class_device_destroy(class, devt);
++   devfs_remove(devfs_name);
++}
++#endif
++
++#ifdef CONFIG_PM
++// Prototype to GobiNetSuspend function
++int GobiNetSuspend(
++   struct usb_interface *     pIntf,
++   pm_message_t               powerEvent );
++#endif /* CONFIG_PM */
++
++// IOCTL to generate a client ID for this service type
++#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
++
++// IOCTL to get the VIDPID of the device
++#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
++
++// IOCTL to get the MEID of the device
++#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
++
++#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL  (0x8BE0 + 4)
++
++// CDC GET_ENCAPSULATED_RESPONSE packet
++#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll
++#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll
++/* The following masks filter the common part of the encapsulated response
++ * packet value for Gobi and QMI devices, ie. ignore usb interface number
++ */
++#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll
++#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll
++
++const int i = 1;
++#define is_bigendian() ( (*(char*)&i) == 0 )
++#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\
++{\
++   *pcdcrsp  = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \
++                          : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \
++   *pmask = is_bigendian() ? CDC_RSP_MASK_BE \
++                           : CDC_RSP_MASK_LE; \
++}
++
++// CDC CONNECTION_SPEED_CHANGE indication packet
++#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll
++#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll
++/* The following masks filter the common part of the connection speed change
++ * packet value for Gobi and QMI devices
++ */
++#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll
++#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll
++#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\
++{\
++   *pcdccscp  = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \
++                          : CDC_CONNECTION_SPEED_CHANGE_LE ; \
++   *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \
++                           : CDC_CONNSPD_MASK_LE; \
++}
++
++#define SET_CONTROL_LINE_STATE_REQUEST_TYPE        0x21
++#define SET_CONTROL_LINE_STATE_REQUEST             0x22
++#define CONTROL_DTR                     0x01
++#define CONTROL_RTS                     0x02
++
++/*=========================================================================*/
++// UserspaceQMIFops
++//    QMI device's userspace file operations
++/*=========================================================================*/
++struct file_operations UserspaceQMIFops = 
++{
++   .owner     = THIS_MODULE,
++   .read      = UserspaceRead,
++   .write     = UserspaceWrite,
++#ifdef CONFIG_COMPAT
++   .compat_ioctl = UserspaceunlockedIOCTL,
++#endif
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 ))
++   .unlocked_ioctl = UserspaceunlockedIOCTL,
++#else
++   .ioctl     = UserspaceIOCTL,
++#endif
++   .open      = UserspaceOpen,
++   .flush     = UserspaceClose,
++   .poll      = UserspacePoll,
++};
++
++/*=========================================================================*/
++// Generic functions
++/*=========================================================================*/
++u8 QMIXactionIDGet( sGobiUSBNet *pDev)
++{
++   u8 transactionID;
++
++   if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) )
++   {
++      transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
++   }
++   
++   return transactionID;
++}
++
++static struct usb_endpoint_descriptor *GetEndpoint(
++    struct usb_interface *pintf,
++    int type,
++    int dir )
++{
++   int i;
++   struct usb_host_interface *iface = pintf->cur_altsetting;
++   struct usb_endpoint_descriptor *pendp;
++
++   for( i = 0; i < iface->desc.bNumEndpoints; i++)
++   {
++      pendp = &iface->endpoint[i].desc;
++      if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir)
++          &&
++          (usb_endpoint_type(pendp) == type) )
++      {
++         return pendp;
++      }
++   }
++
++   return NULL;
++}
++
++/*===========================================================================
++METHOD:
++   IsDeviceValid (Public Method)
++
++DESCRIPTION:
++   Basic test to see if device memory is valid
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool IsDeviceValid( sGobiUSBNet * pDev )
++{
++   if (pDev == NULL)
++   {
++      return false;
++   }
++
++   if (pDev->mbQMIValid == false)
++   {
++      return false;
++   }
++   
++   return true;
++} 
++
++/*===========================================================================
++METHOD:
++   PrintHex (Public Method)
++
++DESCRIPTION:
++   Print Hex data, for debug purposes
++
++PARAMETERS:
++   pBuffer       [ I ] - Data buffer
++   bufSize       [ I ] - Size of data buffer
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void PrintHex(
++   void *      pBuffer,
++   u16         bufSize )
++{
++   char * pPrintBuf;
++   u16 pos;
++   int status;
++   
++   if (debug != 1)
++   {
++       return;
++   }
++
++   pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC );
++   if (pPrintBuf == NULL)
++   {
++      DBG( "Unable to allocate buffer\n" );
++      return;
++   }
++   memset( pPrintBuf, 0 , bufSize * 3 + 1 );
++   
++   for (pos = 0; pos < bufSize; pos++)
++   {
++      status = snprintf( (pPrintBuf + (pos * 3)), 
++                         4, 
++                         "%02X ", 
++                         *(u8 *)(pBuffer + pos) );
++      if (status != 3)
++      {
++         DBG( "snprintf error %d\n", status );
++         kfree( pPrintBuf );
++         return;
++      }
++   }
++   
++   DBG( "   : %s\n", pPrintBuf );
++
++   kfree( pPrintBuf );
++   pPrintBuf = NULL;
++   return;   
++}
++
++/*===========================================================================
++METHOD:
++   GobiSetDownReason (Public Method)
++
++DESCRIPTION:
++   Sets mDownReason and turns carrier off
++
++PARAMETERS
++   pDev     [ I ] - Device specific memory
++   reason   [ I ] - Reason device is down
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void GobiSetDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason )
++{
++   set_bit( reason, &pDev->mDownReason );
++   DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
++   
++   netif_carrier_off( pDev->mpNetDev->net );
++}
++
++/*===========================================================================
++METHOD:
++   GobiClearDownReason (Public Method)
++
++DESCRIPTION:
++   Clear mDownReason and may turn carrier on
++
++PARAMETERS
++   pDev     [ I ] - Device specific memory
++   reason   [ I ] - Reason device is no longer down
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void GobiClearDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason )
++{
++   clear_bit( reason, &pDev->mDownReason );
++   
++   DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
++#if 0 //(LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 ))
++    netif_carrier_on( pDev->mpNetDev->net );
++#else
++   if (pDev->mDownReason == 0)
++   {
++      netif_carrier_on( pDev->mpNetDev->net );
++   }
++#endif
++}
++
++/*===========================================================================
++METHOD:
++   GobiTestDownReason (Public Method)
++
++DESCRIPTION:
++   Test mDownReason and returns whether reason is set
++
++PARAMETERS
++   pDev     [ I ] - Device specific memory
++   reason   [ I ] - Reason device is down
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool GobiTestDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason )
++{
++   return test_bit( reason, &pDev->mDownReason );
++}
++
++/*=========================================================================*/
++// Driver level asynchronous read functions
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   ResubmitIntURB (Public Method)
++
++DESCRIPTION:
++   Resubmit interrupt URB, re-using same values
++
++PARAMETERS
++   pIntURB       [ I ] - Interrupt URB 
++
++RETURN VALUE:
++   int - 0 for success
++         negative errno for failure
++===========================================================================*/
++int ResubmitIntURB( struct urb * pIntURB )
++{
++   int status;
++   int interval;
++
++   // Sanity test
++   if ( (pIntURB == NULL)
++   ||   (pIntURB->dev == NULL) )
++   {
++      return -EINVAL;
++   }
++ 
++   // Interval needs reset after every URB completion
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
++    interval = max((int)(pIntURB->ep->desc.bInterval),
++                  (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3);
++#else
++    interval = s_interval;
++#endif
++
++   // Reschedule interrupt URB
++   usb_fill_int_urb( pIntURB,
++                     pIntURB->dev,
++                     pIntURB->pipe,
++                     pIntURB->transfer_buffer,
++                     pIntURB->transfer_buffer_length,
++                     pIntURB->complete,
++                     pIntURB->context,
++                     interval );
++   status = usb_submit_urb( pIntURB, GFP_ATOMIC );
++   if (status != 0)
++   {
++      DBG( "Error re-submitting Int URB %d\n", status );
++   }
++
++   return status;
++}
++
++/*===========================================================================
++METHOD:
++   ReadCallback (Public Method)
++
++DESCRIPTION:
++   Put the data in storage and notify anyone waiting for data
++
++PARAMETERS
++   pReadURB       [ I ] - URB this callback is run for
++
++RETURN VALUE:
++   None
++===========================================================================*/
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void ReadCallback( struct urb * pReadURB )
++#else
++void ReadCallback(struct urb *pReadURB, struct pt_regs *regs)
++#endif
++{
++   int result;
++   u16 clientID;
++   sClientMemList * pClientMem;
++   void * pData;
++   void * pDataCopy;
++   u16 dataSize;
++   sGobiUSBNet * pDev;
++   unsigned long flags;
++   u16 transactionID;
++
++   if (pReadURB == NULL)
++   {
++      DBG( "bad read URB\n" );
++      return;
++   }
++   
++   pDev = pReadURB->context;
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return;
++   }   
++
++#ifdef READ_QMI_URB_ERROR
++   del_timer(&pDev->mQMIDev.mReadUrbTimer);
++   if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0))
++      pReadURB->status = 0;
++#endif
++
++   if (pReadURB->status != 0)
++   {
++      DBG( "Read status = %d\n", pReadURB->status );
++
++      // Resubmit the interrupt URB
++      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++
++      return;
++   }
++   DBG( "Read %d bytes\n", pReadURB->actual_length );
++   
++   pData = pReadURB->transfer_buffer;
++   dataSize = pReadURB->actual_length;
++
++   PrintHex( pData, dataSize );
++
++#ifdef READ_QMI_URB_ERROR
++   if (dataSize < (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1)) {
++      dataSize = (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1);
++      memset(pReadURB->transfer_buffer + pReadURB->actual_length, 0x00, dataSize - pReadURB->actual_length);
++      INFO( "Read %d / %d bytes\n", pReadURB->actual_length, dataSize);
++   }
++#endif
++
++   result = ParseQMUX( &clientID,
++                       pData,
++                       dataSize );
++   if (result < 0)
++   {
++      DBG( "Read error parsing QMUX %d\n", result );
++
++      // Resubmit the interrupt URB
++      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++
++      return;
++   }
++   
++   // Grab transaction ID
++
++   // Data large enough?
++   if (dataSize < result + 3)
++   {
++      DBG( "Data buffer too small to parse\n" );
++
++      // Resubmit the interrupt URB
++      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++
++      return;
++   }
++   
++   // Transaction ID size is 1 for QMICTL, 2 for others
++   if (clientID == QMICTL)
++   {
++      transactionID = *(u8*)(pData + result + 1);
++   }
++   else
++   {
++      transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) );
++   }
++   
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Find memory storage for this service and Client ID
++   // Not using FindClientMem because it can't handle broadcasts
++   pClientMem = pDev->mQMIDev.mpClientMemList;
++
++   while (pClientMem != NULL)
++   {
++      if (pClientMem->mClientID == clientID 
++      ||  (pClientMem->mClientID | 0xff00) == clientID)
++      {
++         // Make copy of pData
++         pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
++         if (pDataCopy == NULL)
++         {
++            DBG( "Error allocating client data memory\n" );
++
++            // End critical section
++            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++            // Resubmit the interrupt URB
++            ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++
++            return;             
++         }
++
++         memcpy( pDataCopy, pData, dataSize );
++
++         if (AddToReadMemList( pDev,
++                               pClientMem->mClientID,
++                               transactionID,
++                               pDataCopy,
++                               dataSize ) == false)
++         {
++            DBG( "Error allocating pReadMemListEntry "
++                 "read will be discarded\n" );
++            kfree( pDataCopy );
++            
++            // End critical section
++            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++            // Resubmit the interrupt URB
++            ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++
++            return;
++         }
++
++         // Success
++         VDBG( "Creating new readListEntry for client 0x%04X, TID %x\n",
++              clientID,
++              transactionID );
++
++         // Notify this client data exists
++         NotifyAndPopNotifyList( pDev,
++                                 pClientMem->mClientID,
++                                 transactionID );
++
++         // Possibly notify poll() that data exists
++         wake_up_interruptible_sync( &pClientMem->mWaitQueue );
++
++         // Not a broadcast
++         if (clientID >> 8 != 0xff)
++         {
++            break;
++         }
++      }
++      
++      // Next element
++      pClientMem = pClientMem->mpNext;
++   }
++   
++   // End critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++   
++   // Resubmit the interrupt URB
++   ResubmitIntURB( pDev->mQMIDev.mpIntURB );
++}
++
++/*===========================================================================
++METHOD:
++   IntCallback (Public Method)
++
++DESCRIPTION:
++   Data is available, fire off a read URB
++
++PARAMETERS
++   pIntURB       [ I ] - URB this callback is run for
++
++RETURN VALUE:
++   None
++===========================================================================*/
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void IntCallback( struct urb * pIntURB )
++{
++#else
++void IntCallback(struct urb *pIntURB, struct pt_regs *regs)
++{
++#endif
++   int status;
++   u64 CDCEncResp;
++   u64 CDCEncRespMask;
++   
++   sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return;
++   }
++
++   // Verify this was a normal interrupt
++   if (pIntURB->status != 0)
++   {
++        DBG( "IntCallback: Int status = %d\n", pIntURB->status );
++      
++      // Ignore EOVERFLOW errors
++      if (pIntURB->status != -EOVERFLOW)
++      {
++         // Read 'thread' dies here
++         return;
++      }
++   }
++   else
++   {
++      //TODO cast transfer_buffer to struct usb_cdc_notification
++      
++      // CDC GET_ENCAPSULATED_RESPONSE
++      CDC_GET_ENCAPSULATED_RESPONSE(&CDCEncResp, &CDCEncRespMask)
++
++      VDBG( "IntCallback: Encapsulated Response = 0x%llx\n",
++          (*(u64*)pIntURB->transfer_buffer));
++
++      if ((pIntURB->actual_length == 8)
++      &&  ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
++
++      {
++         // Time to read
++         usb_fill_control_urb( pDev->mQMIDev.mpReadURB,
++                               pDev->mpNetDev->udev,
++                               usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ),
++                               (unsigned char *)pDev->mQMIDev.mpReadSetupPacket,
++                               pDev->mQMIDev.mpReadBuffer,
++                               DEFAULT_READ_URB_LENGTH,
++                               ReadCallback,
++                               pDev );
++#ifdef READ_QMI_URB_ERROR
++         mod_timer( &pDev->mQMIDev.mReadUrbTimer, jiffies + msecs_to_jiffies(300) );
++#endif
++         status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
++         if (status != 0)
++         {
++            DBG( "Error submitting Read URB %d\n", status );
++
++            // Resubmit the interrupt urb
++            ResubmitIntURB( pIntURB );
++            return;
++         }
++
++         // Int URB will be resubmitted during ReadCallback
++         return;
++      }
++      // CDC CONNECTION_SPEED_CHANGE
++      else if ((pIntURB->actual_length == 16)
++           &&  (CDC_GET_CONNECTION_SPEED_CHANGE(&CDCEncResp, &CDCEncRespMask))
++           &&  ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
++      {
++         DBG( "IntCallback: Connection Speed Change = 0x%llx\n",
++              (*(u64*)pIntURB->transfer_buffer));
++
++         // if upstream or downstream is 0, stop traffic.  Otherwise resume it
++         if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
++         ||  (*(u32*)(pIntURB->transfer_buffer + 12) == 0))
++         {
++            GobiSetDownReason( pDev, CDC_CONNECTION_SPEED );
++            DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
++         }
++         else
++         {
++            GobiClearDownReason( pDev, CDC_CONNECTION_SPEED );
++            DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" );
++         }
++      }
++      else
++      {
++         DBG( "ignoring invalid interrupt in packet\n" );
++         PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
++      }
++   }
++
++   // Resubmit the interrupt urb
++   ResubmitIntURB( pIntURB );
++
++   return;
++}
++
++#ifdef READ_QMI_URB_ERROR
++static void ReadUrbTimerFunc( struct urb * pReadURB )
++{
++  int result;
++
++  INFO( "%s called (%ld).\n", __func__, jiffies );
++
++  if ((pReadURB != NULL) && (pReadURB->status == -EINPROGRESS))
++  {
++     // Asynchronously unlink URB. On success, -EINPROGRESS will be returned, 
++     // URB status will be set to -ECONNRESET, and ReadCallback() executed
++     result = usb_unlink_urb( pReadURB );
++     INFO( "%s called usb_unlink_urb, result = %d\n", __func__, result);
++  }
++}
++#endif
++
++/*===========================================================================
++METHOD:
++   StartRead (Public Method)
++
++DESCRIPTION:
++   Start continuous read "thread" (callback driven)
++
++   Note: In case of error, KillRead() should be run
++         to remove urbs and clean up memory.
++   
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   int - 0 for success
++         negative errno for failure
++===========================================================================*/
++int StartRead( sGobiUSBNet * pDev )
++{
++   int interval;
++   struct usb_endpoint_descriptor *pendp;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return -ENXIO;
++   }
++
++   // Allocate URB buffers
++   pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL );
++   if (pDev->mQMIDev.mpReadURB == NULL)
++   {
++      DBG( "Error allocating read urb\n" );
++      return -ENOMEM;
++   }
++
++#ifdef READ_QMI_URB_ERROR
++   setup_timer( &pDev->mQMIDev.mReadUrbTimer, (void*)ReadUrbTimerFunc, (unsigned long)pDev->mQMIDev.mpReadURB );
++#endif
++
++   pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL );
++   if (pDev->mQMIDev.mpIntURB == NULL)
++   {
++      DBG( "Error allocating int urb\n" );
++      usb_free_urb( pDev->mQMIDev.mpReadURB );
++      pDev->mQMIDev.mpReadURB = NULL;
++      return -ENOMEM;
++   }
++
++   // Create data buffers
++   pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
++   if (pDev->mQMIDev.mpReadBuffer == NULL)
++   {
++      DBG( "Error allocating read buffer\n" );
++      usb_free_urb( pDev->mQMIDev.mpIntURB );
++      pDev->mQMIDev.mpIntURB = NULL;
++      usb_free_urb( pDev->mQMIDev.mpReadURB );
++      pDev->mQMIDev.mpReadURB = NULL;
++      return -ENOMEM;
++   }
++   
++   pDev->mQMIDev.mpIntBuffer = kmalloc( 64, GFP_KERNEL );
++   if (pDev->mQMIDev.mpIntBuffer == NULL)
++   {
++      DBG( "Error allocating int buffer\n" );
++      kfree( pDev->mQMIDev.mpReadBuffer );
++      pDev->mQMIDev.mpReadBuffer = NULL;
++      usb_free_urb( pDev->mQMIDev.mpIntURB );
++      pDev->mQMIDev.mpIntURB = NULL;
++      usb_free_urb( pDev->mQMIDev.mpReadURB );
++      pDev->mQMIDev.mpReadURB = NULL;
++      return -ENOMEM;
++   }      
++   
++   pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), 
++                                              GFP_KERNEL );
++   if (pDev->mQMIDev.mpReadSetupPacket == NULL)
++   {
++      DBG( "Error allocating setup packet buffer\n" );
++      kfree( pDev->mQMIDev.mpIntBuffer );
++      pDev->mQMIDev.mpIntBuffer = NULL;
++      kfree( pDev->mQMIDev.mpReadBuffer );
++      pDev->mQMIDev.mpReadBuffer = NULL;
++      usb_free_urb( pDev->mQMIDev.mpIntURB );
++      pDev->mQMIDev.mpIntURB = NULL;
++      usb_free_urb( pDev->mQMIDev.mpReadURB );
++      pDev->mQMIDev.mpReadURB = NULL;
++      return -ENOMEM;
++   }
++
++   // CDC Get Encapsulated Response packet
++   pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1;
++   pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1;
++   pDev->mQMIDev.mpReadSetupPacket->mValue = 0;
++   pDev->mQMIDev.mpReadSetupPacket->mIndex =
++      cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);  /* interface number */
++   pDev->mQMIDev.mpReadSetupPacket->mLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
++
++   pendp = GetEndpoint(pDev->mpIntf, USB_ENDPOINT_XFER_INT, USB_DIR_IN);
++   if (pendp == NULL)
++   {
++      DBG( "Invalid interrupt endpoint!\n" );
++      kfree(pDev->mQMIDev.mpReadSetupPacket);
++      pDev->mQMIDev.mpReadSetupPacket = NULL;
++      kfree( pDev->mQMIDev.mpIntBuffer );
++      pDev->mQMIDev.mpIntBuffer = NULL;
++      kfree( pDev->mQMIDev.mpReadBuffer );
++      pDev->mQMIDev.mpReadBuffer = NULL;
++      usb_free_urb( pDev->mQMIDev.mpIntURB );
++      pDev->mQMIDev.mpIntURB = NULL;
++      usb_free_urb( pDev->mQMIDev.mpReadURB );
++      pDev->mQMIDev.mpReadURB = NULL;
++      return -ENXIO;
++   }
++
++   // Interval needs reset after every URB completion
++   interval = max((int)(pendp->bInterval),
++                  (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
++    s_interval = interval;
++#endif
++   
++   // Schedule interrupt URB
++   usb_fill_int_urb( pDev->mQMIDev.mpIntURB,
++                     pDev->mpNetDev->udev,
++                     /* QMI interrupt endpoint for the following
++                      * interface configuration: DM, NMEA, MDM, NET
++                      */
++                     usb_rcvintpipe( pDev->mpNetDev->udev,
++                                     pendp->bEndpointAddress),
++                     pDev->mQMIDev.mpIntBuffer,
++                     min((int)le16_to_cpu(pendp->wMaxPacketSize), 64),
++                     IntCallback,
++                     pDev,
++                     interval );
++   return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL );
++}
++
++/*===========================================================================
++METHOD:
++   KillRead (Public Method)
++
++DESCRIPTION:
++   Kill continuous read "thread"
++   
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void KillRead( sGobiUSBNet * pDev )
++{
++   // Stop reading
++   if (pDev->mQMIDev.mpReadURB != NULL)
++   {
++      DBG( "Killng read URB\n" );
++      usb_kill_urb( pDev->mQMIDev.mpReadURB );
++   }
++
++   if (pDev->mQMIDev.mpIntURB != NULL)
++   {
++      DBG( "Killng int URB\n" );
++      usb_kill_urb( pDev->mQMIDev.mpIntURB );
++   }
++
++   // Release buffers
++   kfree( pDev->mQMIDev.mpReadSetupPacket );
++   pDev->mQMIDev.mpReadSetupPacket = NULL;
++   kfree( pDev->mQMIDev.mpReadBuffer );
++   pDev->mQMIDev.mpReadBuffer = NULL;
++   kfree( pDev->mQMIDev.mpIntBuffer );
++   pDev->mQMIDev.mpIntBuffer = NULL;
++   
++   // Release URB's
++   usb_free_urb( pDev->mQMIDev.mpReadURB );
++   pDev->mQMIDev.mpReadURB = NULL;
++   usb_free_urb( pDev->mQMIDev.mpIntURB );
++   pDev->mQMIDev.mpIntURB = NULL;
++}
++
++/*=========================================================================*/
++// Internal read/write functions
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   ReadAsync (Public Method)
++
++DESCRIPTION:
++   Start asynchronous read
++   NOTE: Reading client's data store, not device
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   transactionID     [ I ] - Transaction ID or 0 for any
++   pCallback         [ I ] - Callback to be executed when data is available
++   pData             [ I ] - Data buffer that willl be passed (unmodified) 
++                             to callback
++
++RETURN VALUE:
++   int - 0 for success
++         negative errno for failure
++===========================================================================*/
++int ReadAsync(
++   sGobiUSBNet *      pDev,
++   u16                clientID,
++   u16                transactionID,
++   void               (*pCallback)(sGobiUSBNet*, u16, void *),
++   void *             pData )
++{
++   sClientMemList * pClientMem;
++   sReadMemList ** ppReadMemList;
++   
++   unsigned long flags;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return -ENXIO;
++   }
++
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Find memory storage for this client ID
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find matching client ID 0x%04X\n",
++           clientID );
++           
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      return -ENXIO;
++   }
++   
++   ppReadMemList = &(pClientMem->mpList);
++   
++   // Does data already exist?
++   while (*ppReadMemList != NULL)
++   {
++      // Is this element our data?
++      if (transactionID == 0 
++      ||  transactionID == (*ppReadMemList)->mTransactionID)
++      {
++         // End critical section
++         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++         // Run our own callback
++         pCallback( pDev, clientID, pData );
++         
++         return 0;
++      }
++      
++      // Next
++      ppReadMemList = &(*ppReadMemList)->mpNext;
++   }
++
++   // Data not found, add ourself to list of waiters
++   if (AddToNotifyList( pDev,
++                        clientID,
++                        transactionID, 
++                        pCallback, 
++                        pData ) == false)
++   {
++      DBG( "Unable to register for notification\n" );
++   }
++
++   // End critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Success
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   UpSem (Public Method)
++
++DESCRIPTION:
++   Notification function for synchronous read
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   pData             [ I ] - Buffer that holds semaphore to be up()-ed
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void UpSem( 
++   sGobiUSBNet * pDev,
++   u16             clientID,
++   void *          pData )
++{
++   VDBG( "0x%04X\n", clientID );
++        
++   up( (struct semaphore *)pData );
++   return;
++}
++
++/*===========================================================================
++METHOD:
++   ReadSync (Public Method)
++
++DESCRIPTION:
++   Start synchronous read
++   NOTE: Reading client's data store, not device
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   ppOutBuffer       [I/O] - On success, will be filled with a 
++                             pointer to read buffer
++   clientID          [ I ] - Requester's client ID
++   transactionID     [ I ] - Transaction ID or 0 for any
++
++RETURN VALUE:
++   int - size of data read for success
++         negative errno for failure
++===========================================================================*/
++int ReadSync(
++   sGobiUSBNet *    pDev,
++   void **            ppOutBuffer,
++   u16                clientID,
++   u16                transactionID )
++{
++   int result;
++   sClientMemList * pClientMem;
++   sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
++   struct semaphore readSem;
++   void * pData;
++   unsigned long flags;
++   u16 dataSize;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return -ENXIO;
++   }
++   
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Find memory storage for this Client ID
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find matching client ID 0x%04X\n",
++           clientID );
++      
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      return -ENXIO;
++   }
++   
++   // Note: in cases where read is interrupted, 
++   //    this will verify client is still valid
++   while (PopFromReadMemList( pDev,
++                              clientID,
++                              transactionID,
++                              &pData,
++                              &dataSize ) == false)
++   {
++      // Data does not yet exist, wait
++      sema_init( &readSem, 0 );
++
++      // Add ourself to list of waiters
++      if (AddToNotifyList( pDev, 
++                           clientID, 
++                           transactionID, 
++                           UpSem, 
++                           &readSem ) == false)
++      {
++         DBG( "unable to register for notification\n" );
++         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++         return -EFAULT;
++      }
++
++      // End critical section while we block
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++      // Wait for notification
++      result = down_interruptible( &readSem );
++      if (result != 0)
++      {
++         DBG( "Interrupted %d\n", result );
++
++         // readSem will fall out of scope, 
++         // remove from notify list so it's not referenced
++         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++         ppNotifyList = &(pClientMem->mpReadNotifyList);
++         pDelNotifyListEntry = NULL;
++
++         // Find and delete matching entry
++         while (*ppNotifyList != NULL)
++         {
++            if ((*ppNotifyList)->mpData == &readSem)
++            {
++               pDelNotifyListEntry = *ppNotifyList;
++               *ppNotifyList = (*ppNotifyList)->mpNext;
++               kfree( pDelNotifyListEntry );
++               break;
++            }
++
++            // Next
++            ppNotifyList = &(*ppNotifyList)->mpNext;
++         }
++
++         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++         return -EINTR;
++      }
++      
++      // Verify device is still valid
++      if (IsDeviceValid( pDev ) == false)
++      {
++         DBG( "Invalid device!\n" );
++         return -ENXIO;
++      }
++      
++      // Restart critical section and continue loop
++      spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++   }
++   
++   // End Critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Success
++   *ppOutBuffer = pData;
++
++   return dataSize;
++}
++
++/*===========================================================================
++METHOD:
++   WriteSyncCallback (Public Method)
++
++DESCRIPTION:
++   Write callback
++
++PARAMETERS
++   pWriteURB       [ I ] - URB this callback is run for
++
++RETURN VALUE:
++   None
++===========================================================================*/
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void WriteSyncCallback( struct urb * pWriteURB )
++#else
++void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs)
++#endif
++{
++   if (pWriteURB == NULL)
++   {
++      DBG( "null urb\n" );
++      return;
++   }
++
++   DBG( "Write status/size %d/%d\n", 
++        pWriteURB->status, 
++        pWriteURB->actual_length );
++
++   // Notify that write has completed by up()-ing semeaphore
++   up( (struct semaphore * )pWriteURB->context );
++   
++   return;
++}
++
++/*===========================================================================
++METHOD:
++   WriteSync (Public Method)
++
++DESCRIPTION:
++   Start synchronous write
++
++PARAMETERS:
++   pDev                 [ I ] - Device specific memory
++   pWriteBuffer         [ I ] - Data to be written
++   writeBufferSize      [ I ] - Size of data to be written
++   clientID             [ I ] - Client ID of requester
++
++RETURN VALUE:
++   int - write size (includes QMUX)
++         negative errno for failure
++===========================================================================*/
++int WriteSync(
++   sGobiUSBNet *          pDev,
++   char *                 pWriteBuffer,
++   int                    writeBufferSize,
++   u16                    clientID )
++{
++   int result;
++   struct semaphore writeSem;
++   struct urb * pWriteURB;
++   sURBSetupPacket writeSetup;
++   unsigned long flags;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return -ENXIO;
++   }
++
++   pWriteURB = usb_alloc_urb( 0, GFP_KERNEL );
++   if (pWriteURB == NULL)
++   {
++      DBG( "URB mem error\n" );
++      return -ENOMEM;
++   }
++
++   // Fill writeBuffer with QMUX
++   result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
++   if (result < 0)
++   {
++      usb_free_urb( pWriteURB );
++      return result;
++   }
++
++   // CDC Send Encapsulated Request packet
++   writeSetup.mRequestType = 0x21;
++   writeSetup.mRequestCode = 0;
++   writeSetup.mValue = 0;
++   writeSetup.mIndex = cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);
++   writeSetup.mLength = cpu_to_le16(writeBufferSize);
++
++   // Create URB   
++   usb_fill_control_urb( pWriteURB,
++                         pDev->mpNetDev->udev,
++                         usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
++                         (unsigned char *)&writeSetup,
++                         (void*)pWriteBuffer,
++                         writeBufferSize,
++                         NULL,
++                         pDev );
++
++   DBG( "Actual Write:\n" );
++   PrintHex( pWriteBuffer, writeBufferSize );
++
++   sema_init( &writeSem, 0 );
++   
++   pWriteURB->complete = WriteSyncCallback;
++   pWriteURB->context = &writeSem;
++
++   // Wake device
++   result = usb_autopm_get_interface( pDev->mpIntf );
++   if (result < 0)
++   {
++      DBG( "unable to resume interface: %d\n", result );
++      
++      // Likely caused by device going from autosuspend -> full suspend
++      if (result == -EPERM)
++      {
++#ifdef CONFIG_PM
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++         pDev->mpNetDev->udev->auto_pm = 0;
++#endif
++#endif
++         GobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
++#endif /* CONFIG_PM */
++      }
++      usb_free_urb( pWriteURB );
++
++      return result;
++   }
++
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   if (AddToURBList( pDev, clientID, pWriteURB ) == false)
++   {
++      usb_free_urb( pWriteURB );
++
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
++      usb_autopm_put_interface( pDev->mpIntf );
++      return -EINVAL;
++   }
++
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++   result = usb_submit_urb( pWriteURB, GFP_KERNEL );
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   if (result < 0)
++   {
++      DBG( "submit URB error %d\n", result );
++      
++      // Get URB back so we can destroy it
++      if (PopFromURBList( pDev, clientID ) != pWriteURB)
++      {
++         // This shouldn't happen
++         DBG( "Didn't get write URB back\n" );
++      }
++
++      usb_free_urb( pWriteURB );
++
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      usb_autopm_put_interface( pDev->mpIntf );
++      return result;
++   }
++   
++   // End critical section while we block
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
++
++   // Wait for write to finish
++   if (interruptible != 0)
++   {
++      // Allow user interrupts
++      result = down_interruptible( &writeSem );
++   }
++   else
++   {
++      // Ignore user interrupts
++      result = 0;
++      down( &writeSem );
++   }
++
++   // Write is done, release device
++   usb_autopm_put_interface( pDev->mpIntf );
++
++   // Verify device is still valid
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++
++      usb_free_urb( pWriteURB );
++      return -ENXIO;
++   }
++
++   // Restart critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Get URB back so we can destroy it
++   if (PopFromURBList( pDev, clientID ) != pWriteURB)
++   {
++      // This shouldn't happen
++      DBG( "Didn't get write URB back\n" );
++   
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      usb_free_urb( pWriteURB );
++      return -EINVAL;
++   }
++
++   // End critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
++
++   if (result == 0)
++   {
++      // Write is finished
++      if (pWriteURB->status == 0)
++      {
++         // Return number of bytes that were supposed to have been written,
++         //   not size of QMI request
++         result = writeBufferSize;
++      }
++      else
++      {
++         DBG( "bad status = %d\n", pWriteURB->status );
++         
++         // Return error value
++         result = pWriteURB->status;
++      }
++   }
++   else
++   {
++      // We have been forcibly interrupted
++      DBG( "Interrupted %d !!!\n", result );
++      DBG( "Device may be in bad state and need reset !!!\n" );
++
++      // URB has not finished
++      usb_kill_urb( pWriteURB );
++   }
++
++   usb_free_urb( pWriteURB );
++
++   return result;
++}
++
++/*=========================================================================*/
++// Internal memory management functions
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   GetClientID (Public Method)
++
++DESCRIPTION:
++   Request a QMI client for the input service type and initialize memory
++   structure
++
++PARAMETERS:
++   pDev           [ I ] - Device specific memory
++   serviceType    [ I ] - Desired QMI service type
++
++RETURN VALUE:
++   int - Client ID for success (positive)
++         Negative errno for error
++===========================================================================*/
++int GetClientID( 
++   sGobiUSBNet *      pDev,
++   u8                 serviceType )
++{
++   u16 clientID;
++   sClientMemList ** ppClientMem;
++   int result;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   void * pReadBuffer;
++   u16 readBufferSize;
++   unsigned long flags;
++   u8 transactionID;
++   
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device!\n" );
++      return -ENXIO;
++   }
++
++   // Run QMI request to be asigned a Client ID
++   if (serviceType != 0)
++   {
++      writeBufferSize = QMICTLGetClientIDReqSize();
++      pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++      if (pWriteBuffer == NULL)
++      {
++         return -ENOMEM;
++      }
++
++      transactionID = QMIXactionIDGet( pDev );
++
++      result = QMICTLGetClientIDReq( pWriteBuffer, 
++                                     writeBufferSize,
++                                     transactionID,
++                                     serviceType );
++      if (result < 0)
++      {
++         kfree( pWriteBuffer );
++         return result;
++      }
++
++      
++      result = WriteSync( pDev,
++                          pWriteBuffer,
++                          writeBufferSize,
++                          QMICTL );
++      kfree( pWriteBuffer );
++
++      if (result < 0)
++      {
++         return result;
++      }
++
++      result = ReadSync( pDev,
++                         &pReadBuffer,
++                         QMICTL,
++                         transactionID );
++      if (result < 0)
++      {
++         DBG( "bad read data %d\n", result );
++         return result;
++      }
++      readBufferSize = result;
++
++      result = QMICTLGetClientIDResp( pReadBuffer,
++                                      readBufferSize,
++                                      &clientID );
++
++     /* Upon return from QMICTLGetClientIDResp, clientID
++      * low address contains the Service Number (SN), and
++      * clientID high address contains Client Number (CN)
++      * For the ReadCallback to function correctly,we swap
++      * the SN and CN on a Big Endian architecture.
++      */
++      clientID = le16_to_cpu(clientID);
++
++      kfree( pReadBuffer );
++
++      if (result < 0)
++      {
++         return result;
++      }
++   }
++   else
++   {
++      // QMI CTL will always have client ID 0
++      clientID = 0;
++   }
++
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Verify client is not already allocated
++   if (FindClientMem( pDev, clientID ) != NULL)
++   {
++      DBG( "Client memory already exists\n" );
++
++      // End Critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      return -ETOOMANYREFS;
++   }
++
++   // Go to last entry in client mem list
++   ppClientMem = &pDev->mQMIDev.mpClientMemList;
++   while (*ppClientMem != NULL)
++   {
++      ppClientMem = &(*ppClientMem)->mpNext;
++   }
++   
++   // Create locations for read to place data into
++   *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC );
++   if (*ppClientMem == NULL)
++   {
++      DBG( "Error allocating read list\n" );
++
++      // End critical section
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      return -ENOMEM;
++   }
++      
++   (*ppClientMem)->mClientID = clientID;
++   (*ppClientMem)->mpList = NULL;
++   (*ppClientMem)->mpReadNotifyList = NULL;
++   (*ppClientMem)->mpURBList = NULL;
++   (*ppClientMem)->mpNext = NULL;
++
++   // Initialize workqueue for poll()
++   init_waitqueue_head( &(*ppClientMem)->mWaitQueue );
++
++   // End Critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++   
++   return (int)( (*ppClientMem)->mClientID );
++}
++
++/*===========================================================================
++METHOD:
++   ReleaseClientID (Public Method)
++
++DESCRIPTION:
++   Release QMI client and free memory
++
++PARAMETERS:
++   pDev           [ I ] - Device specific memory
++   clientID       [ I ] - Requester's client ID
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void ReleaseClientID(
++   sGobiUSBNet *    pDev,
++   u16                clientID )
++{
++   int result;
++   sClientMemList ** ppDelClientMem;
++   sClientMemList * pNextClientMem;
++   struct urb * pDelURB;
++   void * pDelData;
++   u16 dataSize;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   void * pReadBuffer;
++   u16 readBufferSize;
++   unsigned long flags;
++   u8 transactionID;
++
++   // Is device is still valid?
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "invalid device\n" );
++      return;
++   }
++   
++   DBG( "releasing 0x%04X\n", clientID );
++
++   // Run QMI ReleaseClientID if this isn't QMICTL   
++   if (clientID != QMICTL)
++   {
++      // Note: all errors are non fatal, as we always want to delete 
++      //    client memory in latter part of function
++      
++      writeBufferSize = QMICTLReleaseClientIDReqSize();
++      pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++      if (pWriteBuffer == NULL)
++      {
++         DBG( "memory error\n" );
++      }
++      else
++      {
++         transactionID = QMIXactionIDGet( pDev );
++
++         result = QMICTLReleaseClientIDReq( pWriteBuffer, 
++                                            writeBufferSize,
++                                            transactionID,
++                                            clientID );
++         if (result < 0)
++         {
++            kfree( pWriteBuffer );
++            DBG( "error %d filling req buffer\n", result );
++         }
++         else
++         {
++            result = WriteSync( pDev,
++                                pWriteBuffer,
++                                writeBufferSize,
++                                QMICTL );
++            kfree( pWriteBuffer );
++
++            if (result < 0)
++            {
++               DBG( "bad write status %d\n", result );
++            }
++            else
++            {
++               result = ReadSync( pDev,
++                                  &pReadBuffer,
++                                  QMICTL,
++                                  transactionID );
++               if (result < 0)
++               {
++                  DBG( "bad read status %d\n", result );
++               }
++               else
++               {
++                  readBufferSize = result;
++
++                  result = QMICTLReleaseClientIDResp( pReadBuffer,
++                                                      readBufferSize );
++                  kfree( pReadBuffer );
++
++                  if (result < 0)
++                  {
++                     DBG( "error %d parsing response\n", result );
++                  }
++               }
++            }
++         }
++      }
++   }
++
++   // Cleaning up client memory
++   
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Can't use FindClientMem, I need to keep pointer of previous
++   ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
++   while (*ppDelClientMem != NULL)
++   {
++      if ((*ppDelClientMem)->mClientID == clientID)
++      {
++         pNextClientMem = (*ppDelClientMem)->mpNext;
++
++         // Notify all clients
++         while (NotifyAndPopNotifyList( pDev,
++                                        clientID,
++                                        0 ) == true );         
++
++         // Kill and free all URB's
++         pDelURB = PopFromURBList( pDev, clientID );
++         while (pDelURB != NULL)
++         {
++            usb_kill_urb( pDelURB );
++            usb_free_urb( pDelURB );
++            pDelURB = PopFromURBList( pDev, clientID );
++         }
++
++         // Free any unread data
++         while (PopFromReadMemList( pDev, 
++                                    clientID,
++                                    0,
++                                    &pDelData,
++                                    &dataSize ) == true )
++         {
++            kfree( pDelData );
++         }
++
++         // Delete client Mem
++         if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue))
++         kfree( *ppDelClientMem );
++         else
++            DBG("memory leak!\n");
++
++         // Overwrite the pointer that was to this client mem
++         *ppDelClientMem = pNextClientMem;
++      }
++      else
++      {
++         // I now point to (a pointer of ((the node I was at)'s mpNext))
++         ppDelClientMem = &(*ppDelClientMem)->mpNext;
++      }
++   }
++   
++   // End Critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++   return;
++}
++
++/*===========================================================================
++METHOD:
++   FindClientMem (Public Method)
++
++DESCRIPTION:
++   Find this client's memory
++
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev           [ I ] - Device specific memory
++   clientID       [ I ] - Requester's client ID
++
++RETURN VALUE:
++   sClientMemList - Pointer to requested sClientMemList for success
++                    NULL for error
++===========================================================================*/
++sClientMemList * FindClientMem( 
++   sGobiUSBNet *      pDev,
++   u16              clientID )
++{
++   sClientMemList * pClientMem;
++   
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return NULL;
++   }
++   
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++   
++   pClientMem = pDev->mQMIDev.mpClientMemList;
++   while (pClientMem != NULL)
++   {
++      if (pClientMem->mClientID == clientID)
++      {
++         // Success
++         VDBG("Found client's 0x%x memory\n", clientID);
++         return pClientMem;
++      }
++      
++      pClientMem = pClientMem->mpNext;
++   }
++
++   DBG( "Could not find client mem 0x%04X\n", clientID );
++   return NULL;
++}
++
++/*===========================================================================
++METHOD:
++   AddToReadMemList (Public Method)
++
++DESCRIPTION:
++   Add Data to this client's ReadMem list
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev           [ I ] - Device specific memory
++   clientID       [ I ] - Requester's client ID
++   transactionID  [ I ] - Transaction ID or 0 for any
++   pData          [ I ] - Data to add
++   dataSize       [ I ] - Size of data to add
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool AddToReadMemList( 
++   sGobiUSBNet *      pDev,
++   u16              clientID,
++   u16              transactionID,
++   void *           pData,
++   u16              dataSize )
++{
++   sClientMemList * pClientMem;
++   sReadMemList ** ppThisReadMemList;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n",
++           clientID );
++
++      return false;
++   }
++
++   // Go to last ReadMemList entry
++   ppThisReadMemList = &pClientMem->mpList;
++   while (*ppThisReadMemList != NULL)
++   {
++      ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
++   }
++   
++   *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
++   if (*ppThisReadMemList == NULL)
++   {
++      DBG( "Mem error\n" );
++
++      return false;
++   }   
++   
++   (*ppThisReadMemList)->mpNext = NULL;
++   (*ppThisReadMemList)->mpData = pData;
++   (*ppThisReadMemList)->mDataSize = dataSize;
++   (*ppThisReadMemList)->mTransactionID = transactionID;
++   
++   return true;
++}
++
++/*===========================================================================
++METHOD:
++   PopFromReadMemList (Public Method)
++
++DESCRIPTION:
++   Remove data from this client's ReadMem list if it matches 
++   the specified transaction ID.
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   transactionID     [ I ] - Transaction ID or 0 for any
++   ppData            [I/O] - On success, will be filled with a 
++                             pointer to read buffer
++   pDataSize         [I/O] - On succces, will be filled with the 
++                             read buffer's size
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool PopFromReadMemList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID,
++   void **              ppData,
++   u16 *                pDataSize )
++{
++   sClientMemList * pClientMem;
++   sReadMemList * pDelReadMemList, ** ppReadMemList;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n",
++           clientID );
++
++      return false;
++   }
++   
++   ppReadMemList = &(pClientMem->mpList);
++   pDelReadMemList = NULL;
++   
++   // Find first message that matches this transaction ID
++   while (*ppReadMemList != NULL)
++   {
++      // Do we care about transaction ID?
++      if (transactionID == 0
++      ||  transactionID == (*ppReadMemList)->mTransactionID )
++      {
++         pDelReadMemList = *ppReadMemList;
++         VDBG(  "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
++               *ppReadMemList, pDelReadMemList );
++         break;
++      }
++      
++      VDBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID );
++      
++      // Next
++      ppReadMemList = &(*ppReadMemList)->mpNext;
++   }
++   VDBG(  "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
++         *ppReadMemList, pDelReadMemList );
++   if (pDelReadMemList != NULL)
++   {
++      *ppReadMemList = (*ppReadMemList)->mpNext;
++      
++      // Copy to output
++      *ppData = pDelReadMemList->mpData;
++      *pDataSize = pDelReadMemList->mDataSize;
++      VDBG(  "*ppData = 0x%p pDataSize = %u\n",
++            *ppData, *pDataSize );
++      
++      // Free memory
++      kfree( pDelReadMemList );
++      
++      return true;
++   }
++   else
++   {
++      DBG( "No read memory to pop, Client 0x%04X, TID = %x\n", 
++           clientID, 
++           transactionID );
++      return false;
++   }
++}
++
++/*===========================================================================
++METHOD:
++   AddToNotifyList (Public Method)
++
++DESCRIPTION:
++   Add Notify entry to this client's notify List
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   transactionID     [ I ] - Transaction ID or 0 for any
++   pNotifyFunct      [ I ] - Callback function to be run when data is available
++   pData             [ I ] - Data buffer that willl be passed (unmodified) 
++                             to callback
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool AddToNotifyList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID,
++   void                 (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
++   void *               pData )
++{
++   sClientMemList * pClientMem;
++   sNotifyList ** ppThisNotifyList;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n", clientID );
++      return false;
++   }
++
++   // Go to last URBList entry
++   ppThisNotifyList = &pClientMem->mpReadNotifyList;
++   while (*ppThisNotifyList != NULL)
++   {
++      ppThisNotifyList = &(*ppThisNotifyList)->mpNext;
++   }
++   
++   *ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC );
++   if (*ppThisNotifyList == NULL)
++   {
++      DBG( "Mem error\n" );
++      return false;
++   }   
++   
++   (*ppThisNotifyList)->mpNext = NULL;
++   (*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct;
++   (*ppThisNotifyList)->mpData = pData;
++   (*ppThisNotifyList)->mTransactionID = transactionID;
++   
++   return true;
++}
++
++/*===========================================================================
++METHOD:
++   NotifyAndPopNotifyList (Public Method)
++
++DESCRIPTION:
++   Remove first Notify entry from this client's notify list 
++   and Run function
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   transactionID     [ I ] - Transaction ID or 0 for any
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool NotifyAndPopNotifyList( 
++   sGobiUSBNet *        pDev,
++   u16                  clientID,
++   u16                  transactionID )
++{
++   sClientMemList * pClientMem;
++   sNotifyList * pDelNotifyList, ** ppNotifyList;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n", clientID );
++      return false;
++   }
++
++   ppNotifyList = &(pClientMem->mpReadNotifyList);
++   pDelNotifyList = NULL;
++
++   // Remove from list
++   while (*ppNotifyList != NULL)
++   {
++      // Do we care about transaction ID?
++      if (transactionID == 0
++      ||  (*ppNotifyList)->mTransactionID == 0
++      ||  transactionID == (*ppNotifyList)->mTransactionID)
++      {
++         pDelNotifyList = *ppNotifyList;
++         break;
++      }
++      
++      DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
++      
++      // next
++      ppNotifyList = &(*ppNotifyList)->mpNext;
++   }
++   
++   if (pDelNotifyList != NULL)
++   {
++      // Remove element
++      *ppNotifyList = (*ppNotifyList)->mpNext;
++      
++      // Run notification function
++      if (pDelNotifyList->mpNotifyFunct != NULL)
++      {
++         // Unlock for callback
++         spin_unlock( &pDev->mQMIDev.mClientMemLock );
++      
++         pDelNotifyList->mpNotifyFunct( pDev,
++                                        clientID,
++                                        pDelNotifyList->mpData );
++
++         // Restore lock
++         spin_lock( &pDev->mQMIDev.mClientMemLock );
++      }
++      
++      // Delete memory
++      kfree( pDelNotifyList );
++
++      return true;
++   }
++   else
++   {
++      DBG( "no one to notify for TID %x\n", transactionID );
++      
++      return false;
++   }
++}
++
++/*===========================================================================
++METHOD:
++   AddToURBList (Public Method)
++
++DESCRIPTION:
++   Add URB to this client's URB list
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev              [ I ] - Device specific memory
++   clientID          [ I ] - Requester's client ID
++   pURB              [ I ] - URB to be added
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool AddToURBList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   struct urb *     pURB )
++{
++   sClientMemList * pClientMem;
++   sURBList ** ppThisURBList;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n", clientID );
++      return false;
++   }
++
++   // Go to last URBList entry
++   ppThisURBList = &pClientMem->mpURBList;
++   while (*ppThisURBList != NULL)
++   {
++      ppThisURBList = &(*ppThisURBList)->mpNext;
++   }
++   
++   *ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
++   if (*ppThisURBList == NULL)
++   {
++      DBG( "Mem error\n" );
++      return false;
++   }   
++   
++   (*ppThisURBList)->mpNext = NULL;
++   (*ppThisURBList)->mpURB = pURB;
++   
++   return true;
++}
++
++/*===========================================================================
++METHOD:
++   PopFromURBList (Public Method)
++
++DESCRIPTION:
++   Remove URB from this client's URB list
++   
++   Caller MUST have lock on mClientMemLock
++
++PARAMETERS:
++   pDev           [ I ] - Device specific memory
++   clientID       [ I ] - Requester's client ID
++
++RETURN VALUE:
++   struct urb - Pointer to requested client's URB
++                NULL for error
++===========================================================================*/
++struct urb * PopFromURBList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID )
++{
++   sClientMemList * pClientMem;
++   sURBList * pDelURBList;
++   struct urb * pURB;
++
++#ifdef CONFIG_SMP
++   // Verify Lock
++   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
++   {
++      DBG( "unlocked\n" );
++      BUG();
++   }
++#endif
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pDev, clientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n", clientID );
++      return NULL;
++   }
++
++   // Remove from list
++   if (pClientMem->mpURBList != NULL)
++   {
++      pDelURBList = pClientMem->mpURBList;
++      pClientMem->mpURBList = pClientMem->mpURBList->mpNext;
++      
++      // Copy to output
++      pURB = pDelURBList->mpURB;
++      
++      // Delete memory
++      kfree( pDelURBList );
++
++      return pURB;
++   }
++   else
++   {
++      DBG( "No URB's to pop\n" );
++      
++      return NULL;
++   }
++}
++
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
++#ifndef f_dentry
++#define f_dentry f_path.dentry
++#endif
++#endif
++
++/*=========================================================================*/
++// Internal userspace wrappers
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   UserspaceunlockedIOCTL (Public Method)
++
++DESCRIPTION:
++   Internal wrapper for Userspace IOCTL interface
++
++PARAMETERS
++   pFilp        [ I ] - userspace file descriptor
++   cmd          [ I ] - IOCTL command
++   arg          [ I ] - IOCTL argument
++
++RETURN VALUE:
++   long - 0 for success
++         Negative errno for failure
++===========================================================================*/
++long UserspaceunlockedIOCTL(
++   struct file *     pFilp,
++   unsigned int      cmd, 
++   unsigned long     arg )
++{
++   int result;
++   u32 devVIDPID;
++
++   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++
++   if (pFilpData == NULL)
++   {
++      DBG( "Bad file data\n" );
++      return -EBADF;
++   }
++   
++   if (IsDeviceValid( pFilpData->mpDev ) == false)
++   {
++      DBG( "Invalid device! Updating f_ops\n" );
++      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
++      return -ENXIO;
++   }
++
++   switch (cmd)
++   {
++      case IOCTL_QMI_GET_SERVICE_FILE:    
++         DBG( "Setting up QMI for service %lu\n", arg );
++         if ((u8)arg == 0)
++         {
++            DBG( "Cannot use QMICTL from userspace\n" );
++            return -EINVAL;
++         }
++
++         // Connection is already setup
++         if (pFilpData->mClientID != (u16)-1)
++         {
++            DBG( "Close the current connection before opening a new one\n" );
++            return -EBADR;
++         }
++         
++         result = GetClientID( pFilpData->mpDev, (u8)arg );
++// it seems QMIWDA only allow one client, if the last quectel-CM donot realese it (killed by SIGKILL).
++// can force release it at here
++#if 1
++         if (result < 0 && (u8)arg == QMIWDA)
++         {
++             ReleaseClientID( pFilpData->mpDev, QMIWDA | (1 << 8) );
++             result = GetClientID( pFilpData->mpDev, (u8)arg );
++         }
++#endif
++         if (result < 0)
++         {
++            return result;
++         }
++         pFilpData->mClientID = (u16)result;
++         DBG("pFilpData->mClientID = 0x%x\n", pFilpData->mClientID );
++         return 0;
++         break;
++
++
++      case IOCTL_QMI_GET_DEVICE_VIDPID:
++         if (arg == 0)
++         {
++            DBG( "Bad VIDPID buffer\n" );
++            return -EINVAL;
++         }
++         
++         // Extra verification
++         if (pFilpData->mpDev->mpNetDev == 0)
++         {
++            DBG( "Bad mpNetDev\n" );
++            return -ENOMEM;
++         }
++         if (pFilpData->mpDev->mpNetDev->udev == 0)
++         {
++            DBG( "Bad udev\n" );
++            return -ENOMEM;
++         }
++
++         devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16)
++                     + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) );
++
++         result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 );
++         if (result != 0)
++         {
++            DBG( "Copy to userspace failure %d\n", result );
++         }
++
++         return result;
++                 
++         break;
++
++      case IOCTL_QMI_GET_DEVICE_MEID:
++         if (arg == 0)
++         {
++            DBG( "Bad MEID buffer\n" );
++            return -EINVAL;
++         }
++         
++         result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 );
++         if (result != 0)
++         {
++            DBG( "Copy to userspace failure %d\n", result );
++         }
++
++         return result;
++                 
++         break;
++         
++      default:
++         return -EBADRQC;       
++   }
++}
++
++/*=========================================================================*/
++// Userspace wrappers
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   UserspaceOpen (Public Method)
++
++DESCRIPTION:
++   Userspace open
++      IOCTL must be called before reads or writes
++
++PARAMETERS
++   pInode       [ I ] - kernel file descriptor
++   pFilp        [ I ] - userspace file descriptor
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for failure
++===========================================================================*/
++int UserspaceOpen(
++   struct inode *         pInode,
++   struct file *          pFilp )
++{
++   sQMIFilpStorage * pFilpData;
++
++   // Optain device pointer from pInode
++   sQMIDev * pQMIDev = container_of( pInode->i_cdev,
++                                     sQMIDev,
++                                     mCdev );
++   sGobiUSBNet * pDev = container_of( pQMIDev,
++                                    sGobiUSBNet,
++                                    mQMIDev );
++
++   if (pDev->mbMdm9x07)
++   {
++      atomic_inc(&pDev->refcount);
++      if (!pDev->mbQMIReady) {
++         if (wait_for_completion_interruptible_timeout(&pDev->mQMIReadyCompletion, 15*HZ) <= 0) {
++            if (atomic_dec_and_test(&pDev->refcount)) {
++               kfree( pDev );
++            }
++            return -ETIMEDOUT;
++         }
++      }
++      atomic_dec(&pDev->refcount);
++   }
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return -ENXIO;
++   }
++
++   // Setup data in pFilp->private_data
++   pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
++   if (pFilp->private_data == NULL)
++   {
++      DBG( "Mem error\n" );
++      return -ENOMEM;
++   }
++
++   pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++   pFilpData->mClientID = (u16)-1;
++   atomic_inc(&pDev->refcount);
++   pFilpData->mpDev = pDev;
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   UserspaceIOCTL (Public Method)
++
++DESCRIPTION:
++   Userspace IOCTL functions
++
++PARAMETERS
++   pUnusedInode [ I ] - (unused) kernel file descriptor
++   pFilp        [ I ] - userspace file descriptor
++   cmd          [ I ] - IOCTL command
++   arg          [ I ] - IOCTL argument
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for failure
++===========================================================================*/
++int UserspaceIOCTL(
++   struct inode *    pUnusedInode,
++   struct file *     pFilp,
++   unsigned int      cmd,
++   unsigned long     arg ) 
++{
++   // call the internal wrapper function
++   return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg );  
++}
++
++/*===========================================================================
++METHOD:
++   UserspaceClose (Public Method)
++
++DESCRIPTION:
++   Userspace close
++      Release client ID and free memory
++
++PARAMETERS
++   pFilp           [ I ] - userspace file descriptor
++   unusedFileTable [ I ] - (unused) file table
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for failure
++===========================================================================*/
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
++int UserspaceClose( 
++   struct file *       pFilp,
++   fl_owner_t          unusedFileTable )
++#else
++int UserspaceClose( struct file *       pFilp )
++#endif
++{
++   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++   struct task_struct * pEachTask;
++   struct fdtable * pFDT;
++   int count = 0;
++   int used = 0;
++   unsigned long flags;
++
++   if (pFilpData == NULL)
++   {
++      DBG( "bad file data\n" );
++      return -EBADF;
++   }
++
++   // Fallthough.  If f_count == 1 no need to do more checks
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
++   if (atomic_read( &pFilp->f_count ) != 1)
++#else
++   if (atomic_long_read( &pFilp->f_count ) != 1)
++#endif
++   {
++      rcu_read_lock();
++      for_each_process( pEachTask )
++      {
++         task_lock(pEachTask);
++         if (pEachTask == NULL || pEachTask->files == NULL)
++         {
++            // Some tasks may not have files (e.g. Xsession)
++            task_unlock(pEachTask);
++            continue;
++         }
++         spin_lock_irqsave( &pEachTask->files->file_lock, flags );
++         task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
++         pFDT = files_fdtable( pEachTask->files );
++         for (count = 0; count < pFDT->max_fds; count++)
++         {
++            // Before this function was called, this file was removed
++            // from our task's file table so if we find it in a file
++            // table then it is being used by another task
++            if (pFDT->fd[count] == pFilp)
++            {
++               used++;
++               break;
++            }
++         }
++         spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
++      }
++      rcu_read_unlock();
++      
++      if (used > 0)
++      {
++         DBG( "not closing, as this FD is open by %d other process\n", used );
++         return 0;
++      }
++   }
++
++   if (IsDeviceValid( pFilpData->mpDev ) == false)
++   {
++      DBG( "Invalid device! Updating f_ops\n" );
++      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
++      return -ENXIO;
++   }
++   
++   DBG( "0x%04X\n", pFilpData->mClientID );
++   
++   // Disable pFilpData so they can't keep sending read or write 
++   //    should this function hang
++   // Note: memory pointer is still saved in pFilpData to be deleted later
++   pFilp->private_data = NULL;
++
++   if (pFilpData->mClientID != (u16)-1)
++   {
++      if (pFilpData->mpDev->mbDeregisterQMIDevice)
++         pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
++      else
++      ReleaseClientID( pFilpData->mpDev,
++                       pFilpData->mClientID );
++   }
++   atomic_dec(&pFilpData->mpDev->refcount);
++      
++   kfree( pFilpData );
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   UserspaceRead (Public Method)
++
++DESCRIPTION:
++   Userspace read (synchronous)
++
++PARAMETERS
++   pFilp           [ I ] - userspace file descriptor
++   pBuf            [ I ] - read buffer
++   size            [ I ] - size of read buffer
++   pUnusedFpos     [ I ] - (unused) file position
++
++RETURN VALUE:
++   ssize_t - Number of bytes read for success
++             Negative errno for failure
++===========================================================================*/
++ssize_t UserspaceRead( 
++   struct file *          pFilp,
++   char __user *          pBuf, 
++   size_t                 size,
++   loff_t *               pUnusedFpos )
++{
++   int result;
++   void * pReadData = NULL;
++   void * pSmallReadData;
++   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++
++   if (pFilpData == NULL)
++   {
++      DBG( "Bad file data\n" );
++      return -EBADF;
++   }
++
++   if (IsDeviceValid( pFilpData->mpDev ) == false)
++   {
++      DBG( "Invalid device! Updating f_ops\n" );
++      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
++      return -ENXIO;
++   }
++   
++   if (pFilpData->mClientID == (u16)-1)
++   {
++      DBG( "Client ID must be set before reading 0x%04X\n",
++           pFilpData->mClientID );
++      return -EBADR;
++   }
++   
++   // Perform synchronous read
++   result = ReadSync( pFilpData->mpDev,
++                      &pReadData,
++                      pFilpData->mClientID,
++                      0 );
++   if (result <= 0)
++   {
++      return result;
++   }
++   
++   // Discard QMUX header
++   result -= QMUXHeaderSize();
++   pSmallReadData = pReadData + QMUXHeaderSize();
++
++   if (result > size)
++   {
++      DBG( "Read data is too large for amount user has requested\n" );
++      kfree( pReadData );
++      return -EOVERFLOW;
++   }
++
++   DBG(  "pBuf = 0x%p pSmallReadData = 0x%p, result = %d",
++         pBuf, pSmallReadData, result );
++
++   if (copy_to_user( pBuf, pSmallReadData, result ) != 0)
++   {
++      DBG( "Error copying read data to user\n" );
++      result = -EFAULT;
++   }
++   
++   // Reader is responsible for freeing read buffer
++   kfree( pReadData );
++   
++   return result;
++}
++
++/*===========================================================================
++METHOD:
++   UserspaceWrite (Public Method)
++
++DESCRIPTION:
++   Userspace write (synchronous)
++
++PARAMETERS
++   pFilp           [ I ] - userspace file descriptor
++   pBuf            [ I ] - write buffer
++   size            [ I ] - size of write buffer
++   pUnusedFpos     [ I ] - (unused) file position
++
++RETURN VALUE:
++   ssize_t - Number of bytes read for success
++             Negative errno for failure
++===========================================================================*/
++ssize_t UserspaceWrite(
++   struct file *        pFilp, 
++   const char __user *  pBuf, 
++   size_t               size,
++   loff_t *             pUnusedFpos )
++{
++   int status;
++   void * pWriteBuffer;
++   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++
++   if (pFilpData == NULL)
++   {
++      DBG( "Bad file data\n" );
++      return -EBADF;
++   }
++
++   if (IsDeviceValid( pFilpData->mpDev ) == false)
++   {
++      DBG( "Invalid device! Updating f_ops\n" );
++      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
++      return -ENXIO;
++   }
++
++   if (pFilpData->mClientID == (u16)-1)
++   {
++      DBG( "Client ID must be set before writing 0x%04X\n",
++           pFilpData->mClientID );
++      return -EBADR;
++   }
++   
++   // Copy data from user to kernel space
++   pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++   status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size );
++   if (status != 0)
++   {
++      DBG( "Unable to copy data from userspace %d\n", status );
++      kfree( pWriteBuffer );
++      return status;
++   }
++
++   status = WriteSync( pFilpData->mpDev,
++                       pWriteBuffer, 
++                       size + QMUXHeaderSize(),
++                       pFilpData->mClientID );
++
++   kfree( pWriteBuffer );
++   
++   // On success, return requested size, not full QMI reqest size
++   if (status == size + QMUXHeaderSize())
++   {
++      return size;
++   }
++   else
++   {
++      return status;
++   }
++}
++
++/*===========================================================================
++METHOD:
++   UserspacePoll (Public Method)
++
++DESCRIPTION:
++   Used to determine if read/write operations are possible without blocking
++
++PARAMETERS
++   pFilp              [ I ] - userspace file descriptor
++   pPollTable         [I/O] - Wait object to notify the kernel when data 
++                              is ready
++
++RETURN VALUE:
++   unsigned int - bitmask of what operations can be done immediately
++===========================================================================*/
++unsigned int UserspacePoll(
++   struct file *                  pFilp,
++   struct poll_table_struct *     pPollTable )
++{
++   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
++   sClientMemList * pClientMem;
++   unsigned long flags;
++
++   // Always ready to write
++   unsigned long status = POLLOUT | POLLWRNORM;
++
++   if (pFilpData == NULL)
++   {
++      DBG( "Bad file data\n" );
++      return POLLERR;
++   }
++
++   if (IsDeviceValid( pFilpData->mpDev ) == false)
++   {
++      DBG( "Invalid device! Updating f_ops\n" );
++      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
++      return POLLERR;
++   }
++
++   if (pFilpData->mpDev->mbDeregisterQMIDevice)
++   {
++      DBG( "DeregisterQMIDevice ing\n" );
++      return POLLHUP | POLLERR;
++   }
++
++   if (pFilpData->mClientID == (u16)-1)
++   {
++      DBG( "Client ID must be set before polling 0x%04X\n",
++           pFilpData->mClientID );
++      return POLLERR;
++   }
++
++   // Critical section
++   spin_lock_irqsave( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
++
++   // Get this client's memory location
++   pClientMem = FindClientMem( pFilpData->mpDev, 
++                               pFilpData->mClientID );
++   if (pClientMem == NULL)
++   {
++      DBG( "Could not find this client's memory 0x%04X\n",
++           pFilpData->mClientID );
++
++      spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, 
++                              flags );
++      return POLLERR;
++   }
++   
++   poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable );
++
++   if (pClientMem->mpList != NULL)
++   {
++      status |= POLLIN | POLLRDNORM;
++   }
++
++   // End critical section
++   spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
++
++   // Always ready to write 
++   return (status | POLLOUT | POLLWRNORM);
++}
++
++/*=========================================================================*/
++// Initializer and destructor
++/*=========================================================================*/
++int QMICTLSyncProc(sGobiUSBNet *pDev)
++{
++   void *pWriteBuffer;
++   void *pReadBuffer;
++   int result;
++   u16 writeBufferSize;
++   u16 readBufferSize;
++   u8 transactionID;
++   unsigned long flags;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return -EFAULT;
++   }
++
++   writeBufferSize= QMICTLSyncReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++
++   transactionID = QMIXactionIDGet(pDev);
++
++   /* send a QMI_CTL_SYNC_REQ (0x0027) */
++   result = QMICTLSyncReq( pWriteBuffer,
++                           writeBufferSize,
++                           transactionID );
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++
++   result = WriteSync( pDev,
++                       pWriteBuffer,
++                       writeBufferSize,
++                       QMICTL );
++
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++
++   // QMI CTL Sync Response
++   result = ReadSync( pDev,
++                      &pReadBuffer,
++                      QMICTL,
++                      transactionID );
++   if (result < 0)
++   {
++      return result;
++   }
++
++   result = QMICTLSyncResp( pReadBuffer,
++                            (u16)result );
++
++   kfree( pReadBuffer );
++
++   if (result < 0) /* need to re-sync */
++   {
++      DBG( "sync response error code %d\n", result );
++      /* start timer and wait for the response */
++      /* process response */
++      return result;
++   }
++
++#if 1 //free these ununsed qmi response, or when these transactionID re-used, they will be regarded as qmi response of the qmi request that have same transactionID
++   // Enter critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Free any unread data
++   while (PopFromReadMemList( pDev, QMICTL, 0, &pReadBuffer, &readBufferSize) == true) {	
++       kfree( pReadBuffer ); 
++   }
++   
++   // End critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );    
++#endif
++  
++   // Success
++   return 0;
++}
++
++static int qmi_sync_thread(void *data) {
++    sGobiUSBNet * pDev = (sGobiUSBNet *)data;
++    int result = 0;
++
++#if 1  
++   // Device is not ready for QMI connections right away
++   //   Wait up to 30 seconds before failing
++   if (QMIReady( pDev, 30000 ) == false)
++   {
++      DBG( "Device unresponsive to QMI\n" );
++      goto __qmi_sync_finished;
++   }
++
++   // Initiate QMI CTL Sync Procedure
++   DBG( "Sending QMI CTL Sync Request\n" );
++   result = QMICTLSyncProc(pDev);
++   if (result != 0)
++   {
++      DBG( "QMI CTL Sync Procedure Error\n" );
++      goto __qmi_sync_finished;
++   }
++   else
++   {
++      DBG( "QMI CTL Sync Procedure Successful\n" );
++   }
++
++   // Setup Data Format
++   result = QMIWDASetDataFormat (pDev);
++   if (result != 0)
++   {
++      goto __qmi_sync_finished;
++   }
++
++   // Setup WDS callback
++   result = SetupQMIWDSCallback( pDev );
++   if (result != 0)
++   {
++      goto __qmi_sync_finished;
++   }
++
++   // Fill MEID for device
++   result = QMIDMSGetMEID( pDev );
++   if (result != 0)
++   {
++      goto __qmi_sync_finished;
++   }
++#endif
++
++__qmi_sync_finished:
++   pDev->mbQMIReady = true;
++   complete_all(&pDev->mQMIReadyCompletion);
++   if (atomic_dec_and_test(&pDev->refcount)) {
++      kfree( pDev );
++   }
++   return result;
++}
++
++/*===========================================================================
++METHOD:
++   RegisterQMIDevice (Public Method)
++
++DESCRIPTION:
++   QMI Device initialization function
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++   
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for failure
++===========================================================================*/
++int RegisterQMIDevice( sGobiUSBNet * pDev )
++{
++   int result;
++   int GobiQMIIndex = 0;
++   dev_t devno; 
++   char * pDevName;
++
++   if (pDev->mQMIDev.mbCdevIsInitialized == true)
++   {
++      // Should never happen, but always better to check
++      DBG( "device already exists\n" );
++      return -EEXIST;
++   }
++ 
++   pDev->mbQMIValid = true;
++   pDev->mbDeregisterQMIDevice = false;
++
++   // Set up for QMICTL
++   //    (does not send QMI message, just sets up memory)
++   result = GetClientID( pDev, QMICTL );
++   if (result != 0)
++   {
++      pDev->mbQMIValid = false;
++      return result;
++   }
++   atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
++
++   // Start Async reading
++   result = StartRead( pDev );
++   if (result != 0)
++   {
++      pDev->mbQMIValid = false;
++      return result;
++   }
++
++   if (pDev->mbMdm9x07)
++   {
++      usb_control_msg( pDev->mpNetDev->udev,
++                            usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
++                             SET_CONTROL_LINE_STATE_REQUEST,
++                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
++                             CONTROL_DTR,
++                             /* USB interface number to receive control message */
++                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
++                             NULL,
++                             0,
++                             100 );
++   }
++
++    //for EC21&25, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe).
++   if (pDev->mbMdm9x07)
++   {
++      struct task_struct *qmi_sync_task;
++      atomic_inc(&pDev->refcount);
++      init_completion(&pDev->mQMIReadyCompletion);
++      pDev->mbQMIReady = false;
++      qmi_sync_task = kthread_run(qmi_sync_thread, (void *)pDev, "qmi_sync/%d", pDev->mpNetDev->udev->devnum);
++       if (IS_ERR(qmi_sync_task)) {
++         atomic_dec(&pDev->refcount);
++         DBG( "Create qmi_sync_thread fail\n" );
++         return PTR_ERR(qmi_sync_task);
++      }
++      goto __register_chardev_qccmi;
++   }
++   
++   // Device is not ready for QMI connections right away
++   //   Wait up to 30 seconds before failing
++   if (QMIReady( pDev, 30000 ) == false)
++   {
++      DBG( "Device unresponsive to QMI\n" );
++      return -ETIMEDOUT;
++   }
++
++   // Initiate QMI CTL Sync Procedure
++   DBG( "Sending QMI CTL Sync Request\n" );
++   result = QMICTLSyncProc(pDev);
++   if (result != 0)
++   {
++      DBG( "QMI CTL Sync Procedure Error\n" );
++      return result;
++   }
++   else
++   {
++      DBG( "QMI CTL Sync Procedure Successful\n" );
++   }
++
++   // Setup Data Format
++   result = QMIWDASetDataFormat (pDev);
++   if (result != 0)
++   {
++       return result;
++   }
++
++   // Setup WDS callback
++   result = SetupQMIWDSCallback( pDev );
++   if (result != 0)
++   {
++	  return result;
++   }
++
++   // Fill MEID for device
++   result = QMIDMSGetMEID( pDev );
++   if (result != 0)
++   {
++      return result;
++   }
++
++__register_chardev_qccmi:
++   // allocate and fill devno with numbers
++   result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
++   if (result < 0)
++   {
++ 	  return result;
++   }
++
++   // Create cdev
++   cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
++   pDev->mQMIDev.mCdev.owner = THIS_MODULE;
++   pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
++   pDev->mQMIDev.mbCdevIsInitialized = true;
++
++   result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
++   if (result != 0)
++   {
++      DBG( "error adding cdev\n" );
++      return result;
++   }
++
++   // Match interface number (usb# or eth#)
++   if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "eth" ))) {
++       pDevName += strlen( "eth" );
++   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "usb" ))) {
++       pDevName += strlen( "usb" );
++#if 1 //openWRT like use ppp# or lte#
++   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "ppp" ))) {
++       pDevName += strlen( "ppp" );
++   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "lte" ))) {
++       pDevName += strlen( "lte" );
++#endif
++   } else {
++      DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name );
++      return -ENXIO;
++   }
++   GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 );
++   if (GobiQMIIndex < 0)
++   {
++      DBG( "Bad minor number\n" );
++      return -ENXIO;
++   }
++
++   // Always print this output
++   printk( KERN_INFO "creating qcqmi%d\n",
++           GobiQMIIndex );
++
++#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 ))
++   // kernel 2.6.27 added a new fourth parameter to device_create
++   //    void * drvdata : the data to be added to the device for callbacks
++   device_create( pDev->mQMIDev.mpDevClass,
++                  &pDev->mpIntf->dev, 
++                  devno,
++                  NULL,
++                  "qcqmi%d", 
++                  GobiQMIIndex );
++#else
++   device_create( pDev->mQMIDev.mpDevClass,
++                  &pDev->mpIntf->dev, 
++                  devno,
++                  "qcqmi%d", 
++                  GobiQMIIndex );
++#endif
++   
++   pDev->mQMIDev.mDevNum = devno;
++
++   // Success
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   DeregisterQMIDevice (Public Method)
++
++DESCRIPTION:
++   QMI Device cleanup function
++   
++   NOTE: When this function is run the device is no longer valid
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void DeregisterQMIDevice( sGobiUSBNet * pDev )
++{
++   struct inode * pOpenInode;
++   struct list_head * pInodeList;
++   struct task_struct * pEachTask;
++   struct fdtable * pFDT;
++   struct file * pFilp;
++   unsigned long flags;
++   int count = 0;
++   int tries;
++   int result;
++
++   // Should never happen, but check anyway
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "wrong device\n" );
++      return;
++   }
++
++   pDev->mbDeregisterQMIDevice = true;
++
++   // Release all clients
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++   while (pDev->mQMIDev.mpClientMemList != NULL)
++   {
++      u16 mClientID = pDev->mQMIDev.mpClientMemList->mClientID;
++      if (waitqueue_active(&pDev->mQMIDev.mpClientMemList->mWaitQueue)) {
++         DBG("WaitQueue 0x%04X\n", mClientID);
++         wake_up_interruptible_sync( &pDev->mQMIDev.mpClientMemList->mWaitQueue );
++         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );      
++         msleep(10);
++         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++         continue;
++      }
++
++      DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
++   
++      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      ReleaseClientID( pDev, mClientID );
++      // NOTE: pDev->mQMIDev.mpClientMemList will 
++      //       be updated in ReleaseClientID()
++      spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++   }
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++
++   // Stop all reads
++   KillRead( pDev );
++
++   pDev->mbQMIValid = false;
++
++   if (pDev->mQMIDev.mbCdevIsInitialized == false)
++   {
++      return;
++   }
++
++   // Find each open file handle, and manually close it
++   
++   // Generally there will only be only one inode, but more are possible
++   list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list )
++   {
++      // Get the inode
++      pOpenInode = container_of( pInodeList, struct inode, i_devices );
++      if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
++      {
++         // Look for this inode in each task
++
++         rcu_read_lock();
++         for_each_process( pEachTask )
++         {
++            task_lock(pEachTask);
++            if (pEachTask == NULL || pEachTask->files == NULL)
++            {
++               // Some tasks may not have files (e.g. Xsession)
++               task_unlock(pEachTask);
++               continue;
++            }
++            // For each file this task has open, check if it's referencing
++            // our inode.
++            spin_lock_irqsave( &pEachTask->files->file_lock, flags );
++            task_unlock(pEachTask);  //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
++            pFDT = files_fdtable( pEachTask->files );
++            for (count = 0; count < pFDT->max_fds; count++)
++            {
++               pFilp = pFDT->fd[count];
++               if (pFilp != NULL &&  pFilp->f_dentry != NULL)
++               {
++                  if (pFilp->f_dentry->d_inode == pOpenInode)
++                  {
++                     // Close this file handle
++                     rcu_assign_pointer( pFDT->fd[count], NULL );                     
++                     spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
++                     
++                     DBG( "forcing close of open file handle\n" );
++                     filp_close( pFilp, pEachTask->files );
++
++                     spin_lock_irqsave( &pEachTask->files->file_lock, flags );
++                  }
++               }
++            }
++            spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
++         }
++         rcu_read_unlock();
++      }
++   }
++
++   // Send SetControlLineState request (USB_CDC)
++   result = usb_control_msg( pDev->mpNetDev->udev,
++                             usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
++                             SET_CONTROL_LINE_STATE_REQUEST,
++                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
++                             0, // DTR not present
++                             /* USB interface number to receive control message */
++                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
++                             NULL,
++                             0,
++                             100 );
++   if (result < 0)
++   {
++      DBG( "Bad SetControlLineState status %d\n", result );
++   }
++
++   // Remove device (so no more calls can be made by users)
++   if (IS_ERR( pDev->mQMIDev.mpDevClass ) == false)
++   {
++      device_destroy( pDev->mQMIDev.mpDevClass, 
++                      pDev->mQMIDev.mDevNum );   
++   }
++
++   // Hold onto cdev memory location until everyone is through using it.
++   // Timeout after 30 seconds (10 ms interval).  Timeout should never happen,
++   // but exists to prevent an infinate loop just in case.
++   for (tries = 0; tries < 30 * 100; tries++)
++   {
++      int ref = gobi_atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
++      if (ref > 1)
++      {
++         DBG( "cdev in use by %d tasks\n", ref - 1 ); 
++         msleep( 10 );
++      }
++      else
++      {
++         break;
++      }
++   }
++
++   cdev_del( &pDev->mQMIDev.mCdev );
++   
++   unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
++
++   return;
++}
++
++/*=========================================================================*/
++// Driver level client management
++/*=========================================================================*/
++
++/*===========================================================================
++METHOD:
++   QMIReady (Public Method)
++
++DESCRIPTION:
++   Send QMI CTL GET VERSION INFO REQ and SET DATA FORMAT REQ
++   Wait for response or timeout
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++   timeout  [ I ] - Milliseconds to wait for response
++
++RETURN VALUE:
++   bool
++===========================================================================*/
++bool QMIReady(
++   sGobiUSBNet *    pDev,
++   u16                timeout )
++{
++   int result;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   void * pReadBuffer;
++   u16 readBufferSize;
++   struct semaphore readSem;
++   u16 curTime;
++   unsigned long flags;
++   u8 transactionID;
++   
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return false;
++   }
++
++   writeBufferSize = QMICTLReadyReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return false;
++   }
++
++   // An implimentation of down_timeout has not been agreed on,
++   //    so it's been added and removed from the kernel several times.
++   //    We're just going to ignore it and poll the semaphore.
++
++   // Send a write every 1000 ms and see if we get a response
++   for (curTime = 0; curTime < timeout; curTime += 1000)
++   {
++      // Start read
++      sema_init( &readSem, 0 );
++   
++      transactionID = QMIXactionIDGet( pDev );
++
++      result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
++      if (result != 0)
++      {
++         kfree( pWriteBuffer );
++         return false;
++      }
++
++      // Fill buffer
++      result = QMICTLReadyReq( pWriteBuffer, 
++                               writeBufferSize,
++                               transactionID );
++      if (result < 0)
++      {
++         kfree( pWriteBuffer );
++         return false;
++      }
++
++      // Disregard status.  On errors, just try again
++      WriteSync( pDev,
++                 pWriteBuffer,
++                 writeBufferSize,
++                 QMICTL );
++
++      msleep( 1000 );
++      if (down_trylock( &readSem ) == 0)
++      {
++         // Enter critical section
++         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++
++         // Pop the read data
++         if (PopFromReadMemList( pDev,
++                                 QMICTL,
++                                 transactionID,
++                                 &pReadBuffer,
++                                 &readBufferSize ) == true)
++         {
++            // Success
++
++            // End critical section
++            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++         
++            // We don't care about the result
++            kfree( pReadBuffer );
++
++            break;
++         }
++         else
++         {
++            // Read mismatch/failure, unlock and continue
++            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++         }
++      }
++      else
++      {
++         // Enter critical section
++         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++         
++         // Timeout, remove the async read
++         NotifyAndPopNotifyList( pDev, QMICTL, transactionID );
++         
++         // End critical section
++         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
++      }
++   }
++
++   kfree( pWriteBuffer );
++
++   // Did we time out?   
++   if (curTime >= timeout)
++   {
++      return false;
++   }
++   
++   DBG( "QMI Ready after %u milliseconds\n", curTime );
++
++   // Success
++   return true;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDSCallback (Public Method)
++
++DESCRIPTION:
++   QMI WDS callback function
++   Update net stats or link state
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++   clientID [ I ] - Client ID
++   pData    [ I ] - Callback data (unused)
++
++RETURN VALUE:
++   None
++===========================================================================*/
++void QMIWDSCallback(
++   sGobiUSBNet *    pDev,
++   u16                clientID,
++   void *             pData )
++{
++   bool bRet;
++   int result;
++   void * pReadBuffer;
++   u16 readBufferSize;
++
++#if 0
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
++   struct net_device_stats * pStats = &(pDev->mpNetDev->stats);
++#else
++   struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
++#endif
++#endif
++
++   u32 TXOk = (u32)-1;
++   u32 RXOk = (u32)-1;
++   u32 TXErr = (u32)-1;
++   u32 RXErr = (u32)-1;
++   u32 TXOfl = (u32)-1;
++   u32 RXOfl = (u32)-1;
++   u64 TXBytesOk = (u64)-1;
++   u64 RXBytesOk = (u64)-1;
++   bool bLinkState;
++   bool bReconfigure;
++   unsigned long flags;
++   
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return;
++   }
++
++   // Critical section
++   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
++   
++   bRet = PopFromReadMemList( pDev,
++                              clientID,
++                              0,
++                              &pReadBuffer,
++                              &readBufferSize );
++   
++   // End critical section
++   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); 
++   
++   if (bRet == false)
++   {
++      DBG( "WDS callback failed to get data\n" );
++      return;
++   }
++   
++   // Default values
++   bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
++   bReconfigure = false;
++
++   result = QMIWDSEventResp( pReadBuffer,
++                             readBufferSize,
++                             &TXOk,
++                             &RXOk,
++                             &TXErr,
++                             &RXErr,
++                             &TXOfl,
++                             &RXOfl,
++                             &TXBytesOk,
++                             &RXBytesOk,
++                             &bLinkState,
++                             &bReconfigure );
++   if (result < 0)
++   {
++      DBG( "bad WDS packet\n" );
++   }
++   else
++   {
++#if 0 //usbbet.c will do this job
++      // Fill in new values, ignore max values
++      if (TXOfl != (u32)-1)
++      {
++         pStats->tx_fifo_errors = TXOfl;
++      }
++      
++      if (RXOfl != (u32)-1)
++      {
++         pStats->rx_fifo_errors = RXOfl;
++      }
++
++      if (TXErr != (u32)-1)
++      {
++         pStats->tx_errors = TXErr;
++      }
++      
++      if (RXErr != (u32)-1)
++      {
++         pStats->rx_errors = RXErr;
++      }
++
++      if (TXOk != (u32)-1)
++      {
++         pStats->tx_packets = TXOk + pStats->tx_errors;
++      }
++      
++      if (RXOk != (u32)-1)
++      {
++         pStats->rx_packets = RXOk + pStats->rx_errors;
++      }
++
++      if (TXBytesOk != (u64)-1)
++      {
++         pStats->tx_bytes = TXBytesOk;
++      }
++      
++      if (RXBytesOk != (u64)-1)
++      {
++         pStats->rx_bytes = RXBytesOk;
++      }
++#endif
++
++      if (bReconfigure == true)
++      {
++         DBG( "Net device link reset\n" );
++         GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
++         GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
++      }
++      else
++      {
++         if (bLinkState == true)
++         {
++            if (GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
++                DBG( "Net device link is connected\n" );
++                GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
++            }
++         }
++         else
++         {
++            if (!GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
++                DBG( "Net device link is disconnected\n" );
++                GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
++            }
++         }
++      }
++   }
++
++   kfree( pReadBuffer );
++
++   // Setup next read
++   result = ReadAsync( pDev,
++                       clientID,
++                       0,
++                       QMIWDSCallback,
++                       pData );
++   if (result != 0)
++   {
++      DBG( "unable to setup next async read\n" );
++   }
++
++   return;
++}
++
++/*===========================================================================
++METHOD:
++   SetupQMIWDSCallback (Public Method)
++
++DESCRIPTION:
++   Request client and fire off reqests and start async read for 
++   QMI WDS callback
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   int - 0 for success
++         Negative errno for failure
++===========================================================================*/
++int SetupQMIWDSCallback( sGobiUSBNet * pDev )
++{
++   int result;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   u16 WDSClientID;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return -EFAULT;
++   }
++   
++   result = GetClientID( pDev, QMIWDS );
++   if (result < 0)
++   {
++      return result;
++   }
++   WDSClientID = result;
++
++#if 0 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use quectel-CM, or cannot obtain IP by udhcpc
++    if (pDev->mbMdm9x07)
++    {
++       void * pReadBuffer;
++       u16 readBufferSize;
++
++        writeBufferSize = QMIWDSSetQMUXBindMuxDataPortSize();
++        pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++        if (pWriteBuffer == NULL)
++        {
++            return -ENOMEM;
++        }
++   
++        result = QMIWDSSetQMUXBindMuxDataPortReq( pWriteBuffer, 
++                                         writeBufferSize,
++                                         3 );
++        if (result < 0)
++        {
++            kfree( pWriteBuffer );
++            return result;
++        }
++
++       result = WriteSync( pDev,
++                           pWriteBuffer,
++                           writeBufferSize,
++                           WDSClientID );
++        kfree( pWriteBuffer );
++
++        if (result < 0)
++        {
++            return result;
++        }
++       
++        result = ReadSync( pDev,
++                          &pReadBuffer,
++                          WDSClientID,
++                          3 );
++        if (result < 0)
++        {
++            return result;
++        }
++        readBufferSize = result;
++
++        kfree( pReadBuffer );
++   }
++#endif
++
++   // QMI WDS Set Event Report
++   writeBufferSize = QMIWDSSetEventReportReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++   
++   result = QMIWDSSetEventReportReq( pWriteBuffer, 
++                                     writeBufferSize,
++                                     1 );
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++
++   result = WriteSync( pDev,
++                       pWriteBuffer,
++                       writeBufferSize,
++                       WDSClientID );
++   kfree( pWriteBuffer );
++
++   if (result < 0)
++   {
++      return result;
++   }
++
++   // QMI WDS Get PKG SRVC Status
++   writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++
++   result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer, 
++                                       writeBufferSize,
++                                       2 );
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++   
++   result = WriteSync( pDev,
++                       pWriteBuffer,
++                       writeBufferSize,
++                       WDSClientID );
++   kfree( pWriteBuffer );
++
++   if (result < 0)
++   {
++      return result;
++   }
++
++   // Setup asnyc read callback
++   result = ReadAsync( pDev,
++                       WDSClientID,
++                       0,
++                       QMIWDSCallback,
++                       NULL );
++   if (result != 0)
++   {
++      DBG( "unable to setup async read\n" );
++      return result;
++   }
++
++   // Send SetControlLineState request (USB_CDC)
++   //   Required for Autoconnect
++   result = usb_control_msg( pDev->mpNetDev->udev,
++                             usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
++                             SET_CONTROL_LINE_STATE_REQUEST,
++                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
++                             CONTROL_DTR,
++                             /* USB interface number to receive control message */
++                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
++                             NULL,
++                             0,
++                             100 );
++   if (result < 0)
++   {
++      DBG( "Bad SetControlLineState status %d\n", result );
++      return result;
++   }
++
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMIDMSGetMEID (Public Method)
++
++DESCRIPTION:
++   Register DMS client
++   send MEID req and parse response
++   Release DMS client
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   None
++===========================================================================*/
++int QMIDMSGetMEID( sGobiUSBNet * pDev )
++{
++   int result;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   void * pReadBuffer;
++   u16 readBufferSize;
++   u16 DMSClientID;
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return -EFAULT;
++   }
++
++   result = GetClientID( pDev, QMIDMS );
++   if (result < 0)
++   {
++      return result;
++   }
++   DMSClientID = result;
++
++   // QMI DMS Get Serial numbers Req
++   writeBufferSize = QMIDMSGetMEIDReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++
++   result = QMIDMSGetMEIDReq( pWriteBuffer, 
++                              writeBufferSize,
++                              1 );
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++
++   result = WriteSync( pDev,
++                       pWriteBuffer,
++                       writeBufferSize,
++                       DMSClientID );
++   kfree( pWriteBuffer );
++
++   if (result < 0)
++   {
++      return result;
++   }
++
++   // QMI DMS Get Serial numbers Resp
++   result = ReadSync( pDev,
++                      &pReadBuffer,
++                      DMSClientID,
++                      1 );
++   if (result < 0)
++   {
++      return result;
++   }
++   readBufferSize = result;
++
++   result = QMIDMSGetMEIDResp( pReadBuffer,
++                               readBufferSize,
++                               &pDev->mMEID[0],
++                               14 );
++   kfree( pReadBuffer );
++
++   if (result < 0)
++   {
++      DBG( "bad get MEID resp\n" );
++      
++      // Non fatal error, device did not return any MEID
++      //    Fill with 0's
++      memset( &pDev->mMEID[0], '0', 14 );
++   }
++
++   ReleaseClientID( pDev, DMSClientID );
++
++   // Success
++   return 0;
++}
++
++/*===========================================================================
++METHOD:
++   QMIWDASetDataFormat (Public Method)
++
++DESCRIPTION:
++   Register WDA client
++   send Data format request and parse response
++   Release WDA client
++
++PARAMETERS:
++   pDev     [ I ] - Device specific memory
++
++RETURN VALUE:
++   None
++===========================================================================*/
++int QMIWDASetDataFormat( sGobiUSBNet * pDev )
++{
++   int result;
++   void * pWriteBuffer;
++   u16 writeBufferSize;
++   void * pReadBuffer;
++   u16 readBufferSize;
++   u16 WDAClientID;
++
++   DBG("\n");
++
++   if (IsDeviceValid( pDev ) == false)
++   {
++      DBG( "Invalid device\n" );
++      return -EFAULT;
++   }
++
++   result = GetClientID( pDev, QMIWDA );
++   if (result < 0)
++   {
++      return result;
++   }
++   WDAClientID = result;
++
++   // QMI WDA Set Data Format Request
++   writeBufferSize = QMIWDASetDataFormatReqSize();
++   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
++   if (pWriteBuffer == NULL)
++   {
++      return -ENOMEM;
++   }
++
++   result = QMIWDASetDataFormatReq( pWriteBuffer,
++                              writeBufferSize, pDev->mbRawIPMode,
++                                    1 );
++   if (result < 0)
++   {
++      kfree( pWriteBuffer );
++      return result;
++   }
++
++   result = WriteSync( pDev,
++                       pWriteBuffer,
++                       writeBufferSize,
++                       WDAClientID );
++   kfree( pWriteBuffer );
++
++   if (result < 0)
++   {
++      return result;
++   }
++
++   // QMI DMS Get Serial numbers Resp
++   result = ReadSync( pDev,
++                      &pReadBuffer,
++                      WDAClientID,
++                      1 );
++   if (result < 0)
++   {
++      return result;
++   }
++   readBufferSize = result;
++
++   result = QMIWDASetDataFormatResp( pReadBuffer,
++                                     readBufferSize, pDev->mbRawIPMode );
++
++   kfree( pReadBuffer );
++
++#if 1 //def DATA_MODE_RP
++   pDev->mbRawIPMode = (result == 2);    /* LinkProt: 0x1 - ETH; 0x2 - rawIP */
++   if (pDev->mbRawIPMode) {
++       pDev->mpNetDev->net->flags |= IFF_NOARP;
++   }
++#endif
++
++   if (result < 0)
++   {
++      DBG( "Data Format Cannot be set\n" );
++   }
++
++   ReleaseClientID( pDev, WDAClientID );
++
++   // Success
++    return 0;
++}
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMIDevice.h linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.h
+--- linux-at91/drivers/net/usb/ec20/QMIDevice.h	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.h	2024-05-22 15:51:24.604628419 +0800
+@@ -0,0 +1,345 @@
++/*===========================================================================
++FILE:
++   QMIDevice.h
++
++DESCRIPTION:
++   Functions related to the QMI interface device
++   
++FUNCTIONS:
++   Generic functions
++      IsDeviceValid
++      PrintHex
++      GobiSetDownReason
++      GobiClearDownReason
++      GobiTestDownReason
++
++   Driver level asynchronous read functions
++      ResubmitIntURB
++      ReadCallback
++      IntCallback
++      StartRead
++      KillRead
++
++   Internal read/write functions
++      ReadAsync
++      UpSem
++      ReadSync
++      WriteSyncCallback
++      WriteSync
++
++   Internal memory management functions
++      GetClientID
++      ReleaseClientID
++      FindClientMem
++      AddToReadMemList
++      PopFromReadMemList
++      AddToNotifyList
++      NotifyAndPopNotifyList
++      AddToURBList
++      PopFromURBList
++
++   Internal userspace wrapper functions
++      UserspaceunlockedIOCTL
++
++   Userspace wrappers
++      UserspaceOpen
++      UserspaceIOCTL
++      UserspaceClose
++      UserspaceRead
++      UserspaceWrite
++      UserspacePoll
++
++   Initializer and destructor
++      RegisterQMIDevice
++      DeregisterQMIDevice
++
++   Driver level client management
++      QMIReady
++      QMIWDSCallback
++      SetupQMIWDSCallback
++      QMIDMSGetMEID
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++//---------------------------------------------------------------------------
++// Pragmas
++//---------------------------------------------------------------------------
++#pragma once
++
++//---------------------------------------------------------------------------
++// Include Files
++//---------------------------------------------------------------------------
++#include "Structs.h"
++#include "QMI.h"
++
++/*=========================================================================*/
++// Generic functions
++/*=========================================================================*/
++
++// Basic test to see if device memory is valid
++bool IsDeviceValid( sGobiUSBNet * pDev );
++
++// Print Hex data, for debug purposes
++void PrintHex(
++   void *         pBuffer,
++   u16            bufSize );
++
++// Sets mDownReason and turns carrier off
++void GobiSetDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason );
++
++// Clear mDownReason and may turn carrier on
++void GobiClearDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason );
++
++// Tests mDownReason and returns whether reason is set
++bool GobiTestDownReason(
++   sGobiUSBNet *    pDev,
++   u8                 reason );
++
++/*=========================================================================*/
++// Driver level asynchronous read functions
++/*=========================================================================*/
++
++// Resubmit interrupt URB, re-using same values
++int ResubmitIntURB( struct urb * pIntURB );
++
++// Read callback
++//    Put the data in storage and notify anyone waiting for data
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void ReadCallback( struct urb * pReadURB );
++#else
++void ReadCallback(struct urb *pReadURB, struct pt_regs *regs);
++#endif
++
++// Inturrupt callback
++//    Data is available, start a read URB
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void IntCallback( struct urb * pIntURB );
++#else
++void IntCallback(struct urb *pIntURB, struct pt_regs *regs);
++#endif
++
++// Start continuous read "thread"
++int StartRead( sGobiUSBNet * pDev );
++
++// Kill continuous read "thread"
++void KillRead( sGobiUSBNet * pDev );
++
++/*=========================================================================*/
++// Internal read/write functions
++/*=========================================================================*/
++
++// Start asynchronous read
++//     Reading client's data store, not device
++int ReadAsync(
++   sGobiUSBNet *    pDev,
++   u16                clientID,
++   u16                transactionID,
++   void               (*pCallback)(sGobiUSBNet *, u16, void *),
++   void *             pData );
++
++// Notification function for synchronous read
++void UpSem( 
++   sGobiUSBNet *    pDev,
++   u16                clientID,
++   void *             pData );
++
++// Start synchronous read
++//     Reading client's data store, not device
++int ReadSync(
++   sGobiUSBNet *    pDev,
++   void **            ppOutBuffer,
++   u16                clientID,
++   u16                transactionID );
++
++// Write callback
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
++void WriteSyncCallback( struct urb * pWriteURB );
++#else
++void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs);
++#endif
++
++// Start synchronous write
++int WriteSync(
++   sGobiUSBNet *    pDev,
++   char *             pInWriteBuffer,
++   int                size,
++   u16                clientID );
++
++/*=========================================================================*/
++// Internal memory management functions
++/*=========================================================================*/
++
++// Create client and allocate memory
++int GetClientID( 
++   sGobiUSBNet *      pDev,
++   u8                   serviceType );
++
++// Release client and free memory
++void ReleaseClientID(
++   sGobiUSBNet *      pDev,
++   u16                  clientID );
++
++// Find this client's memory
++sClientMemList * FindClientMem(
++   sGobiUSBNet *      pDev,
++   u16                  clientID );
++
++// Add Data to this client's ReadMem list
++bool AddToReadMemList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID,
++   void *               pData,
++   u16                  dataSize );
++
++// Remove data from this client's ReadMem list if it matches 
++// the specified transaction ID.
++bool PopFromReadMemList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID,
++   void **              ppData,
++   u16 *                pDataSize );
++
++// Add Notify entry to this client's notify List
++bool AddToNotifyList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID,
++   void                 (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
++   void *               pData );
++
++// Remove first Notify entry from this client's notify list 
++//    and Run function
++bool NotifyAndPopNotifyList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   u16                  transactionID );
++
++// Add URB to this client's URB list
++bool AddToURBList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID,
++   struct urb *         pURB );
++
++// Remove URB from this client's URB list
++struct urb * PopFromURBList( 
++   sGobiUSBNet *      pDev,
++   u16                  clientID );
++
++/*=========================================================================*/
++// Internal userspace wrappers
++/*=========================================================================*/
++
++// Userspace unlocked ioctl
++long UserspaceunlockedIOCTL(
++   struct file *     pFilp,
++   unsigned int      cmd,
++   unsigned long     arg );
++
++/*=========================================================================*/
++// Userspace wrappers
++/*=========================================================================*/
++
++// Userspace open
++int UserspaceOpen( 
++   struct inode *   pInode, 
++   struct file *    pFilp );
++
++// Userspace ioctl
++int UserspaceIOCTL( 
++   struct inode *    pUnusedInode, 
++   struct file *     pFilp,
++   unsigned int      cmd, 
++   unsigned long     arg );
++
++// Userspace close
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
++int UserspaceClose( 
++   struct file *       pFilp,
++   fl_owner_t          unusedFileTable );
++#else
++int UserspaceClose( struct file *       pFilp );
++#endif
++
++// Userspace read (synchronous)
++ssize_t UserspaceRead( 
++   struct file *        pFilp,
++   char __user *        pBuf, 
++   size_t               size,
++   loff_t *             pUnusedFpos );
++
++// Userspace write (synchronous)
++ssize_t UserspaceWrite(
++   struct file *        pFilp, 
++   const char __user *  pBuf, 
++   size_t               size,
++   loff_t *             pUnusedFpos );
++
++unsigned int UserspacePoll(
++   struct file *                  pFilp,
++   struct poll_table_struct *     pPollTable );
++
++/*=========================================================================*/
++// Initializer and destructor
++/*=========================================================================*/
++
++// QMI Device initialization function
++int RegisterQMIDevice( sGobiUSBNet * pDev );
++
++// QMI Device cleanup function
++void DeregisterQMIDevice( sGobiUSBNet * pDev );
++
++/*=========================================================================*/
++// Driver level client management
++/*=========================================================================*/
++
++// Check if QMI is ready for use
++bool QMIReady(
++   sGobiUSBNet *    pDev,
++   u16                timeout );
++
++// QMI WDS callback function
++void QMIWDSCallback(
++   sGobiUSBNet *    pDev,
++   u16                clientID,
++   void *             pData );
++
++// Fire off reqests and start async read for QMI WDS callback
++int SetupQMIWDSCallback( sGobiUSBNet * pDev );
++
++// Register client, send req and parse MEID response, release client
++int QMIDMSGetMEID( sGobiUSBNet * pDev );
++
++// Register client, send req and parse Data format response, release client
++int QMIWDASetDataFormat( sGobiUSBNet * pDev );
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMI.h linux-at91-loragw/drivers/net/usb/ec20/QMI.h
+--- linux-at91/drivers/net/usb/ec20/QMI.h	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/QMI.h	2024-05-22 15:51:24.608628371 +0800
+@@ -0,0 +1,328 @@
++/*===========================================================================
++FILE:
++   QMI.h
++
++DESCRIPTION:
++   Qualcomm QMI driver header
++   
++FUNCTIONS:
++   Generic QMUX functions
++      ParseQMUX
++      FillQMUX
++   
++   Generic QMI functions
++      GetTLV
++      ValidQMIMessage
++      GetQMIMessageID
++
++   Get sizes of buffers needed by QMI requests
++      QMUXHeaderSize
++      QMICTLGetClientIDReqSize
++      QMICTLReleaseClientIDReqSize
++      QMICTLReadyReqSize
++      QMIWDSSetEventReportReqSize
++      QMIWDSGetPKGSRVCStatusReqSize
++      QMIDMSGetMEIDReqSize
++      QMICTLSyncReqSize
++
++   Fill Buffers with QMI requests
++      QMICTLGetClientIDReq
++      QMICTLReleaseClientIDReq
++      QMICTLReadyReq
++      QMIWDSSetEventReportReq
++      QMIWDSGetPKGSRVCStatusReq
++      QMIDMSGetMEIDReq
++      QMICTLSetDataFormatReq
++      QMICTLSyncReq
++      
++   Parse data from QMI responses
++      QMICTLGetClientIDResp
++      QMICTLReleaseClientIDResp
++      QMIWDSEventResp
++      QMIDMSGetMEIDResp
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++#pragma once
++
++/*=========================================================================*/
++// Definitions
++/*=========================================================================*/
++
++extern int debug;
++// DBG macro
++#define DBG( format, arg... ) do { \
++   if (debug == 1)\
++   { \
++      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
++   } }while(0)
++
++#if 0
++#define VDBG( format, arg... ) do { \
++   if (debug == 1)\
++   { \
++      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
++   } } while(0)
++#else
++#define VDBG( format, arg... ) do { } while(0)
++#endif
++
++#define INFO( format, arg... ) do { \
++      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
++   }while(0)
++
++// QMI Service Types
++#define QMICTL 0
++#define QMIWDS 1
++#define QMIDMS 2
++#define QMINAS 3
++#define QMIUIM 11
++#define QMIWDA 0x1A
++
++#define u8        unsigned char
++#define u16       unsigned short
++#define u32       unsigned int
++#define u64       unsigned long long
++
++#define bool      u8
++#define true      1
++#define false     0
++
++#define ENOMEM    12
++#define EFAULT    14
++#define EINVAL    22
++#ifndef ENOMSG
++#define ENOMSG    42
++#endif
++#define ENODATA   61
++
++#define TLV_TYPE_LINK_PROTO 0x10
++
++/*=========================================================================*/
++// Struct sQMUX
++//
++//    Structure that defines a QMUX header
++/*=========================================================================*/
++typedef struct sQMUX
++{
++   /* T\F, always 1 */
++   u8         mTF;
++
++   /* Size of message */
++   u16        mLength;
++
++   /* Control flag */
++   u8         mCtrlFlag;
++   
++   /* Service Type */
++   u8         mQMIService;
++   
++   /* Client ID */
++   u8         mQMIClientID;
++
++}__attribute__((__packed__)) sQMUX;
++
++/*=========================================================================*/
++// Generic QMUX functions
++/*=========================================================================*/
++
++// Remove QMUX headers from a buffer
++int ParseQMUX(
++   u16 *    pClientID,
++   void *   pBuffer,
++   u16      buffSize );
++
++// Fill buffer with QMUX headers
++int FillQMUX(
++   u16      clientID,
++   void *   pBuffer,
++   u16      buffSize );
++
++/*=========================================================================*/
++// Generic QMI functions
++/*=========================================================================*/
++
++// Get data buffer of a specified TLV from a QMI message
++int GetTLV(
++   void *   pQMIMessage,
++   u16      messageLen,
++   u8       type,
++   void *   pOutDataBuf,
++   u16      bufferLen );
++
++// Check mandatory TLV in a QMI message
++int ValidQMIMessage(
++   void *   pQMIMessage,
++   u16      messageLen );
++
++// Get the message ID of a QMI message
++int GetQMIMessageID(
++   void *   pQMIMessage,
++   u16      messageLen );
++
++/*=========================================================================*/
++// Get sizes of buffers needed by QMI requests
++/*=========================================================================*/
++
++// Get size of buffer needed for QMUX
++u16 QMUXHeaderSize( void );
++
++// Get size of buffer needed for QMUX + QMICTLGetClientIDReq
++u16 QMICTLGetClientIDReqSize( void );
++
++// Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq
++u16 QMICTLReleaseClientIDReqSize( void );
++
++// Get size of buffer needed for QMUX + QMICTLReadyReq
++u16 QMICTLReadyReqSize( void );
++
++// Get size of buffer needed for QMUX + QMIWDSSetEventReportReq
++u16 QMIWDSSetEventReportReqSize( void );
++
++// Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq
++u16 QMIWDSGetPKGSRVCStatusReqSize( void );
++
++u16 QMIWDSSetQMUXBindMuxDataPortSize( void );
++
++// Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
++u16 QMIDMSGetMEIDReqSize( void );
++
++// Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
++u16 QMIWDASetDataFormatReqSize( void );
++
++// Get size of buffer needed for QMUX + QMICTLSyncReq
++u16 QMICTLSyncReqSize( void );
++
++/*=========================================================================*/
++// Fill Buffers with QMI requests
++/*=========================================================================*/
++
++// Fill buffer with QMI CTL Get Client ID Request
++int QMICTLGetClientIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID,
++   u8       serviceType );
++
++// Fill buffer with QMI CTL Release Client ID Request
++int QMICTLReleaseClientIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID,
++   u16      clientID );
++
++// Fill buffer with QMI CTL Get Version Info Request
++int QMICTLReadyReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u8       transactionID );
++
++// Fill buffer with QMI WDS Set Event Report Request
++int QMIWDSSetEventReportReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID );
++
++// Fill buffer with QMI WDS Get PKG SRVC Status Request
++int QMIWDSGetPKGSRVCStatusReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID );
++
++u16 QMIWDSSetQMUXBindMuxDataPortReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID );
++
++// Fill buffer with QMI DMS Get Serial Numbers Request
++int QMIDMSGetMEIDReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID );
++
++// Fill buffer with QMI WDA Set Data Format Request
++int QMIWDASetDataFormatReq(
++   void *   pBuffer,
++   u16      buffSize,
++   bool     bRawIPMode,
++   u16      transactionID );
++
++int QMICTLSyncReq(
++   void *   pBuffer,
++   u16      buffSize,
++   u16      transactionID );
++
++/*=========================================================================*/
++// Parse data from QMI responses
++/*=========================================================================*/
++
++// Parse the QMI CTL Get Client ID Resp
++int QMICTLGetClientIDResp(
++   void * pBuffer,
++   u16    buffSize,
++   u16 *  pClientID );
++
++// Verify the QMI CTL Release Client ID Resp is valid
++int QMICTLReleaseClientIDResp(
++   void *   pBuffer,
++   u16      buffSize );
++
++// Parse the QMI WDS Set Event Report Resp/Indication or
++//    QMI WDS Get PKG SRVC Status Resp/Indication
++int QMIWDSEventResp(
++   void *   pBuffer,
++   u16      buffSize,
++   u32 *    pTXOk,
++   u32 *    pRXOk,
++   u32 *    pTXErr,
++   u32 *    pRXErr,
++   u32 *    pTXOfl,
++   u32 *    pRXOfl,
++   u64 *    pTXBytesOk,
++   u64 *    pRXBytesOk,
++   bool *   pbLinkState,
++   bool *   pbReconfigure );
++
++// Parse the QMI DMS Get Serial Numbers Resp
++int QMIDMSGetMEIDResp(
++   void *   pBuffer,
++   u16      buffSize,
++   char *   pMEID,
++   int      meidSize );
++
++// Parse the QMI DMS Get Serial Numbers Resp
++int QMIWDASetDataFormatResp(
++   void *   pBuffer,
++   u16      buffSize, bool bRawIPMode );
++
++// Pasre the QMI CTL Sync Response
++int QMICTLSyncResp(
++   void *pBuffer,
++   u16  buffSize );
++
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/Structs.h linux-at91-loragw/drivers/net/usb/ec20/Structs.h
+--- linux-at91/drivers/net/usb/ec20/Structs.h	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/ec20/Structs.h	2024-05-22 15:51:24.608628371 +0800
+@@ -0,0 +1,442 @@
++/*===========================================================================
++FILE:
++   Structs.h
++
++DESCRIPTION:
++   Declaration of structures used by the Qualcomm Linux USB Network driver
++   
++FUNCTIONS:
++   none
++
++Copyright (c) 2011, Code Aurora Forum. All rights reserved.
++
++Redistribution and use in source and binary forms, with or without
++modification, are permitted provided that the following conditions are met:
++    * Redistributions of source code must retain the above copyright
++      notice, this list of conditions and the following disclaimer.
++    * Redistributions in binary form must reproduce the above copyright
++      notice, this list of conditions and the following disclaimer in the
++      documentation and/or other materials provided with the distribution.
++    * Neither the name of Code Aurora Forum nor
++      the names of its contributors may be used to endorse or promote
++      products derived from this software without specific prior written
++      permission.
++
++
++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++POSSIBILITY OF SUCH DAMAGE.
++===========================================================================*/
++
++//---------------------------------------------------------------------------
++// Pragmas
++//---------------------------------------------------------------------------
++#pragma once
++
++//---------------------------------------------------------------------------
++// Include Files
++//---------------------------------------------------------------------------
++#include <linux/etherdevice.h>
++#include <linux/ethtool.h>
++#include <linux/mii.h>
++#include <linux/usb.h>
++#include <linux/version.h>
++#include <linux/cdev.h>
++#include <linux/kthread.h>
++#include <linux/poll.h>
++#include <linux/completion.h>
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 ))
++static inline void skb_reset_mac_header(struct sk_buff *skb)
++{
++    skb->mac.raw = skb->data;
++}
++#endif
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
++#define bool      u8
++#ifndef URB_FREE_BUFFER
++#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self
++#define URB_FREE_BUFFER		0x0100	/* Free transfer buffer with the URB */
++#endif
++
++/**
++ * usb_endpoint_type - get the endpoint's transfer type
++ * @epd: endpoint to be checked
++ *
++ * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according
++ * to @epd's transfer type.
++ */
++static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd)
++{
++	return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
++}
++#endif
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 ))
++/**
++ * usb_endpoint_dir_in - check if the endpoint has IN direction
++ * @epd: endpoint to be checked
++ *
++ * Returns true if the endpoint is of type IN, otherwise it returns false.
++ */
++static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd)
++{
++	return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN);
++}
++
++/**
++ * usb_endpoint_dir_out - check if the endpoint has OUT direction
++ * @epd: endpoint to be checked
++ *
++ * Returns true if the endpoint is of type OUT, otherwise it returns false.
++ */
++static inline int usb_endpoint_dir_out(
++				const struct usb_endpoint_descriptor *epd)
++{
++	return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
++}
++
++/**
++ * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type
++ * @epd: endpoint to be checked
++ *
++ * Returns true if the endpoint is of type interrupt, otherwise it returns
++ * false.
++ */
++static inline int usb_endpoint_xfer_int(
++				const struct usb_endpoint_descriptor *epd)
++{
++	return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
++		USB_ENDPOINT_XFER_INT);
++}
++
++static inline int usb_autopm_set_interface(struct usb_interface *intf)
++{ return 0; }
++
++static inline int usb_autopm_get_interface(struct usb_interface *intf)
++{ return 0; }
++
++static inline int usb_autopm_get_interface_async(struct usb_interface *intf)
++{ return 0; }
++
++static inline void usb_autopm_put_interface(struct usb_interface *intf)
++{ }
++static inline void usb_autopm_put_interface_async(struct usb_interface *intf)
++{ }
++static inline void usb_autopm_enable(struct usb_interface *intf)
++{ }
++static inline void usb_autopm_disable(struct usb_interface *intf)
++{ }
++static inline void usb_mark_last_busy(struct usb_device *udev)
++{ }
++#endif
++
++#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
++   #include "usbnet.h"
++#else
++   #include <linux/usb/usbnet.h>
++#endif
++
++#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 ))
++   #include <linux/fdtable.h>
++#else
++   #include <linux/file.h>
++#endif
++
++// Used in recursion, defined later below
++struct sGobiUSBNet;
++
++/*=========================================================================*/
++// Struct sReadMemList
++//
++//    Structure that defines an entry in a Read Memory linked list
++/*=========================================================================*/
++typedef struct sReadMemList
++{
++   /* Data buffer */
++   void *                     mpData;
++   
++   /* Transaction ID */
++   u16                        mTransactionID;
++
++   /* Size of data buffer */
++   u16                        mDataSize;
++
++   /* Next entry in linked list */
++   struct sReadMemList *      mpNext;
++
++} sReadMemList;
++
++/*=========================================================================*/
++// Struct sNotifyList
++//
++//    Structure that defines an entry in a Notification linked list
++/*=========================================================================*/
++typedef struct sNotifyList
++{
++   /* Function to be run when data becomes available */
++   void                  (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *);
++   
++   /* Transaction ID */
++   u16                   mTransactionID;
++
++   /* Data to provide as parameter to mpNotifyFunct */
++   void *                mpData;
++   
++   /* Next entry in linked list */
++   struct sNotifyList *  mpNext;
++
++} sNotifyList;
++
++/*=========================================================================*/
++// Struct sURBList
++//
++//    Structure that defines an entry in a URB linked list
++/*=========================================================================*/
++typedef struct sURBList
++{
++   /* The current URB */
++   struct urb *       mpURB;
++
++   /* Next entry in linked list */
++   struct sURBList *  mpNext;
++
++} sURBList;
++
++/*=========================================================================*/
++// Struct sClientMemList
++//
++//    Structure that defines an entry in a Client Memory linked list
++//      Stores data specific to a Service Type and Client ID
++/*=========================================================================*/
++typedef struct sClientMemList
++{
++   /* Client ID for this Client */
++   u16                          mClientID;
++
++   /* Linked list of Read entries */
++   /*    Stores data read from device before sending to client */
++   sReadMemList *               mpList;
++   
++   /* Linked list of Notification entries */
++   /*    Stores notification functions to be run as data becomes 
++         available or the device is removed */
++   sNotifyList *                mpReadNotifyList;
++
++   /* Linked list of URB entries */
++   /*    Stores pointers to outstanding URBs which need canceled 
++         when the client is deregistered or the device is removed */
++   sURBList *                   mpURBList;
++   
++   /* Next entry in linked list */
++   struct sClientMemList *      mpNext;
++
++   /* Wait queue object for poll() */
++   wait_queue_head_t    mWaitQueue;
++
++} sClientMemList;
++
++/*=========================================================================*/
++// Struct sURBSetupPacket
++//
++//    Structure that defines a USB Setup packet for Control URBs
++//    Taken from USB CDC specifications
++/*=========================================================================*/
++typedef struct sURBSetupPacket
++{
++   /* Request type */
++   u8    mRequestType;
++
++   /* Request code */
++   u8    mRequestCode;
++
++   /* Value */
++   u16   mValue;
++
++   /* Index */
++   u16   mIndex;
++
++   /* Length of Control URB */
++   u16   mLength;
++
++} sURBSetupPacket;
++
++// Common value for sURBSetupPacket.mLength
++#define DEFAULT_READ_URB_LENGTH 0x1000
++
++#ifdef CONFIG_PM
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
++/*=========================================================================*/
++// Struct sAutoPM
++//
++//    Structure used to manage AutoPM thread which determines whether the
++//    device is in use or may enter autosuspend.  Also submits net 
++//    transmissions asynchronously.
++/*=========================================================================*/
++typedef struct sAutoPM
++{
++   /* Thread for atomic autopm function */
++   struct task_struct *       mpThread;
++
++   /* Signal for completion when it's time for the thread to work */
++   struct completion          mThreadDoWork;
++
++   /* Time to exit? */
++   bool                       mbExit;
++
++   /* List of URB's queued to be sent to the device */
++   sURBList *                 mpURBList;
++
++   /* URB list lock (for adding and removing elements) */
++   spinlock_t                 mURBListLock;
++
++   /* Length of the URB list */
++   atomic_t                   mURBListLen;
++   
++   /* Active URB */
++   struct urb *               mpActiveURB;
++
++   /* Active URB lock (for adding and removing elements) */
++   spinlock_t                 mActiveURBLock;
++   
++   /* Duplicate pointer to USB device interface */
++   struct usb_interface *     mpIntf;
++
++} sAutoPM;
++#endif
++#endif /* CONFIG_PM */
++
++/*=========================================================================*/
++// Struct sQMIDev
++//
++//    Structure that defines the data for the QMI device
++/*=========================================================================*/
++typedef struct sQMIDev
++{
++   /* Device number */
++   dev_t                      mDevNum;
++
++   /* Device class */
++   struct class *             mpDevClass;
++
++   /* cdev struct */
++   struct cdev                mCdev;
++
++   /* is mCdev initialized? */
++   bool                       mbCdevIsInitialized;
++
++   /* Pointer to read URB */
++   struct urb *               mpReadURB;
++
++//#define READ_QMI_URB_ERROR
++#ifdef READ_QMI_URB_ERROR
++   struct timer_list mReadUrbTimer;
++#endif
++
++   /* Read setup packet */
++   sURBSetupPacket *          mpReadSetupPacket;
++
++   /* Read buffer attached to current read URB */
++   void *                     mpReadBuffer;
++   
++   /* Inturrupt URB */
++   /*    Used to asynchronously notify when read data is available */
++   struct urb *               mpIntURB;
++
++   /* Buffer used by Inturrupt URB */
++   void *                     mpIntBuffer;
++   
++   /* Pointer to memory linked list for all clients */
++   sClientMemList *           mpClientMemList;
++   
++   /* Spinlock for client Memory entries */
++   spinlock_t                 mClientMemLock;
++
++   /* Transaction ID associated with QMICTL "client" */
++   atomic_t                   mQMICTLTransactionID;
++
++} sQMIDev;
++
++/*=========================================================================*/
++// Struct sGobiUSBNet
++//
++//    Structure that defines the data associated with the Qualcomm USB device
++/*=========================================================================*/
++typedef struct sGobiUSBNet
++{
++    atomic_t refcount;
++
++   /* Net device structure */
++   struct usbnet *        mpNetDev;
++
++#if 1 //def DATA_MODE_RP
++    bool                   mbMdm9x07;  
++   /* QMI "device" work in IP Mode or ETH Mode */
++   bool                   mbRawIPMode;
++#ifdef CONFIG_BRIDGE
++   unsigned char     mHostMAC[6];
++#endif
++#endif
++
++   struct completion mQMIReadyCompletion;
++   bool                   mbQMIReady;
++
++   /* Usb device interface */
++   struct usb_interface * mpIntf;
++
++   /* Pointers to usbnet_open and usbnet_stop functions */
++   int                  (* mpUSBNetOpen)(struct net_device *);
++   int                  (* mpUSBNetStop)(struct net_device *);
++   
++   /* Reason(s) why interface is down */
++   /* Used by Gobi*DownReason */
++   unsigned long          mDownReason;
++#define NO_NDIS_CONNECTION    0
++#define CDC_CONNECTION_SPEED  1
++#define DRIVER_SUSPENDED      2
++#define NET_IFACE_STOPPED     3
++
++   /* QMI "device" status */
++   bool                   mbQMIValid;
++
++   bool                   mbDeregisterQMIDevice;
++
++   /* QMI "device" memory */
++   sQMIDev                mQMIDev;
++
++   /* Device MEID */
++   char                   mMEID[14];
++   
++#ifdef CONFIG_PM
++   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))   
++   /* AutoPM thread */
++   sAutoPM                mAutoPM;
++#endif
++#endif /* CONFIG_PM */
++} sGobiUSBNet;
++
++/*=========================================================================*/
++// Struct sQMIFilpStorage
++//
++//    Structure that defines the storage each file handle contains
++//       Relates the file handle to a client
++/*=========================================================================*/
++typedef struct sQMIFilpStorage
++{
++   /* Client ID */
++   u16                  mClientID;
++   
++   /* Device pointer */
++   sGobiUSBNet *        mpDev;
++
++} sQMIFilpStorage;
++
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/Kconfig linux-at91-loragw/drivers/net/usb/Kconfig
+--- linux-at91/drivers/net/usb/Kconfig	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/Kconfig	2024-05-22 15:51:24.608628371 +0800
+@@ -613,4 +613,6 @@
+ 	  To compile this driver as a module, choose M here: the
+ 	  module will be called ch9200.
+ 
++source "drivers/net/usb/ec20/Kconfig"
++
+ endif # USB_NET_DRIVERS
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/Makefile linux-at91-loragw/drivers/net/usb/Makefile
+--- linux-at91/drivers/net/usb/Makefile	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/Makefile	2024-05-22 15:51:24.608628371 +0800
+@@ -40,3 +40,4 @@
+ obj-$(CONFIG_USB_NET_QMI_WWAN)	+= qmi_wwan.o
+ obj-$(CONFIG_USB_NET_CDC_MBIM)	+= cdc_mbim.o
+ obj-$(CONFIG_USB_NET_CH9200)	+= ch9200.o
++obj-$(CONFIG_EC20_GOBINET)   += ec20/
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/qmi_wwan.c linux-at91-loragw/drivers/net/usb/qmi_wwan.c
+--- linux-at91/drivers/net/usb/qmi_wwan.c	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/drivers/net/usb/qmi_wwan.c	2024-05-22 15:51:24.608628371 +0800
+@@ -1325,8 +1325,10 @@
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9225)},	/* Sony Gobi 2000 Modem device (N0279, VU730) */
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9245)},	/* Samsung Gobi 2000 Modem device (VL176) */
+ 	{QMI_GOBI_DEVICE(0x03f0, 0x251d)},	/* HP Gobi 2000 Modem device (VP412) */
++#ifndef CONFIG_EC20_GOBINET /*  add by guowenxue, conflict with EC20 GOBINET driver  */
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9215)},	/* Acer Gobi 2000 Modem device (VP413) */
+ 	{QMI_FIXED_INTF(0x05c6, 0x9215, 4)},	/* Quectel EC20 Mini PCIe */
++#endif
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9265)},	/* Asus Gobi 2000 Modem device (VR305) */
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9235)},	/* Top Global Gobi 2000 Modem device (VR306) */
+ 	{QMI_GOBI_DEVICE(0x05c6, 0x9275)},	/* iRex Technologies Gobi 2000 Modem device (VR307) */
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/usb/serial/option.c linux-at91-loragw/drivers/usb/serial/option.c
+--- linux-at91/drivers/usb/serial/option.c	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/drivers/usb/serial/option.c	2024-05-22 15:51:24.608628371 +0800
+@@ -1774,7 +1774,7 @@
+ 	{ USB_DEVICE(ALINK_VENDOR_ID, ALINK_PRODUCT_PH300) },
+ 	{ USB_DEVICE_AND_INTERFACE_INFO(ALINK_VENDOR_ID, ALINK_PRODUCT_3GU, 0xff, 0xff, 0xff) },
+ 	{ USB_DEVICE(ALINK_VENDOR_ID, SIMCOM_PRODUCT_SIM7100E),
+-	  .driver_info = RSVD(5) | RSVD(6) },
++	  .driver_info = RSVD(0) | RSVD(4) | RSVD(5) | RSVD(6) }, /* modify by guowenxue, remove Diagnostic(0), audio interface(4)  */
+ 	{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) },	/* Simcom SIM7500/SIM7600 MBIM mode */
+ 	{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff),	/* Simcom SIM7500/SIM7600 RNDIS mode */
+ 	  .driver_info = RSVD(7) },
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/usb/serial/qcserial.c linux-at91-loragw/drivers/usb/serial/qcserial.c
+--- linux-at91/drivers/usb/serial/qcserial.c	2019-07-10 18:07:41.000000000 +0800
++++ linux-at91-loragw/drivers/usb/serial/qcserial.c	2024-05-22 15:51:24.608628371 +0800
+@@ -88,7 +88,9 @@
+ 	{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) */
++#ifndef CONFIG_EC20_GOBINET /* add by guowenxue, conflict with EC20 GOBINET driver */
+ 	{USB_DEVICE(0x05c6, 0x9215)},	/* Acer Gobi 2000 Modem device (VP413) */
++#endif
+ 	{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) */
+diff -Nuar -x include-prefixes -x .gitignore linux-at91/linuxrom-loragw.its linux-at91-loragw/linuxrom-loragw.its
+--- linux-at91/linuxrom-loragw.its	1970-01-01 08:00:00.000000000 +0800
++++ linux-at91-loragw/linuxrom-loragw.its	2024-05-22 16:38:16.409073258 +0800
+@@ -0,0 +1,37 @@
++/* U-Boot uImage source file for LoRaWAN Gateway board with AT91SAM9X35 */
++ 
++/dts-v1/;
++ 
++/ {
++    description = "U-Boot uImage source file for LoRaWAN Gateway board with AT91SAM9X35";
++    #address-cells = <1>;
++ 
++    images {
++        kernel@LoRaGW {
++            description = "Linux kernel for LoRaWAN Gateway board";
++            data = /incbin/("arch/arm/boot/zImage");
++            type = "kernel";
++            arch = "arm";
++            os = "linux";
++            compression = "none";
++            load = <0x20008000>;
++            entry = <0x20008000>;
++        };
++        fdt@LoRaGW {
++            description = "Flattened Device Tree blob for LoRaWAN Gateway board";
++            data = /incbin/("arch/arm/boot/dts/at91sam9x35ek.dtb");
++            type = "flat_dt";
++            arch = "arm";
++            compression = "none";
++        };
++    };
++ 
++    configurations {
++        default = "conf@LoRaGW";
++        conf@LoRaGW {
++            description = "Boot Linux kernel with FDT blob";
++            kernel = "kernel@LoRaGW";
++            fdt = "fdt@LoRaGW";
++        };
++    };
++};

--
Gitblit v1.9.1