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