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-10-08 15:53:07.131777488 +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-10-08 15:53:07.131777488 +0800 @@ -42,10 +42,17 @@ atmel,pins = ; /* PB18 multidrive, conflicts with led */ }; }; + + gpio_keys_0 { /* add by guowenxue, 2019.12.10 */ + pinctrl_key_gpio: gpio_keys_0 { + atmel,pins = ; /* 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-10-08 15:53:07.135777407 +0800 @@ -713,8 +713,8 @@ i2c_gpio0 { pinctrl_i2c_gpio0: i2c_gpio0-0 { atmel,pins = - ; /* PA31 gpio multidrive I2C0 clock */ + ;/* 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-10-08 15:53:07.135777407 +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-10-08 15:56:53.791404675 +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-10-08 15:53:07.135777407 +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 +#include +#include +#include +#include +//#include + +//----------------------------------------------------------------------------- +// 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-10-08 15:53:07.135777407 +0800 @@ -0,0 +1,13 @@ +# +# Quectel EC20 GobiNet driver configuration +# Add by guowenxue 2020.12.4 +# Reference to: <> +# 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-10-08 15:53:07.135777407 +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-10-08 15:53:07.135777407 +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 +#include +#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-10-08 15:53:07.139777327 +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 +#include "QMIDevice.h" +#include + +#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 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) + #include +#else + #include +#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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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-10-08 15:53:07.139777327 +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"; + }; + }; +}; diff -Nuar -x include-prefixes -x .gitignore linux-at91/scripts/dtc/dtc-lexer.l linux-at91-loragw/scripts/dtc/dtc-lexer.l --- linux-at91/scripts/dtc/dtc-lexer.l 2019-07-10 18:07:41.000000000 +0800 +++ linux-at91-loragw/scripts/dtc/dtc-lexer.l 2024-10-08 16:38:32.557221250 +0800 @@ -38,7 +38,7 @@ #include "srcpos.h" #include "dtc-parser.tab.h" -YYLTYPE yylloc; +extern YYLTYPE yylloc; extern bool treesource_error; /* CAUTION: this will stop working if we ever use yyless() or yyunput() */