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<E_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<E_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