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 = <AT91_PIOB 18 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>; /* PB18 multidrive, conflicts with led */
|
};
|
};
|
+
|
+ gpio_keys_0 { /* add by guowenxue, 2019.12.10 */
|
+ pinctrl_key_gpio: gpio_keys_0 {
|
+ atmel,pins = <AT91_PIOB 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP_DEGLITCH>; /* PB15 */
|
+ };
|
+ };
|
+
|
};
|
|
rtc@fffffeb0 {
|
- status = "okay";
|
+ status = "disabled";
|
};
|
};
|
|
@@ -78,58 +85,82 @@
|
#address-cells = <1>;
|
#size-cells = <1>;
|
|
- at91bootstrap@0 {
|
- label = "at91bootstrap";
|
- reg = <0x0 0x40000>;
|
+ bootloader@0 {
|
+ label = "bootloader - 1MB";
|
+ reg = <0x0 0x100000>;
|
};
|
|
- uboot@40000 {
|
- label = "u-boot";
|
- reg = <0x40000 0xc0000>;
|
+ linux@100000 {
|
+ label = "kernel - 7MB";
|
+ reg = <0x100000 0x700000>;
|
};
|
|
- ubootenvred@100000 {
|
- label = "U-Boot Env Redundant";
|
- reg = <0x100000 0x40000>;
|
+ rootfs@8000000 {
|
+ label = "rootfs - 100MB";
|
+ reg = <0x800000 0x6400000>;
|
};
|
|
- ubootenv@140000 {
|
- label = "U-Boot Env";
|
- reg = <0x140000 0x40000>;
|
- };
|
-
|
- dtb@180000 {
|
- label = "device tree";
|
- reg = <0x180000 0x80000>;
|
- };
|
-
|
- kernel@200000 {
|
- label = "kernel";
|
- reg = <0x200000 0x600000>;
|
- };
|
+ apps@6000000 {
|
+ label = "apps - 100MB";
|
+ reg = <0x6C00000 0x6400000>;
|
+ };
|
|
- rootfs@800000 {
|
- label = "rootfs";
|
- reg = <0x800000 0x0f800000>;
|
- };
|
+ data@B000000 {
|
+ label = "data - 48MB";
|
+ reg = <0xD000000 0x3000000>;
|
+ };
|
};
|
};
|
};
|
};
|
};
|
|
+ i2c-gpio-0 { /* add ISL1208 RTC on GPIO I2C by guowenuxe, 2019.12.10 */
|
+ status = "okay";
|
+
|
+ rtc@6f {
|
+ compatible = "isil,isl1208";
|
+ reg = <0x6f>;
|
+ status="okay";
|
+ };
|
+ };
|
+
|
+ gpio_keys { /* add gpio keys by guowenxue, 2019.12.10 */
|
+ compatible = "gpio-keys";
|
+
|
+ pinctrl-names = "default";
|
+ pinctrl-0 = <&pinctrl_key_gpio>;
|
+
|
+ initkey {
|
+ label = "initkey";
|
+ gpios = <&pioB 15 GPIO_ACTIVE_LOW>;
|
+ linux,code = <0x100>;
|
+ wakeup-source;
|
+ };
|
+ };
|
+
|
leds {
|
compatible = "gpio-leds";
|
|
- pb18 {
|
- label = "pb18";
|
- gpios = <&pioB 18 GPIO_ACTIVE_LOW>;
|
+ run {
|
+ label = "sys";
|
+ gpios = <&pioB 11 GPIO_ACTIVE_LOW>;
|
linux,default-trigger = "heartbeat";
|
};
|
|
- pd21 {
|
- label = "pd21";
|
- gpios = <&pioD 21 GPIO_ACTIVE_HIGH>;
|
+ lora {
|
+ label = "lora";
|
+ gpios = <&pioB 18 GPIO_ACTIVE_LOW>;
|
+ };
|
+
|
+ eth {
|
+ label = "eth";
|
+ gpios = <&pioB 8 GPIO_ACTIVE_LOW>;
|
+ };
|
+
|
+ gprs {
|
+ label = "gprs";
|
+ gpios = <&pioB 14 GPIO_ACTIVE_LOW>;
|
};
|
};
|
|
@@ -139,7 +170,7 @@
|
linux,open-drain;
|
pinctrl-names = "default";
|
pinctrl-0 = <&pinctrl_1wire_cm>;
|
- status = "okay";
|
+ status = "disabled";
|
};
|
|
};
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x5.dtsi linux-at91-loragw/arch/arm/boot/dts/at91sam9x5.dtsi
|
--- linux-at91/arch/arm/boot/dts/at91sam9x5.dtsi 2019-07-10 18:07:41.000000000 +0800
|
+++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x5.dtsi 2024-10-08 15:53:07.135777407 +0800
|
@@ -713,8 +713,8 @@
|
i2c_gpio0 {
|
pinctrl_i2c_gpio0: i2c_gpio0-0 {
|
atmel,pins =
|
- <AT91_PIOA 30 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE /* PA30 gpio multidrive I2C0 data */
|
- AT91_PIOA 31 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>; /* PA31 gpio multidrive I2C0 clock */
|
+ <AT91_PIOA 27 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE /* PA27 gpio multidrive I2C0 data, guowenxue */
|
+ AT91_PIOA 28 AT91_PERIPH_GPIO AT91_PINCTRL_MULTI_DRIVE>;/* PA28 gpio multidrive I2C0 clock,guowenxue */
|
};
|
};
|
|
@@ -1282,8 +1282,8 @@
|
|
i2c-gpio-0 {
|
compatible = "i2c-gpio";
|
- gpios = <&pioA 30 GPIO_ACTIVE_HIGH /* sda */
|
- &pioA 31 GPIO_ACTIVE_HIGH /* scl */
|
+ gpios = <&pioA 27 GPIO_ACTIVE_HIGH /* sda guowenxue */
|
+ &pioA 28 GPIO_ACTIVE_HIGH /* scl */
|
>;
|
i2c-gpio,sda-open-drain;
|
i2c-gpio,scl-open-drain;
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/arch/arm/boot/dts/at91sam9x5ek.dtsi linux-at91-loragw/arch/arm/boot/dts/at91sam9x5ek.dtsi
|
--- linux-at91/arch/arm/boot/dts/at91sam9x5ek.dtsi 2019-07-10 18:07:41.000000000 +0800
|
+++ linux-at91-loragw/arch/arm/boot/dts/at91sam9x5ek.dtsi 2024-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 <linux/etherdevice.h>
|
+#include <linux/ethtool.h>
|
+#include <linux/module.h>
|
+#include <net/arp.h>
|
+#include <linux/ip.h>
|
+//#include <linux/udp.h>
|
+
|
+//-----------------------------------------------------------------------------
|
+// Definitions
|
+//-----------------------------------------------------------------------------
|
+
|
+// Version Information
|
+#define DRIVER_VERSION "Quectel_WCDMA<E_Linux&Android_GobiNet_Driver_V1.3.0"
|
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
|
+#define DRIVER_DESC "GobiNet"
|
+
|
+// Debug flag
|
+int debug = 0;
|
+
|
+// Allow user interrupts
|
+int interruptible = 1;
|
+
|
+// Number of IP packets which may be queued up for transmit
|
+int txQueueLength = 100;
|
+
|
+// Class should be created during module init, so needs to be global
|
+static struct class * gpClass;
|
+
|
+static const unsigned char ec20_mac[ETH_ALEN] = {0x02, 0x50, 0xf3, 0x00, 0x00, 0x00};
|
+//static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
+
|
+//#define QUECTEL_WWAN_MULTI_PACKAGES
|
+
|
+#ifdef QUECTEL_WWAN_MULTI_PACKAGES
|
+static uint __read_mostly rx_packets = 10;
|
+module_param( rx_packets, uint, S_IRUGO | S_IWUSR );
|
+
|
+#define USB_CDC_SET_MULTI_PACKAGE_COMMAND (0x5C)
|
+#define QUEC_NET_MSG_SPEC (0x80)
|
+#define QUEC_NET_MSG_ID_IP_DATA (0x00)
|
+
|
+struct multi_package_config {
|
+ __le32 enable;
|
+ __le32 package_max_len;
|
+ __le32 package_max_count_in_queue;
|
+ __le32 timeout;
|
+} __packed;
|
+
|
+struct quec_net_package_header {
|
+ unsigned char msg_spec;
|
+ unsigned char msg_id;
|
+ unsigned short payload_len;
|
+ unsigned char reserve[16];
|
+} __packed;
|
+#endif
|
+
|
+#ifdef CONFIG_BRIDGE
|
+static int __read_mostly bridge_mode = 0;
|
+static uint __read_mostly bridge_ipv4 = 0;
|
+module_param( bridge_mode, int, S_IRUGO | S_IWUSR );
|
+module_param( bridge_ipv4, uint, S_IRUGO | S_IWUSR );
|
+
|
+static int bridge_arp_reply(struct net_device *dev, struct sk_buff *skb) {
|
+ struct arphdr *parp;
|
+ u8 *arpptr, *sha;
|
+ __be32 sip, tip, ipv4;
|
+ struct sk_buff *reply = NULL;
|
+
|
+ parp = arp_hdr(skb);
|
+
|
+ if (parp->ar_hrd == htons(ARPHRD_ETHER) && parp->ar_pro == htons(ETH_P_IP)
|
+ && parp->ar_op == htons(ARPOP_REQUEST) && parp->ar_hln == 6 && parp->ar_pln == 4) {
|
+ arpptr = (u8 *)parp + sizeof(struct arphdr);
|
+ sha = arpptr;
|
+ arpptr += dev->addr_len; /* sha */
|
+ memcpy(&sip, arpptr, sizeof(sip));
|
+ arpptr += sizeof(sip);
|
+ arpptr += dev->addr_len; /* tha */
|
+ memcpy(&tip, arpptr, sizeof(tip));
|
+
|
+ ipv4 = ((bridge_ipv4 & 0xff)<<24) | ((bridge_ipv4 & 0xff00)<<8)
|
+ | ((bridge_ipv4 & 0xff0000) >> 8) |((bridge_ipv4 & 0xff000000) >> 24);
|
+ DBG("sip = 0x%08x, tip=%08x, gw=%08x\n", sip, tip, ipv4);
|
+ if (((u8 *)&tip)[0] == ((u8 *)&ipv4)[0] && ((u8 *)&tip)[1] == ((u8 *)&ipv4)[1] && ((u8 *)&tip)[2] == ((u8 *)&ipv4)[2] && ((u8 *)&tip)[3] != ((u8 *)&ipv4)[3])
|
+ reply = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, ec20_mac, sha);
|
+
|
+ if (reply) {
|
+ skb_reset_mac_header(reply);
|
+ __skb_pull(reply, skb_network_offset(reply));
|
+ reply->ip_summed = CHECKSUM_UNNECESSARY;
|
+ reply->pkt_type = PACKET_HOST;
|
+
|
+ netif_rx_ni(reply);
|
+ }
|
+ return 1;
|
+ }
|
+
|
+ return 0;
|
+}
|
+#endif
|
+
|
+#ifdef CONFIG_PM
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetSuspend (Public Method)
|
+
|
+DESCRIPTION:
|
+ Stops QMI traffic while device is suspended
|
+
|
+PARAMETERS
|
+ pIntf [ I ] - Pointer to interface
|
+ powerEvent [ I ] - Power management event
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int GobiNetSuspend(
|
+ struct usb_interface * pIntf,
|
+ pm_message_t powerEvent )
|
+{
|
+ struct usbnet * pDev;
|
+ sGobiUSBNet * pGobiDev;
|
+
|
+ if (pIntf == 0)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
|
+ pDev = usb_get_intfdata( pIntf );
|
+#else
|
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
|
+#endif
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get netdevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Is this autosuspend or system suspend?
|
+ // do we allow remote wakeup?
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+ if (pDev->udev->auto_pm == 0)
|
+#else
|
+ if (1)
|
+#endif
|
+#else
|
+ if ((powerEvent.event & PM_EVENT_AUTO) == 0)
|
+#endif
|
+ {
|
+ DBG( "device suspended to power level %d\n",
|
+ powerEvent.event );
|
+ GobiSetDownReason( pGobiDev, DRIVER_SUSPENDED );
|
+ }
|
+ else
|
+ {
|
+ DBG( "device autosuspend\n" );
|
+ }
|
+
|
+ if (powerEvent.event & PM_EVENT_SUSPEND)
|
+ {
|
+ // Stop QMI read callbacks
|
+ KillRead( pGobiDev );
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
|
+ pDev->udev->reset_resume = 0;
|
+#endif
|
+
|
+ // Store power state to avoid duplicate resumes
|
+ pIntf->dev.power.power_state.event = powerEvent.event;
|
+ }
|
+ else
|
+ {
|
+ // Other power modes cause QMI connection to be lost
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
|
+ pDev->udev->reset_resume = 1;
|
+#endif
|
+ }
|
+
|
+ // Run usbnet's suspend function
|
+ return usbnet_suspend( pIntf, powerEvent );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetResume (Public Method)
|
+
|
+DESCRIPTION:
|
+ Resume QMI traffic or recreate QMI device
|
+
|
+PARAMETERS
|
+ pIntf [ I ] - Pointer to interface
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int GobiNetResume( struct usb_interface * pIntf )
|
+{
|
+ struct usbnet * pDev;
|
+ sGobiUSBNet * pGobiDev;
|
+ int nRet;
|
+ int oldPowerState;
|
+
|
+ if (pIntf == 0)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
|
+ pDev = usb_get_intfdata( pIntf );
|
+#else
|
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
|
+#endif
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get netdevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ oldPowerState = pIntf->dev.power.power_state.event;
|
+ pIntf->dev.power.power_state.event = PM_EVENT_ON;
|
+ DBG( "resuming from power mode %d\n", oldPowerState );
|
+
|
+ if (oldPowerState & PM_EVENT_SUSPEND)
|
+ {
|
+ // It doesn't matter if this is autoresume or system resume
|
+ GobiClearDownReason( pGobiDev, DRIVER_SUSPENDED );
|
+
|
+ nRet = usbnet_resume( pIntf );
|
+ if (nRet != 0)
|
+ {
|
+ DBG( "usbnet_resume error %d\n", nRet );
|
+ return nRet;
|
+ }
|
+
|
+ // Restart QMI read callbacks
|
+ nRet = StartRead( pGobiDev );
|
+ if (nRet != 0)
|
+ {
|
+ DBG( "StartRead error %d\n", nRet );
|
+ return nRet;
|
+ }
|
+
|
+#ifdef CONFIG_PM
|
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ // Kick Auto PM thread to process any queued URBs
|
+ complete( &pGobiDev->mAutoPM.mThreadDoWork );
|
+ #endif
|
+#endif /* CONFIG_PM */
|
+ }
|
+ else
|
+ {
|
+ DBG( "nothing to resume\n" );
|
+ return 0;
|
+ }
|
+
|
+ return nRet;
|
+}
|
+#endif /* CONFIG_PM */
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetDriverBind (Public Method)
|
+
|
+DESCRIPTION:
|
+ Setup in and out pipes
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Pointer to usbnet device
|
+ pIntf [ I ] - Pointer to interface
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+static int GobiNetDriverBind(
|
+ struct usbnet * pDev,
|
+ struct usb_interface * pIntf )
|
+{
|
+ int numEndpoints;
|
+ int endpointIndex;
|
+ struct usb_host_endpoint * pEndpoint = NULL;
|
+ struct usb_host_endpoint * pIn = NULL;
|
+ struct usb_host_endpoint * pOut = NULL;
|
+
|
+ // Verify one altsetting
|
+ if (pIntf->num_altsetting != 1)
|
+ {
|
+ DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting );
|
+ return -ENODEV;
|
+ }
|
+
|
+ // Verify correct interface (4 for UC20)
|
+ if ( !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data))
|
+ {
|
+ DBG( "invalid interface %d\n",
|
+ pIntf->cur_altsetting->desc.bInterfaceNumber );
|
+ return -ENODEV;
|
+ }
|
+
|
+ if ( pIntf->cur_altsetting->desc.bInterfaceClass != 0xff)
|
+ {
|
+ struct usb_interface_descriptor *desc = &pIntf->cur_altsetting->desc;
|
+ const char *qcfg_usbnet = "UNKNOW";
|
+
|
+ if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x0e) {
|
+ qcfg_usbnet = "MBIM";
|
+ } else if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x06) {
|
+ qcfg_usbnet = "ECM";
|
+ } else if (desc->bInterfaceClass == 0xe0 && desc->bInterfaceSubClass == 1 && desc->bInterfaceProtocol == 3) {
|
+ qcfg_usbnet = "RNDIS";
|
+ }
|
+
|
+ INFO( "usbnet is %s not NDIS/RMNET!\n", qcfg_usbnet);
|
+
|
+ return -ENODEV;
|
+ }
|
+
|
+ // Collect In and Out endpoints
|
+ numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints;
|
+ for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++)
|
+ {
|
+ pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex;
|
+ if (pEndpoint == NULL)
|
+ {
|
+ DBG( "invalid endpoint %u\n", endpointIndex );
|
+ return -ENODEV;
|
+ }
|
+
|
+ if (usb_endpoint_dir_in( &pEndpoint->desc ) == true
|
+ && usb_endpoint_xfer_int( &pEndpoint->desc ) == false)
|
+ {
|
+ pIn = pEndpoint;
|
+ }
|
+ else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true)
|
+ {
|
+ pOut = pEndpoint;
|
+ }
|
+ }
|
+
|
+ if (pIn == NULL || pOut == NULL)
|
+ {
|
+ DBG( "invalid endpoints\n" );
|
+ return -ENODEV;
|
+ }
|
+
|
+ if (usb_set_interface( pDev->udev,
|
+ pIntf->cur_altsetting->desc.bInterfaceNumber,
|
+ 0 ) != 0)
|
+ {
|
+ DBG( "unable to set interface\n" );
|
+ return -ENODEV;
|
+ }
|
+
|
+ pDev->in = usb_rcvbulkpipe( pDev->udev,
|
+ pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
|
+ pDev->out = usb_sndbulkpipe( pDev->udev,
|
+ pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
|
+
|
+#ifdef QUECTEL_WWAN_MULTI_PACKAGES
|
+ if (rx_packets && pDev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
|
+ struct multi_package_config rx_config = {
|
+ .enable = cpu_to_le32(1),
|
+ .package_max_len = cpu_to_le32((1500 + sizeof(struct quec_net_package_header)) * rx_packets),
|
+ .package_max_count_in_queue = cpu_to_le32(rx_packets),
|
+ .timeout = cpu_to_le32(10*1000), //10ms
|
+ };
|
+ int ret = 0;
|
+
|
+ ret = usb_control_msg(
|
+ interface_to_usbdev(pIntf),
|
+ usb_sndctrlpipe(interface_to_usbdev(pIntf), 0),
|
+ USB_CDC_SET_MULTI_PACKAGE_COMMAND,
|
+ 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
|
+ 1,
|
+ pIntf->cur_altsetting->desc.bInterfaceNumber,
|
+ &rx_config, sizeof(rx_config), 100);
|
+
|
+ DBG( "Quectel EC21&EC25 rx_packets=%d, ret=%d\n", rx_packets, ret);
|
+ if (ret == sizeof(rx_config)) {
|
+ pDev->rx_urb_size = le32_to_cpu(rx_config.package_max_len);
|
+ } else {
|
+ rx_packets = 0;
|
+ }
|
+ }
|
+#endif
|
+
|
+#if 1 //def DATA_MODE_RP
|
+ /* make MAC addr easily distinguishable from an IP header */
|
+ if ((pDev->net->dev_addr[0] & 0xd0) == 0x40) {
|
+ /*clear this bit wil make usbnet apdater named as usbX(instead if ethX)*/
|
+ pDev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
|
+ pDev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
|
+ }
|
+#endif
|
+
|
+ DBG( "in %x, out %x\n",
|
+ pIn->desc.bEndpointAddress,
|
+ pOut->desc.bEndpointAddress );
|
+
|
+ // In later versions of the kernel, usbnet helps with this
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
|
+ pIntf->dev.platform_data = (void *)pDev;
|
+#endif
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetDriverUnbind (Public Method)
|
+
|
+DESCRIPTION:
|
+ Deregisters QMI device (Registration happened in the probe function)
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Pointer to usbnet device
|
+ pIntfUnused [ I ] - Pointer to interface
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+static void GobiNetDriverUnbind(
|
+ struct usbnet * pDev,
|
+ struct usb_interface * pIntf)
|
+{
|
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+
|
+ // Should already be down, but just in case...
|
+ netif_carrier_off( pDev->net );
|
+
|
+ DeregisterQMIDevice( pGobiDev );
|
+
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
|
+ kfree( pDev->net->netdev_ops );
|
+ pDev->net->netdev_ops = NULL;
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
|
+ pIntf->dev.platform_data = NULL;
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 ))
|
+ pIntf->needs_remote_wakeup = 0;
|
+#endif
|
+
|
+ if (atomic_dec_and_test(&pGobiDev->refcount))
|
+ kfree( pGobiDev );
|
+ else
|
+ DBG("memory leak!\n");
|
+}
|
+
|
+#if 1 //def DATA_MODE_RP
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetDriverTxFixup (Public Method)
|
+
|
+DESCRIPTION:
|
+ Handling data format mode on transmit path
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Pointer to usbnet device
|
+ pSKB [ I ] - Pointer to transmit packet buffer
|
+ flags [ I ] - os flags
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+struct sk_buff *GobiNetDriverTxFixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
|
+{
|
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
|
+
|
+ if (!pGobiDev) {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ dev_kfree_skb_any(skb);
|
+ return NULL;
|
+ }
|
+
|
+ if (!pGobiDev->mbRawIPMode)
|
+ return skb;
|
+
|
+#ifdef CONFIG_BRIDGE
|
+ if (bridge_mode) {
|
+ struct ethhdr *ehdr = eth_hdr(skb);
|
+ const struct iphdr *iph;
|
+
|
+//debug = 1;
|
+// DBG("ethhdr: ");
|
+// PrintHex(ehdr, sizeof(struct ethhdr));
|
+
|
+ if (ehdr->h_proto == htons(ETH_P_ARP)) {
|
+ bridge_arp_reply(dev->net, skb);
|
+ dev_kfree_skb_any(skb);
|
+ return NULL;
|
+ }
|
+
|
+ iph = ip_hdr(skb);
|
+ //DBG("iphdr: ");
|
+ //PrintHex((void *)iph, sizeof(struct iphdr));
|
+
|
+// 1 0.000000000 0.0.0.0 255.255.255.255 DHCP 362 DHCP Request - Transaction ID 0xe7643ad7
|
+ if (ehdr->h_proto == htons(ETH_P_IP) && iph->protocol == IPPROTO_UDP && iph->saddr == 0x00000000 && iph->daddr == 0xFFFFFFFF) {
|
+ //DBG("udphdr: ");
|
+ //PrintHex(udp_hdr(skb), sizeof(struct udphdr));
|
+
|
+ //if (udp_hdr(skb)->dest == htons(67)) //DHCP Request
|
+ {
|
+ memcpy(pGobiDev->mHostMAC, ehdr->h_source, ETH_ALEN);
|
+ DBG("PC Mac Address: ");
|
+ PrintHex(pGobiDev->mHostMAC, ETH_ALEN);
|
+ }
|
+ }
|
+
|
+#if 0
|
+//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv4mcast_7f:ff:fa (01:00:5e:7f:ff:fa)
|
+//126 85.213727000 10.184.164.175 239.255.255.250 SSDP 175 M-SEARCH * HTTP/1.1
|
+//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv6mcast_16 (33:33:00:00:00:16)
|
+//160 110.305488000 fe80::6819:38ad:fcdc:2444 ff02::16 ICMPv6 90 Multicast Listener Report Message v2
|
+ if (memcmp(ehdr->h_dest, ec20_mac, ETH_ALEN) && memcmp(ehdr->h_dest, broadcast_addr, ETH_ALEN)) {
|
+ DBG("Drop h_dest: ");
|
+ PrintHex(ehdr, sizeof(struct ethhdr));
|
+ dev_kfree_skb_any(skb);
|
+ return NULL;
|
+ }
|
+#endif
|
+
|
+ if (memcmp(ehdr->h_source, pGobiDev->mHostMAC, ETH_ALEN)) {
|
+ DBG("Drop h_source: ");
|
+ PrintHex(ehdr, sizeof(struct ethhdr));
|
+ dev_kfree_skb_any(skb);
|
+ return NULL;
|
+ }
|
+
|
+//debug = 0;
|
+ }
|
+#endif
|
+
|
+ // Skip Ethernet header from message
|
+ if (skb_pull(skb, ETH_HLEN)) {
|
+ return skb;
|
+ } else {
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
|
+ dev_err(&dev->intf->dev, "Packet Dropped ");
|
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+ dev_err(dev->net->dev.parent, "Packet Dropped ");
|
+#else
|
+ INFO("Packet Dropped ");
|
+#endif
|
+ }
|
+
|
+ // Filter the packet out, release it
|
+ dev_kfree_skb_any(skb);
|
+ return NULL;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiNetDriverRxFixup (Public Method)
|
+
|
+DESCRIPTION:
|
+ Handling data format mode on receive path
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Pointer to usbnet device
|
+ pSKB [ I ] - Pointer to received packet buffer
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+static int GobiNetDriverRxFixup(struct usbnet *dev, struct sk_buff *skb)
|
+{
|
+ __be16 proto;
|
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
|
+
|
+ if (!pGobiDev->mbRawIPMode)
|
+ return 1;
|
+
|
+ /* This check is no longer done by usbnet */
|
+ if (skb->len < dev->net->hard_header_len)
|
+ return 0;
|
+
|
+ switch (skb->data[0] & 0xf0) {
|
+ case 0x40:
|
+ proto = htons(ETH_P_IP);
|
+ break;
|
+ case 0x60:
|
+ proto = htons(ETH_P_IPV6);
|
+ break;
|
+ case 0x00:
|
+ if (is_multicast_ether_addr(skb->data))
|
+ return 1;
|
+ /* possibly bogus destination - rewrite just in case */
|
+ skb_reset_mac_header(skb);
|
+ goto fix_dest;
|
+ default:
|
+ /* pass along other packets without modifications */
|
+ return 1;
|
+ }
|
+ if (skb_headroom(skb) < ETH_HLEN && pskb_expand_head(skb, ETH_HLEN, 0, GFP_ATOMIC)) {
|
+ DBG("%s: couldn't pskb_expand_head\n", __func__);
|
+ return 0;
|
+ }
|
+ skb_push(skb, ETH_HLEN);
|
+ skb_reset_mac_header(skb);
|
+ eth_hdr(skb)->h_proto = proto;
|
+ memcpy(eth_hdr(skb)->h_source, ec20_mac, ETH_ALEN);
|
+fix_dest:
|
+#ifdef CONFIG_BRIDGE
|
+ if (bridge_mode) {
|
+ memcpy(eth_hdr(skb)->h_dest, pGobiDev->mHostMAC, ETH_ALEN);
|
+ //memcpy(eth_hdr(skb)->h_dest, broadcast_addr, ETH_ALEN);
|
+ } else
|
+#endif
|
+ memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
|
+
|
+#ifdef CONFIG_BRIDGE
|
+#if 0
|
+ if (bridge_mode) {
|
+ struct ethhdr *ehdr = eth_hdr(skb);
|
+debug = 1;
|
+ DBG(": ");
|
+ PrintHex(ehdr, sizeof(struct ethhdr));
|
+debug = 0;
|
+ }
|
+#endif
|
+#endif
|
+
|
+ return 1;
|
+}
|
+
|
+#ifdef QUECTEL_WWAN_MULTI_PACKAGES
|
+static int GobiNetDriverRxPktsFixup(struct usbnet *dev, struct sk_buff *skb)
|
+{
|
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
|
+
|
+ if (!pGobiDev->mbRawIPMode)
|
+ return 1;
|
+
|
+ /* This check is no longer done by usbnet */
|
+ if (skb->len < dev->net->hard_header_len)
|
+ return 0;
|
+
|
+ if (!rx_packets) {
|
+ return GobiNetDriverRxFixup(dev, skb);
|
+ }
|
+
|
+ while (likely(skb->len)) {
|
+ struct sk_buff* new_skb;
|
+ struct quec_net_package_header package_header;
|
+
|
+ if (skb->len < sizeof(package_header))
|
+ return 0;
|
+
|
+ memcpy(&package_header, skb->data, sizeof(package_header));
|
+ package_header.payload_len = be16_to_cpu(package_header.payload_len);
|
+
|
+ if (package_header.msg_spec != QUEC_NET_MSG_SPEC || package_header.msg_id != QUEC_NET_MSG_ID_IP_DATA)
|
+ return 0;
|
+
|
+ if (skb->len < (package_header.payload_len + sizeof(package_header)))
|
+ return 0;
|
+
|
+ skb_pull(skb, sizeof(package_header));
|
+
|
+ if (skb->len == package_header.payload_len)
|
+ return GobiNetDriverRxFixup(dev, skb);
|
+
|
+ new_skb = skb_clone(skb, GFP_ATOMIC);
|
+ if (new_skb) {
|
+ skb_trim(new_skb, package_header.payload_len);
|
+ if (GobiNetDriverRxFixup(dev, new_skb))
|
+ usbnet_skb_return(dev, new_skb);
|
+ else
|
+ return 0;
|
+ }
|
+
|
+ skb_pull(skb, package_header.payload_len);
|
+ }
|
+
|
+ return 0;
|
+}
|
+#endif
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+#ifdef CONFIG_PM
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetURBCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ Write is complete, cleanup and signal that we're ready for next packet
|
+
|
+PARAMETERS
|
+ pURB [ I ] - Pointer to sAutoPM struct
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+void GobiUSBNetURBCallback( struct urb * pURB )
|
+#else
|
+void GobiUSBNetURBCallback(struct urb *pURB, struct pt_regs *regs)
|
+#endif
|
+{
|
+ unsigned long activeURBflags;
|
+ sAutoPM * pAutoPM = (sAutoPM *)pURB->context;
|
+ if (pAutoPM == NULL)
|
+ {
|
+ // Should never happen
|
+ DBG( "bad context\n" );
|
+ return;
|
+ }
|
+
|
+ if (pURB->status != 0)
|
+ {
|
+ // Note that in case of an error, the behaviour is no different
|
+ DBG( "urb finished with error %d\n", pURB->status );
|
+ }
|
+
|
+ // Remove activeURB (memory to be freed later)
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // EAGAIN used to signify callback is done
|
+ pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN );
|
+
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ complete( &pAutoPM->mThreadDoWork );
|
+
|
+#ifdef URB_FREE_BUFFER_BY_SELF
|
+ if (pURB->transfer_flags & URB_FREE_BUFFER)
|
+ kfree(pURB->transfer_buffer);
|
+#endif
|
+ usb_free_urb( pURB );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetTXTimeout (Public Method)
|
+
|
+DESCRIPTION:
|
+ Timeout declared by the net driver. Stop all transfers
|
+
|
+PARAMETERS
|
+ pNet [ I ] - Pointer to net device
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void GobiUSBNetTXTimeout( struct net_device * pNet )
|
+{
|
+ struct sGobiUSBNet * pGobiDev;
|
+ sAutoPM * pAutoPM;
|
+ sURBList * pURBListEntry;
|
+ unsigned long activeURBflags, URBListFlags;
|
+ struct usbnet * pDev = netdev_priv( pNet );
|
+ struct urb * pURB;
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get usbnet device\n" );
|
+ return;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return;
|
+ }
|
+ pAutoPM = &pGobiDev->mAutoPM;
|
+
|
+ DBG( "\n" );
|
+
|
+ // Grab a pointer to active URB
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ pURB = pAutoPM->mpActiveURB;
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ // Stop active URB
|
+ if (pURB != NULL)
|
+ {
|
+ usb_kill_urb( pURB );
|
+ }
|
+
|
+ // Cleanup URB List
|
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ pURBListEntry = pAutoPM->mpURBList;
|
+ while (pURBListEntry != NULL)
|
+ {
|
+ pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
|
+ atomic_dec( &pAutoPM->mURBListLen );
|
+ usb_free_urb( pURBListEntry->mpURB );
|
+ kfree( pURBListEntry );
|
+ pURBListEntry = pAutoPM->mpURBList;
|
+ }
|
+
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ complete( &pAutoPM->mThreadDoWork );
|
+
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetAutoPMThread (Public Method)
|
+
|
+DESCRIPTION:
|
+ Handle device Auto PM state asynchronously
|
+ Handle network packet transmission asynchronously
|
+
|
+PARAMETERS
|
+ pData [ I ] - Pointer to sAutoPM struct
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+static int GobiUSBNetAutoPMThread( void * pData )
|
+{
|
+ unsigned long activeURBflags, URBListFlags;
|
+ sURBList * pURBListEntry;
|
+ int status;
|
+ struct usb_device * pUdev;
|
+ sAutoPM * pAutoPM = (sAutoPM *)pData;
|
+ struct urb * pURB;
|
+
|
+ if (pAutoPM == NULL)
|
+ {
|
+ DBG( "passed null pointer\n" );
|
+ return -EINVAL;
|
+ }
|
+
|
+ pUdev = interface_to_usbdev( pAutoPM->mpIntf );
|
+
|
+ DBG( "traffic thread started\n" );
|
+
|
+ while (pAutoPM->mbExit == false)
|
+ {
|
+ // Wait for someone to poke us
|
+ wait_for_completion_interruptible( &pAutoPM->mThreadDoWork );
|
+
|
+ // Time to exit?
|
+ if (pAutoPM->mbExit == true)
|
+ {
|
+ // Stop activeURB
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ pURB = pAutoPM->mpActiveURB;
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // EAGAIN used to signify callback is done
|
+ if (IS_ERR( pAutoPM->mpActiveURB )
|
+ && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
|
+ {
|
+ pURB = NULL;
|
+ }
|
+
|
+ if (pURB != NULL)
|
+ {
|
+ usb_kill_urb( pURB );
|
+ }
|
+ // Will be freed in callback function
|
+
|
+ // Cleanup URB List
|
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ pURBListEntry = pAutoPM->mpURBList;
|
+ while (pURBListEntry != NULL)
|
+ {
|
+ pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
|
+ atomic_dec( &pAutoPM->mURBListLen );
|
+ usb_free_urb( pURBListEntry->mpURB );
|
+ kfree( pURBListEntry );
|
+ pURBListEntry = pAutoPM->mpURBList;
|
+ }
|
+
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ break;
|
+ }
|
+
|
+ // Is our URB active?
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // EAGAIN used to signify callback is done
|
+ if (IS_ERR( pAutoPM->mpActiveURB )
|
+ && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
|
+ {
|
+ pAutoPM->mpActiveURB = NULL;
|
+
|
+ // Restore IRQs so task can sleep
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // URB is done, decrement the Auto PM usage count
|
+ usb_autopm_put_interface( pAutoPM->mpIntf );
|
+
|
+ // Lock ActiveURB again
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ }
|
+
|
+ if (pAutoPM->mpActiveURB != NULL)
|
+ {
|
+ // There is already a URB active, go back to sleep
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ continue;
|
+ }
|
+
|
+ // Is there a URB waiting to be submitted?
|
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
|
+ if (pAutoPM->mpURBList == NULL)
|
+ {
|
+ // No more URBs to submit, go back to sleep
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ continue;
|
+ }
|
+
|
+ // Pop an element
|
+ pURBListEntry = pAutoPM->mpURBList;
|
+ pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
|
+ atomic_dec( &pAutoPM->mURBListLen );
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ // Set ActiveURB
|
+ pAutoPM->mpActiveURB = pURBListEntry->mpURB;
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // Tell autopm core we need device woken up
|
+ status = usb_autopm_get_interface( pAutoPM->mpIntf );
|
+ if (status < 0)
|
+ {
|
+ DBG( "unable to autoresume interface: %d\n", status );
|
+
|
+ // likely caused by device going from autosuspend -> full suspend
|
+ if (status == -EPERM)
|
+ {
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+ pUdev->auto_pm = 0;
|
+#else
|
+ pUdev = pUdev;
|
+#endif
|
+#endif
|
+ GobiNetSuspend( pAutoPM->mpIntf, PMSG_SUSPEND );
|
+ }
|
+
|
+ // Add pURBListEntry back onto pAutoPM->mpURBList
|
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
|
+ pURBListEntry->mpNext = pAutoPM->mpURBList;
|
+ pAutoPM->mpURBList = pURBListEntry;
|
+ atomic_inc( &pAutoPM->mURBListLen );
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ pAutoPM->mpActiveURB = NULL;
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+
|
+ // Go back to sleep
|
+ continue;
|
+ }
|
+
|
+ // Submit URB
|
+ status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL );
|
+ if (status < 0)
|
+ {
|
+ // Could happen for a number of reasons
|
+ DBG( "Failed to submit URB: %d. Packet dropped\n", status );
|
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ usb_free_urb( pAutoPM->mpActiveURB );
|
+ pAutoPM->mpActiveURB = NULL;
|
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
|
+ usb_autopm_put_interface( pAutoPM->mpIntf );
|
+
|
+ // Loop again
|
+ complete( &pAutoPM->mThreadDoWork );
|
+ }
|
+
|
+ kfree( pURBListEntry );
|
+ }
|
+
|
+ DBG( "traffic thread exiting\n" );
|
+ pAutoPM->mpThread = NULL;
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetStartXmit (Public Method)
|
+
|
+DESCRIPTION:
|
+ Convert sk_buff to usb URB and queue for transmit
|
+
|
+PARAMETERS
|
+ pNet [ I ] - Pointer to net device
|
+
|
+RETURN VALUE:
|
+ NETDEV_TX_OK on success
|
+ NETDEV_TX_BUSY on error
|
+===========================================================================*/
|
+int GobiUSBNetStartXmit(
|
+ struct sk_buff * pSKB,
|
+ struct net_device * pNet )
|
+{
|
+ unsigned long URBListFlags;
|
+ struct sGobiUSBNet * pGobiDev;
|
+ sAutoPM * pAutoPM;
|
+ sURBList * pURBListEntry, ** ppURBListEnd;
|
+ void * pURBData;
|
+ struct usbnet * pDev = netdev_priv( pNet );
|
+
|
+ //DBG( "\n" );
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get usbnet device\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+ pAutoPM = &pGobiDev->mAutoPM;
|
+
|
+ if( NULL == pSKB )
|
+ {
|
+ DBG( "Buffer is NULL \n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
|
+ {
|
+ // Should not happen
|
+ DBG( "device is suspended\n" );
|
+ dump_stack();
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION ))
|
+ {
|
+ //netif_carrier_off( pGobiDev->mpNetDev->net );
|
+ //DBG( "device is disconnected\n" );
|
+ //dump_stack();
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ // Convert the sk_buff into a URB
|
+
|
+ // Check if buffer is full
|
+ if ( atomic_read( &pAutoPM->mURBListLen ) >= txQueueLength)
|
+ {
|
+ DBG( "not scheduling request, buffer is full\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ // Allocate URBListEntry
|
+ pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
|
+ if (pURBListEntry == NULL)
|
+ {
|
+ DBG( "unable to allocate URBList memory\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+ pURBListEntry->mpNext = NULL;
|
+
|
+ // Allocate URB
|
+ pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC );
|
+ if (pURBListEntry->mpURB == NULL)
|
+ {
|
+ DBG( "unable to allocate URB\n" );
|
+ // release all memory allocated by now
|
+ if (pURBListEntry)
|
+ kfree( pURBListEntry );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+#if 1 //def DATA_MODE_RP
|
+ GobiNetDriverTxFixup(pDev, pSKB, GFP_ATOMIC);
|
+#endif
|
+
|
+ // Allocate URB transfer_buffer
|
+ pURBData = kmalloc( pSKB->len, GFP_ATOMIC );
|
+ if (pURBData == NULL)
|
+ {
|
+ DBG( "unable to allocate URB data\n" );
|
+ // release all memory allocated by now
|
+ if (pURBListEntry)
|
+ {
|
+ usb_free_urb( pURBListEntry->mpURB );
|
+ kfree( pURBListEntry );
|
+ }
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+ // Fill with SKB's data
|
+ memcpy( pURBData, pSKB->data, pSKB->len );
|
+
|
+ usb_fill_bulk_urb( pURBListEntry->mpURB,
|
+ pGobiDev->mpNetDev->udev,
|
+ pGobiDev->mpNetDev->out,
|
+ pURBData,
|
+ pSKB->len,
|
+ GobiUSBNetURBCallback,
|
+ pAutoPM );
|
+
|
+ /* Handle the need to send a zero length packet and release the
|
+ * transfer buffer
|
+ */
|
+ pURBListEntry->mpURB->transfer_flags |= (URB_ZERO_PACKET | URB_FREE_BUFFER);
|
+
|
+ // Aquire lock on URBList
|
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ // Add URB to end of list
|
+ ppURBListEnd = &pAutoPM->mpURBList;
|
+ while ((*ppURBListEnd) != NULL)
|
+ {
|
+ ppURBListEnd = &(*ppURBListEnd)->mpNext;
|
+ }
|
+ *ppURBListEnd = pURBListEntry;
|
+ atomic_inc( &pAutoPM->mURBListLen );
|
+
|
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
|
+
|
+ complete( &pAutoPM->mThreadDoWork );
|
+
|
+ // Start transfer timer
|
+ pNet->trans_start = jiffies;
|
+ // Free SKB
|
+ if (pSKB)
|
+ dev_kfree_skb_any( pSKB );
|
+
|
+ return NETDEV_TX_OK;
|
+}
|
+#endif
|
+static int (*local_usbnet_start_xmit) (struct sk_buff *skb, struct net_device *net);
|
+#endif
|
+
|
+static int GobiUSBNetStartXmit2( struct sk_buff *pSKB, struct net_device *pNet ){
|
+ struct sGobiUSBNet * pGobiDev;
|
+ struct usbnet * pDev = netdev_priv( pNet );
|
+
|
+ //DBG( "\n" );
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get usbnet device\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ if( NULL == pSKB )
|
+ {
|
+ DBG( "Buffer is NULL \n" );
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
|
+ {
|
+ // Should not happen
|
+ DBG( "device is suspended\n" );
|
+ dump_stack();
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+ if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION ))
|
+ {
|
+ //netif_carrier_off( pGobiDev->mpNetDev->net );
|
+ //DBG( "device is disconnected\n" );
|
+ //dump_stack();
|
+ return NETDEV_TX_BUSY;
|
+ }
|
+
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ return local_usbnet_start_xmit(pSKB, pNet);
|
+#else
|
+ return usbnet_start_xmit(pSKB, pNet);
|
+#endif
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetOpen (Public Method)
|
+
|
+DESCRIPTION:
|
+ Wrapper to usbnet_open, correctly handling autosuspend
|
+ Start AutoPM thread (if CONFIG_PM is defined)
|
+
|
+PARAMETERS
|
+ pNet [ I ] - Pointer to net device
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GobiUSBNetOpen( struct net_device * pNet )
|
+{
|
+ int status = 0;
|
+ struct sGobiUSBNet * pGobiDev;
|
+ struct usbnet * pDev = netdev_priv( pNet );
|
+
|
+ if (pDev == NULL)
|
+ {
|
+ DBG( "failed to get usbnet device\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ DBG( "\n" );
|
+
|
+#ifdef CONFIG_PM
|
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ // Start the AutoPM thread
|
+ pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf;
|
+ pGobiDev->mAutoPM.mbExit = false;
|
+ pGobiDev->mAutoPM.mpURBList = NULL;
|
+ pGobiDev->mAutoPM.mpActiveURB = NULL;
|
+ spin_lock_init( &pGobiDev->mAutoPM.mURBListLock );
|
+ spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock );
|
+ atomic_set( &pGobiDev->mAutoPM.mURBListLen, 0 );
|
+ init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
|
+
|
+ pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread,
|
+ &pGobiDev->mAutoPM,
|
+ "GobiUSBNetAutoPMThread" );
|
+ if (IS_ERR( pGobiDev->mAutoPM.mpThread ))
|
+ {
|
+ DBG( "AutoPM thread creation error\n" );
|
+ return PTR_ERR( pGobiDev->mAutoPM.mpThread );
|
+ }
|
+ #endif
|
+#endif /* CONFIG_PM */
|
+
|
+ // Allow traffic
|
+ GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED );
|
+
|
+ // Pass to usbnet_open if defined
|
+ if (pGobiDev->mpUSBNetOpen != NULL)
|
+ {
|
+ status = pGobiDev->mpUSBNetOpen( pNet );
|
+#ifdef CONFIG_PM
|
+ // If usbnet_open was successful enable Auto PM
|
+ if (status == 0)
|
+ {
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
|
+ usb_autopm_enable( pGobiDev->mpIntf );
|
+#else
|
+ usb_autopm_put_interface( pGobiDev->mpIntf );
|
+#endif
|
+ }
|
+#endif /* CONFIG_PM */
|
+ }
|
+ else
|
+ {
|
+ DBG( "no USBNetOpen defined\n" );
|
+ }
|
+
|
+ return status;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetStop (Public Method)
|
+
|
+DESCRIPTION:
|
+ Wrapper to usbnet_stop, correctly handling autosuspend
|
+ Stop AutoPM thread (if CONFIG_PM is defined)
|
+
|
+PARAMETERS
|
+ pNet [ I ] - Pointer to net device
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GobiUSBNetStop( struct net_device * pNet )
|
+{
|
+ struct sGobiUSBNet * pGobiDev;
|
+ struct usbnet * pDev = netdev_priv( pNet );
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get netdevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "failed to get QMIDevice\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Stop traffic
|
+ GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
|
+
|
+#ifdef CONFIG_PM
|
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ // Tell traffic thread to exit
|
+ pGobiDev->mAutoPM.mbExit = true;
|
+ complete( &pGobiDev->mAutoPM.mThreadDoWork );
|
+
|
+ // Wait for it to exit
|
+ while( pGobiDev->mAutoPM.mpThread != NULL )
|
+ {
|
+ msleep( 100 );
|
+ }
|
+ DBG( "thread stopped\n" );
|
+ #endif
|
+#endif /* CONFIG_PM */
|
+
|
+ // Pass to usbnet_stop, if defined
|
+ if (pGobiDev->mpUSBNetStop != NULL)
|
+ {
|
+ return pGobiDev->mpUSBNetStop( pNet );
|
+ }
|
+ else
|
+ {
|
+ return 0;
|
+ }
|
+}
|
+
|
+/*=========================================================================*/
|
+// Struct driver_info
|
+/*=========================================================================*/
|
+static const struct driver_info GobiNetInfo =
|
+{
|
+ .description = "GobiNet Ethernet Device",
|
+#ifdef CONFIG_ANDROID
|
+ .flags = FLAG_ETHER | FLAG_POINTTOPOINT, //usb0
|
+#else
|
+ .flags = FLAG_ETHER,
|
+#endif
|
+ .bind = GobiNetDriverBind,
|
+ .unbind = GobiNetDriverUnbind,
|
+#if 1 //def DATA_MODE_RP
|
+#ifdef QUECTEL_WWAN_MULTI_PACKAGES
|
+ .rx_fixup = GobiNetDriverRxPktsFixup,
|
+#else
|
+ .rx_fixup = GobiNetDriverRxFixup,
|
+#endif
|
+ .tx_fixup = GobiNetDriverTxFixup,
|
+#endif
|
+ .data = (1 << 4),
|
+};
|
+
|
+/*=========================================================================*/
|
+// Qualcomm Gobi 3000 VID/PIDs
|
+/*=========================================================================*/
|
+#define GOBI_FIXED_INTF(vend, prod) \
|
+ { \
|
+ USB_DEVICE( vend, prod ), \
|
+ .driver_info = (unsigned long)&GobiNetInfo, \
|
+ }
|
+static const struct usb_device_id GobiVIDPIDTable [] =
|
+{
|
+ GOBI_FIXED_INTF( 0x05c6, 0x9003 ), // Quectel UC20
|
+ GOBI_FIXED_INTF( 0x05c6, 0x9215 ), // Quectel EC20
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0125 ), // Quectel EC25
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0121 ), // Quectel EC21
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0306 ), // Quectel EP06
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0435 ), // Quectel AG35
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0296 ), // Quectel BG96
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0191 ), // Quectel EG91
|
+ GOBI_FIXED_INTF( 0x2c7c, 0x0195 ), // Quectel EG95
|
+ //Terminating entry
|
+ { }
|
+};
|
+
|
+MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetProbe (Public Method)
|
+
|
+DESCRIPTION:
|
+ Run usbnet_probe
|
+ Setup QMI device
|
+
|
+PARAMETERS
|
+ pIntf [ I ] - Pointer to interface
|
+ pVIDPIDs [ I ] - Pointer to VID/PID table
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GobiUSBNetProbe(
|
+ struct usb_interface * pIntf,
|
+ const struct usb_device_id * pVIDPIDs )
|
+{
|
+ int status;
|
+ struct usbnet * pDev;
|
+ sGobiUSBNet * pGobiDev;
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
|
+ struct net_device_ops * pNetDevOps;
|
+#endif
|
+
|
+ status = usbnet_probe( pIntf, pVIDPIDs );
|
+ if (status < 0)
|
+ {
|
+ DBG( "usbnet_probe failed %d\n", status );
|
+ return status;
|
+ }
|
+
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 ))
|
+ pIntf->needs_remote_wakeup = 1;
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
|
+ pDev = usb_get_intfdata( pIntf );
|
+#else
|
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
|
+#endif
|
+
|
+ if (pDev == NULL || pDev->net == NULL)
|
+ {
|
+ DBG( "failed to get netdevice\n" );
|
+ usbnet_disconnect( pIntf );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pGobiDev = kzalloc( sizeof( sGobiUSBNet ), GFP_KERNEL );
|
+ if (pGobiDev == NULL)
|
+ {
|
+ DBG( "falied to allocate device buffers" );
|
+ usbnet_disconnect( pIntf );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ atomic_set(&pGobiDev->refcount, 1);
|
+
|
+ pDev->data[0] = (unsigned long)pGobiDev;
|
+
|
+ pGobiDev->mpNetDev = pDev;
|
+
|
+ // Clearing endpoint halt is a magic handshake that brings
|
+ // the device out of low power (airplane) mode
|
+ usb_clear_halt( pGobiDev->mpNetDev->udev, pDev->out );
|
+
|
+ // Overload PM related network functions
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ pGobiDev->mpUSBNetOpen = pDev->net->open;
|
+ pDev->net->open = GobiUSBNetOpen;
|
+ pGobiDev->mpUSBNetStop = pDev->net->stop;
|
+ pDev->net->stop = GobiUSBNetStop;
|
+#if defined(CONFIG_PM) && (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
|
+ pDev->net->hard_start_xmit = GobiUSBNetStartXmit;
|
+ pDev->net->tx_timeout = GobiUSBNetTXTimeout;
|
+#else //quectel donot send dhcp request before ndis connect for uc20
|
+ local_usbnet_start_xmit = pDev->net->hard_start_xmit;
|
+ pDev->net->hard_start_xmit = GobiUSBNetStartXmit2;
|
+#endif
|
+#else
|
+ pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL );
|
+ if (pNetDevOps == NULL)
|
+ {
|
+ DBG( "falied to allocate net device ops" );
|
+ usbnet_disconnect( pIntf );
|
+ return -ENOMEM;
|
+ }
|
+ memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) );
|
+
|
+ pGobiDev->mpUSBNetOpen = pNetDevOps->ndo_open;
|
+ pNetDevOps->ndo_open = GobiUSBNetOpen;
|
+ pGobiDev->mpUSBNetStop = pNetDevOps->ndo_stop;
|
+ pNetDevOps->ndo_stop = GobiUSBNetStop;
|
+#if 1 //quectel donot send dhcp request before ndis connect for uc20
|
+ pNetDevOps->ndo_start_xmit = GobiUSBNetStartXmit2;
|
+#else
|
+ pNetDevOps->ndo_start_xmit = usbnet_start_xmit;
|
+#endif
|
+ pNetDevOps->ndo_tx_timeout = usbnet_tx_timeout;
|
+
|
+ pDev->net->netdev_ops = pNetDevOps;
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
|
+ memset( &(pGobiDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) );
|
+#else
|
+ memset( &(pGobiDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) );
|
+#endif
|
+
|
+ pGobiDev->mpIntf = pIntf;
|
+ memset( &(pGobiDev->mMEID), '0', 14 );
|
+
|
+ DBG( "Mac Address:\n" );
|
+ PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 );
|
+
|
+ pGobiDev->mbQMIValid = false;
|
+ memset( &pGobiDev->mQMIDev, 0, sizeof( sQMIDev ) );
|
+ pGobiDev->mQMIDev.mbCdevIsInitialized = false;
|
+
|
+ pGobiDev->mQMIDev.mpDevClass = gpClass;
|
+
|
+#ifdef CONFIG_PM
|
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
|
+ #endif
|
+#endif /* CONFIG_PM */
|
+ spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock );
|
+
|
+ // Default to device down
|
+ pGobiDev->mDownReason = 0;
|
+
|
+//#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,11,0 ))
|
+ GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
|
+ GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
|
+//#endif
|
+
|
+ // Register QMI
|
+ pGobiDev->mbMdm9x07 |= (pDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c));
|
+ pGobiDev->mbRawIPMode = pGobiDev->mbMdm9x07;
|
+#ifdef CONFIG_BRIDGE
|
+ memcpy(pGobiDev->mHostMAC, pDev->net->dev_addr, 6);
|
+#endif
|
+ status = RegisterQMIDevice( pGobiDev );
|
+ if (status != 0)
|
+ {
|
+ // usbnet_disconnect() will call GobiNetDriverUnbind() which will call
|
+ // DeregisterQMIDevice() to clean up any partially created QMI device
|
+ usbnet_disconnect( pIntf );
|
+ return status;
|
+ }
|
+
|
+ // Success
|
+ return 0;
|
+}
|
+
|
+static struct usb_driver GobiNet =
|
+{
|
+ .name = "GobiNet",
|
+ .id_table = GobiVIDPIDTable,
|
+ .probe = GobiUSBNetProbe,
|
+ .disconnect = usbnet_disconnect,
|
+#ifdef CONFIG_PM
|
+ .suspend = GobiNetSuspend,
|
+ .resume = GobiNetResume,
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+ .supports_autosuspend = true,
|
+#endif
|
+#endif /* CONFIG_PM */
|
+};
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetModInit (Public Method)
|
+
|
+DESCRIPTION:
|
+ Initialize module
|
+ Create device class
|
+ Register out usb_driver struct
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+static int __init GobiUSBNetModInit( void )
|
+{
|
+ gpClass = class_create( THIS_MODULE, "GobiQMI" );
|
+ if (IS_ERR( gpClass ) == true)
|
+ {
|
+ DBG( "error at class_create %ld\n",
|
+ PTR_ERR( gpClass ) );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // This will be shown whenever driver is loaded
|
+ printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
|
+
|
+ return usb_register( &GobiNet );
|
+}
|
+module_init( GobiUSBNetModInit );
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiUSBNetModExit (Public Method)
|
+
|
+DESCRIPTION:
|
+ Deregister module
|
+ Destroy device class
|
+
|
+RETURN VALUE:
|
+ void
|
+===========================================================================*/
|
+static void __exit GobiUSBNetModExit( void )
|
+{
|
+ usb_deregister( &GobiNet );
|
+
|
+ class_destroy( gpClass );
|
+}
|
+module_exit( GobiUSBNetModExit );
|
+
|
+MODULE_VERSION( DRIVER_VERSION );
|
+MODULE_AUTHOR( DRIVER_AUTHOR );
|
+MODULE_DESCRIPTION( DRIVER_DESC );
|
+MODULE_LICENSE("Dual BSD/GPL");
|
+
|
+#ifdef bool
|
+#undef bool
|
+#endif
|
+
|
+module_param( debug, int, S_IRUGO | S_IWUSR );
|
+MODULE_PARM_DESC( debug, "Debuging enabled or not" );
|
+
|
+module_param( interruptible, int, S_IRUGO | S_IWUSR );
|
+MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" );
|
+module_param( txQueueLength, int, S_IRUGO | S_IWUSR );
|
+MODULE_PARM_DESC( txQueueLength,
|
+ "Number of IP packets which may be queued up for transmit" );
|
+
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/Kconfig linux-at91-loragw/drivers/net/usb/ec20/Kconfig
|
--- linux-at91/drivers/net/usb/ec20/Kconfig 1970-01-01 08:00:00.000000000 +0800
|
+++ linux-at91-loragw/drivers/net/usb/ec20/Kconfig 2024-10-08 15:53:07.135777407 +0800
|
@@ -0,0 +1,13 @@
|
+#
|
+# Quectel EC20 GobiNet driver configuration
|
+# Add by guowenxue 2020.12.4
|
+# Reference to: <<Quectel_WCDMA<E_Linux&Android_GobiNet_Driver_V1.3.0.pdf>>
|
+# Chapter 3.4 and 5.3
|
+#
|
+config EC20_GOBINET
|
+ bool "Quectel EC20 Cat4 module GobiNet driver"
|
+ depends on USB_USBNET
|
+ default y
|
+ help
|
+ Say Y here if you have Quectel EC20 Cat4 4G module and wanna use GobiNet driver here.
|
+ If unsure, say N.
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/Makefile linux-at91-loragw/drivers/net/usb/ec20/Makefile
|
--- linux-at91/drivers/net/usb/ec20/Makefile 1970-01-01 08:00:00.000000000 +0800
|
+++ linux-at91-loragw/drivers/net/usb/ec20/Makefile 2024-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 <asm/unaligned.h>
|
+#include <linux/kernel.h>
|
+#include "Structs.h"
|
+#include "QMI.h"
|
+
|
+/*=========================================================================*/
|
+// Get sizes of buffers needed by QMI requests
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMUXHeaderSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMUXHeaderSize( void )
|
+{
|
+ return sizeof( sQMUX );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLGetClientIDReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMICTLGetClientIDReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMICTLGetClientIDReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 10;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLReleaseClientIDReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of header
|
+===========================================================================*/
|
+u16 QMICTLReleaseClientIDReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 11;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLReadyReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMICTLReadyReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMICTLReadyReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 6;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSSetEventReportReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMIWDSSetEventReportReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMIWDSSetEventReportReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 15;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSGetPKGSRVCStatusReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMIWDSGetPKGSRVCStatusReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 7;
|
+}
|
+
|
+u16 QMIWDSSetQMUXBindMuxDataPortSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 29;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIDMSGetMEIDReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMIDMSGetMEIDReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 7;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDASetDataFormatReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMIWDASetDataFormatReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 25;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLSetDataFormatReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMICTLSetDataFormatReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMICTLSetDataFormatReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 15;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLSyncReqSize (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get size of buffer needed for QMUX + QMICTLSyncReq
|
+
|
+RETURN VALUE:
|
+ u16 - size of buffer
|
+===========================================================================*/
|
+u16 QMICTLSyncReqSize( void )
|
+{
|
+ return sizeof( sQMUX ) + 6;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Generic QMUX functions
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ParseQMUX (Public Method)
|
+
|
+DESCRIPTION:
|
+ Remove QMUX headers from a buffer
|
+
|
+PARAMETERS
|
+ pClientID [ O ] - On success, will point to Client ID
|
+ pBuffer [ I ] - Full Message passed in
|
+ buffSize [ I ] - Size of pBuffer
|
+
|
+RETURN VALUE:
|
+ int - Positive for size of QMUX header
|
+ Negative errno for error
|
+===========================================================================*/
|
+int ParseQMUX(
|
+ u16 * pClientID,
|
+ void * pBuffer,
|
+ u16 buffSize )
|
+{
|
+ sQMUX * pQMUXHeader;
|
+
|
+ if (pBuffer == 0 || buffSize < 12)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMUX Header
|
+ pQMUXHeader = (sQMUX *)pBuffer;
|
+
|
+ if (pQMUXHeader->mTF != 1
|
+ || le16_to_cpu(get_unaligned(&pQMUXHeader->mLength)) != buffSize - 1
|
+ || pQMUXHeader->mCtrlFlag != 0x80 )
|
+ {
|
+ return -EINVAL;
|
+ }
|
+
|
+ // Client ID
|
+ *pClientID = (pQMUXHeader->mQMIClientID << 8) + pQMUXHeader->mQMIService;
|
+
|
+ return sizeof( sQMUX );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ FillQMUX (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMUX headers
|
+
|
+PARAMETERS
|
+ clientID [ I ] - Client ID
|
+ pBuffer [ O ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer (must be at least 6)
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int FillQMUX(
|
+ u16 clientID,
|
+ void * pBuffer,
|
+ u16 buffSize )
|
+{
|
+ sQMUX * pQMUXHeader;
|
+
|
+ if (pBuffer == 0 || buffSize < sizeof( sQMUX ))
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMUX Header
|
+ pQMUXHeader = (sQMUX *)pBuffer;
|
+
|
+ pQMUXHeader->mTF = 1;
|
+ put_unaligned(cpu_to_le16(buffSize - 1), &pQMUXHeader->mLength);
|
+ //DBG("pQMUXHeader->mLength = 0x%x, buffSize - 1 = 0x%x\n",pQMUXHeader->mLength, buffSize - 1);
|
+ pQMUXHeader->mCtrlFlag = 0;
|
+
|
+ // Service and Client ID
|
+ pQMUXHeader->mQMIService = clientID & 0xff;
|
+ pQMUXHeader->mQMIClientID = clientID >> 8;
|
+
|
+ return 0;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Generic QMI functions
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GetTLV (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get data buffer of a specified TLV from a QMI message
|
+
|
+ QMI Message shall NOT include SDU
|
+
|
+PARAMETERS
|
+ pQMIMessage [ I ] - QMI Message buffer
|
+ messageLen [ I ] - Size of QMI Message buffer
|
+ type [ I ] - Desired Type
|
+ pOutDataBuf [ O ] - Buffer to be filled with TLV
|
+ messageLen [ I ] - Size of QMI Message buffer
|
+
|
+RETURN VALUE:
|
+ u16 - Size of TLV for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GetTLV(
|
+ void * pQMIMessage,
|
+ u16 messageLen,
|
+ u8 type,
|
+ void * pOutDataBuf,
|
+ u16 bufferLen )
|
+{
|
+ u16 pos;
|
+ u16 tlvSize = 0;
|
+ u16 cpyCount;
|
+
|
+ if (pQMIMessage == 0 || pOutDataBuf == 0)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ for (pos = 4;
|
+ pos + 3 < messageLen;
|
+ pos += tlvSize + 3)
|
+ {
|
+ tlvSize = le16_to_cpu( get_unaligned(((u16 *)(pQMIMessage + pos + 1) )) );
|
+ if (*(u8 *)(pQMIMessage + pos) == type)
|
+ {
|
+ if (bufferLen < tlvSize)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ for (cpyCount = 0; cpyCount < tlvSize; cpyCount++)
|
+ {
|
+ *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount));
|
+ }
|
+
|
+ return tlvSize;
|
+ }
|
+ }
|
+
|
+ return -ENOMSG;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ValidQMIMessage (Public Method)
|
+
|
+DESCRIPTION:
|
+ Check mandatory TLV in a QMI message
|
+
|
+ QMI Message shall NOT include SDU
|
+
|
+PARAMETERS
|
+ pQMIMessage [ I ] - QMI Message buffer
|
+ messageLen [ I ] - Size of QMI Message buffer
|
+
|
+RETURN VALUE:
|
+ int - 0 for success (no error)
|
+ Negative errno for error
|
+ Positive for QMI error code
|
+===========================================================================*/
|
+int ValidQMIMessage(
|
+ void * pQMIMessage,
|
+ u16 messageLen )
|
+{
|
+ char mandTLV[4];
|
+
|
+ if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4)
|
+ {
|
+ // Found TLV
|
+ if (*(u16 *)&mandTLV[0] != 0)
|
+ {
|
+ return le16_to_cpu( get_unaligned(&mandTLV[2]) );
|
+ }
|
+ else
|
+ {
|
+ return 0;
|
+ }
|
+ }
|
+ else
|
+ {
|
+ return -ENOMSG;
|
+ }
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GetQMIMessageID (Public Method)
|
+
|
+DESCRIPTION:
|
+ Get the message ID of a QMI message
|
+
|
+ QMI Message shall NOT include SDU
|
+
|
+PARAMETERS
|
+ pQMIMessage [ I ] - QMI Message buffer
|
+ messageLen [ I ] - Size of QMI Message buffer
|
+
|
+RETURN VALUE:
|
+ int - Positive for message ID
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GetQMIMessageID(
|
+ void * pQMIMessage,
|
+ u16 messageLen )
|
+{
|
+ if (messageLen < 2)
|
+ {
|
+ return -ENODATA;
|
+ }
|
+ else
|
+ {
|
+ return le16_to_cpu( get_unaligned((u16 *)pQMIMessage) );
|
+ }
|
+}
|
+
|
+/*=========================================================================*/
|
+// Fill Buffers with QMI requests
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLGetClientIDReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI CTL Get Client ID Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+ serviceType [ I ] - Service type requested
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLGetClientIDReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u8 transactionID,
|
+ u8 serviceType )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI CTL GET CLIENT ID
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00;
|
+ // Transaction ID
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
|
+ // Message ID
|
+ put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
|
+ // Size of TLV's
|
+ put_unaligned(cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
|
+ // QMI Service Type
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
|
+ // Size
|
+ put_unaligned(cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
|
+ // QMI svc type
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = serviceType;
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 10;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLReleaseClientIDReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI CTL Release Client ID Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+ clientID [ I ] - Service type requested
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLReleaseClientIDReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u8 transactionID,
|
+ u16 clientID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ DBG( "buffSize: 0x%x, transactionID: 0x%x, clientID: 0x%x,\n",
|
+ buffSize, transactionID, clientID );
|
+
|
+ // QMI CTL RELEASE CLIENT ID REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID;
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0023), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
|
+ // Size of TLV's
|
+ put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
|
+ // Release client ID
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
|
+ // Size
|
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
|
+ // QMI svs type / Client ID
|
+ put_unaligned(cpu_to_le16(clientID), (u16 *)(pBuffer + sizeof( sQMUX ) + 9));
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 11;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLReadyReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI CTL Get Version Info Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLReadyReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u8 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ DBG("buffSize: 0x%x, transactionID: 0x%x\n", buffSize, transactionID);
|
+
|
+ // QMI CTL GET VERSION INFO REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0021), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
|
+ // Size of TLV's
|
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 6;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSSetEventReportReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI WDS Set Event Report Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIWDSSetEventReportReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI WDS SET EVENT REPORT REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
|
+ // Size of TLV's
|
+ put_unaligned(cpu_to_le16(0x0008), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
|
+ // Report channel rate TLV
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x11;
|
+ // Size
|
+ put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
|
+ // Stats period
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01;
|
+ // Stats mask
|
+ put_unaligned( cpu_to_le32(0x000000ff), (u32 *)(pBuffer + sizeof( sQMUX ) + 11) );
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 15;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSGetPKGSRVCStatusReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI WDS Get PKG SRVC Status Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIWDSGetPKGSRVCStatusReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI WDS Get PKG SRVC Status REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
|
+ // Message ID
|
+ put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
|
+ // Size of TLV's
|
+ put_unaligned(cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 7;
|
+}
|
+
|
+u16 QMIWDSSetQMUXBindMuxDataPortReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMIWDSSetQMUXBindMuxDataPortSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI WDS Set QMUX Bind Mux Data Port REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
|
+ // Message ID
|
+ put_unaligned(cpu_to_le16(0x00a2), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
|
+ // Size of TLV's
|
+ put_unaligned(cpu_to_le16(0x0016), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
|
+
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10;
|
+ put_unaligned(cpu_to_le16(0x08), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
|
+ put_unaligned(cpu_to_le32(0x02), (u32 *)(pBuffer + sizeof( sQMUX ) + 10)); // ep_type
|
+ put_unaligned(cpu_to_le32(0x04), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); // iface_id
|
+
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x11;
|
+ put_unaligned(cpu_to_le16(0x01), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 21) = 0x81; // MuxId
|
+
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 22) = 0x13;
|
+ put_unaligned(cpu_to_le16(0x04), (u16 *)(pBuffer + sizeof( sQMUX ) + 23));
|
+ put_unaligned(cpu_to_le32(0x01), (u32 *)(pBuffer + sizeof( sQMUX ) + 25));
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 29;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIDMSGetMEIDReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI DMS Get Serial Numbers Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIDMSGetMEIDReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI DMS GET SERIAL NUMBERS REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0025), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
|
+ // Size of TLV's
|
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 7;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDASetDataFormatReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI WDA Set Data Format Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIWDASetDataFormatReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ bool bRawIPMode,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // QMI WDA SET DATA FORMAT REQ
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+
|
+ // Transaction ID
|
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
|
+
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0020), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
|
+
|
+ // Size of TLV's
|
+ put_unaligned( cpu_to_le16(0x0012), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
|
+
|
+ /* TLVType QOS Data Format 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; // type data format
|
+
|
+ /* TLVLength 2 bytes - see spec */
|
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
|
+
|
+ /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
|
+#ifdef QOS_MODE
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 1; /* QOS header */
|
+#else
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0; /* no-QOS header */
|
+#endif
|
+
|
+ /* TLVType Link-Layer Protocol (Optional) 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x11;
|
+
|
+ /* TLVLength 2 bytes */
|
+ put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 12));
|
+
|
+ /* LinkProt: 0x1 - ETH; 0x2 - rawIP 4 bytes */
|
+if (bRawIPMode) { //#ifdef DATA_MODE_RP
|
+ /* Set RawIP mode */
|
+ put_unaligned( cpu_to_le32(0x00000002), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
|
+ DBG("Request RawIP Data Format\n");
|
+} else { //#else
|
+ /* Set Ethernet mode */
|
+ put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
|
+ DBG("Request Ethernet Data Format\n");
|
+} //#endif
|
+
|
+ /* TLVType Uplink Data Aggression Protocol - 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x13;
|
+
|
+ /* TLVLength 2 bytes */
|
+ put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
|
+
|
+ /* TLV Data */
|
+ put_unaligned( cpu_to_le32(0x00000000), (u32 *)(pBuffer + sizeof( sQMUX ) + 21));
|
+
|
+ // success
|
+ return QMIWDASetDataFormatReqSize();
|
+}
|
+
|
+
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLSetDataFormatReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI CTL Set Data Format Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLSetDataFormatReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u8 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMICTLSetDataFormatReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ /* QMI CTL Set Data Format Request */
|
+ /* Request */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; // QMICTL_FLAG_REQUEST
|
+
|
+ /* Transaction ID 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
|
+
|
+ /* QMICTLType 2 bytes */
|
+ put_unaligned( cpu_to_le16(0x0026), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
|
+
|
+ /* Length 2 bytes of 2 TLVs each - see spec */
|
+ put_unaligned( cpu_to_le16(0x0009), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
|
+
|
+ /* TLVType Data Format (Mandatory) 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; // type data format
|
+
|
+ /* TLVLength 2 bytes - see spec */
|
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
|
+
|
+ /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
|
+#ifdef QOS_MODE
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 1; /* QOS header */
|
+#else
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 0; /* no-QOS header */
|
+#endif
|
+
|
+ /* TLVType Link-Layer Protocol (Optional) 1 byte */
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = TLV_TYPE_LINK_PROTO;
|
+
|
+ /* TLVLength 2 bytes */
|
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 11));
|
+
|
+ /* LinkProt: 0x1 - ETH; 0x2 - rawIP 2 bytes */
|
+#ifdef DATA_MODE_RP
|
+ /* Set RawIP mode */
|
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
|
+ DBG("Request RawIP Data Format\n");
|
+#else
|
+ /* Set Ethernet mode */
|
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
|
+ DBG("Request Ethernet Data Format\n");
|
+#endif
|
+
|
+ /* success */
|
+ return sizeof( sQMUX ) + 15;
|
+
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLSyncReq (Public Method)
|
+
|
+DESCRIPTION:
|
+ Fill buffer with QMI CTL Sync Request
|
+
|
+PARAMETERS
|
+ pBuffer [ 0 ] - Buffer to be filled
|
+ buffSize [ I ] - Size of pBuffer
|
+ transactionID [ I ] - Transaction ID
|
+
|
+RETURN VALUE:
|
+ int - Positive for resulting size of pBuffer
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLSyncReq(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 transactionID )
|
+{
|
+ if (pBuffer == 0 || buffSize < QMICTLSyncReqSize() )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // Request
|
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
|
+ // Transaction ID
|
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
|
+ // Message ID
|
+ put_unaligned( cpu_to_le16(0x0027), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
|
+ // Size of TLV's
|
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
|
+
|
+ // success
|
+ return sizeof( sQMUX ) + 6;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Parse data from QMI responses
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLGetClientIDResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Parse the QMI CTL Get Client ID Resp
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+ pClientID [ 0 ] - Recieved client ID
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLGetClientIDResp(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u16 * pClientID )
|
+{
|
+ int result;
|
+
|
+ // Ignore QMUX and SDU
|
+ // QMI CTL SDU is 2 bytes, not 3
|
+ u8 offset = sizeof( sQMUX ) + 2;
|
+
|
+ if (pBuffer == 0 || buffSize < offset)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ if (result != 0x22)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = ValidQMIMessage( pBuffer, buffSize );
|
+ if (result != 0)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 );
|
+ if (result != 2)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLReleaseClientIDResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Verify the QMI CTL Release Client ID Resp is valid
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLReleaseClientIDResp(
|
+ void * pBuffer,
|
+ u16 buffSize )
|
+{
|
+ int result;
|
+
|
+ // Ignore QMUX and SDU
|
+ // QMI CTL SDU is 2 bytes, not 3
|
+ u8 offset = sizeof( sQMUX ) + 2;
|
+
|
+ if (pBuffer == 0 || buffSize < offset)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ if (result != 0x23)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = ValidQMIMessage( pBuffer, buffSize );
|
+ if (result != 0)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSEventResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Parse the QMI WDS Set Event Report Resp/Indication or
|
+ QMI WDS Get PKG SRVC Status Resp/Indication
|
+
|
+ Return parameters will only be updated if value was received
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+ pTXOk [ O ] - Number of transmitted packets without errors
|
+ pRXOk [ O ] - Number of recieved packets without errors
|
+ pTXErr [ O ] - Number of transmitted packets with framing errors
|
+ pRXErr [ O ] - Number of recieved packets with framing errors
|
+ pTXOfl [ O ] - Number of transmitted packets dropped due to overflow
|
+ pRXOfl [ O ] - Number of recieved packets dropped due to overflow
|
+ pTXBytesOk [ O ] - Number of transmitted bytes without errors
|
+ pRXBytesOk [ O ] - Number of recieved bytes without errors
|
+ pbLinkState [ 0 ] - Is the link active?
|
+ pbReconfigure [ 0 ] - Must interface be reconfigured? (reset IP address)
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIWDSEventResp(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ u32 * pTXOk,
|
+ u32 * pRXOk,
|
+ u32 * pTXErr,
|
+ u32 * pRXErr,
|
+ u32 * pTXOfl,
|
+ u32 * pRXOfl,
|
+ u64 * pTXBytesOk,
|
+ u64 * pRXBytesOk,
|
+ bool * pbLinkState,
|
+ bool * pbReconfigure )
|
+{
|
+ int result;
|
+ u8 pktStatusRead[2];
|
+
|
+ // Ignore QMUX and SDU
|
+ u8 offset = sizeof( sQMUX ) + 3;
|
+
|
+ if (pBuffer == 0
|
+ || buffSize < offset
|
+ || pTXOk == 0
|
+ || pRXOk == 0
|
+ || pTXErr == 0
|
+ || pRXErr == 0
|
+ || pTXOfl == 0
|
+ || pRXOfl == 0
|
+ || pTXBytesOk == 0
|
+ || pRXBytesOk == 0
|
+ || pbLinkState == 0
|
+ || pbReconfigure == 0 )
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ // Note: Indications. No Mandatory TLV required
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ // QMI WDS Set Event Report Resp
|
+ if (result == 0x01)
|
+ {
|
+ // TLV's are not mandatory
|
+ GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 );
|
+ put_unaligned( le32_to_cpu(*pTXOk), pTXOk);
|
+ GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 );
|
+ put_unaligned( le32_to_cpu(*pRXOk), pRXOk);
|
+ GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 );
|
+ put_unaligned( le32_to_cpu(*pTXErr), pTXErr);
|
+ GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 );
|
+ put_unaligned( le32_to_cpu(*pRXErr), pRXErr);
|
+ GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 );
|
+ put_unaligned( le32_to_cpu(*pTXOfl), pTXOfl);
|
+ GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 );
|
+ put_unaligned( le32_to_cpu(*pRXOfl), pRXOfl);
|
+ GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 );
|
+ put_unaligned( le64_to_cpu(*pTXBytesOk), pTXBytesOk);
|
+ GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 );
|
+ put_unaligned( le64_to_cpu(*pRXBytesOk), pRXBytesOk);
|
+ }
|
+ // QMI WDS Get PKG SRVC Status Resp
|
+ else if (result == 0x22)
|
+ {
|
+ result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 );
|
+ // 1 or 2 bytes may be received
|
+ if (result >= 1)
|
+ {
|
+ if (pktStatusRead[0] == 0x02)
|
+ {
|
+ *pbLinkState = true;
|
+ }
|
+ else
|
+ {
|
+ *pbLinkState = false;
|
+ }
|
+ }
|
+ if (result == 2)
|
+ {
|
+ if (pktStatusRead[1] == 0x01)
|
+ {
|
+ *pbReconfigure = true;
|
+ }
|
+ else
|
+ {
|
+ *pbReconfigure = false;
|
+ }
|
+ }
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ }
|
+ else
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIDMSGetMEIDResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Parse the QMI DMS Get Serial Numbers Resp
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+ pMEID [ O ] - Device MEID
|
+ meidSize [ I ] - Size of MEID buffer (at least 14)
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIDMSGetMEIDResp(
|
+ void * pBuffer,
|
+ u16 buffSize,
|
+ char * pMEID,
|
+ int meidSize )
|
+{
|
+ int result;
|
+
|
+ // Ignore QMUX and SDU
|
+ u8 offset = sizeof( sQMUX ) + 3;
|
+
|
+ if (pBuffer == 0 || buffSize < offset || meidSize < 14)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ if (result != 0x25)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = ValidQMIMessage( pBuffer, buffSize );
|
+ if (result != 0)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 );
|
+ if (result != 14)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDASetDataFormatResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Parse the QMI WDA Set Data Format Response
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMIWDASetDataFormatResp(
|
+ void * pBuffer,
|
+ u16 buffSize, bool bRawIPMode )
|
+{
|
+
|
+ int result;
|
+
|
+ u8 pktLinkProtocol[4];
|
+
|
+ // Ignore QMUX and SDU
|
+ // QMI SDU is 3 bytes
|
+ u8 offset = sizeof( sQMUX ) + 3;
|
+
|
+ if (pBuffer == 0 || buffSize < offset)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ if (result != 0x20)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ /* Check response message result TLV */
|
+ result = ValidQMIMessage( pBuffer, buffSize );
|
+ if (result != 0)
|
+ {
|
+ DBG("EFAULT: Data Format Mode Bad Response\n");
|
+// return -EFAULT;
|
+ return 0;
|
+ }
|
+
|
+ /* Check response message link protocol */
|
+ result = GetTLV( pBuffer, buffSize, 0x11,
|
+ &pktLinkProtocol[0], 4);
|
+ if (result != 4)
|
+ {
|
+ DBG("EFAULT: Wrong TLV format\n");
|
+ return 0;
|
+
|
+ }
|
+
|
+if (bRawIPMode) { ////#ifdef DATA_MODE_RP
|
+ if (pktLinkProtocol[0] != 2)
|
+ {
|
+ DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n");
|
+ return pktLinkProtocol[0];
|
+ }
|
+ DBG("Data Format Set to RawIP\n");
|
+} else { ////#else
|
+ if (pktLinkProtocol[0] != 1)
|
+ {
|
+ DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n");
|
+ return pktLinkProtocol[0];
|
+ }
|
+ DBG("Data Format Set to Ethernet Mode \n");
|
+} //#endif
|
+
|
+ return pktLinkProtocol[0];
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMICTLSyncResp (Public Method)
|
+
|
+DESCRIPTION:
|
+ Validate the QMI CTL Sync Response
|
+
|
+PARAMETERS
|
+ pBuffer [ I ] - Buffer to be parsed
|
+ buffSize [ I ] - Size of pBuffer
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for error
|
+===========================================================================*/
|
+int QMICTLSyncResp(
|
+ void *pBuffer,
|
+ u16 buffSize )
|
+{
|
+ int result;
|
+
|
+ // Ignore QMUX (2 bytes for QMI CTL) and SDU
|
+ u8 offset = sizeof( sQMUX ) + 2;
|
+
|
+ if (pBuffer == 0 || buffSize < offset)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pBuffer = pBuffer + offset;
|
+ buffSize -= offset;
|
+
|
+ result = GetQMIMessageID( pBuffer, buffSize );
|
+ if (result != 0x27)
|
+ {
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = ValidQMIMessage( pBuffer, buffSize );
|
+
|
+ return result;
|
+}
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMIDevice.c linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.c
|
--- linux-at91/drivers/net/usb/ec20/QMIDevice.c 1970-01-01 08:00:00.000000000 +0800
|
+++ linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.c 2024-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 <asm/unaligned.h>
|
+#include "QMIDevice.h"
|
+#include <linux/module.h>
|
+
|
+#if (__GNUC__ > 7 && defined(_ASM_X86_ATOMIC_H))|| \
|
+ (__GNUC__ == 7 && defined(_ASM_X86_ATOMIC_H) && (__GNUC_MINOR__ > 0 || \
|
+ (__GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ > 0)))
|
+#define gobi_atomic_read(x) atomic_read((const atomic_t *)x)
|
+#else
|
+#define gobi_atomic_read(x) atomic_read((atomic_t *)x)
|
+#endif
|
+
|
+//-----------------------------------------------------------------------------
|
+// Definitions
|
+//-----------------------------------------------------------------------------
|
+
|
+extern int debug;
|
+extern int interruptible;
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
|
+static int s_interval;
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 ))
|
+#include <linux/devfs_fs_kernel.h>
|
+static char devfs_name[32];
|
+int device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
|
+{
|
+ va_list vargs;
|
+ struct class_device *class_dev;
|
+ int err;
|
+
|
+ va_start(vargs, fmt);
|
+ vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs);
|
+ va_end(vargs);
|
+
|
+ class_dev = class_device_create(class, devt, parent, "%s", devfs_name);
|
+ if (IS_ERR(class_dev)) {
|
+ err = PTR_ERR(class_dev);
|
+ goto out;
|
+ }
|
+
|
+ err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name);
|
+ if (err) {
|
+ class_device_destroy(class, devt);
|
+ goto out;
|
+ }
|
+
|
+ return 0;
|
+
|
+out:
|
+ return err;
|
+}
|
+
|
+void device_destroy(struct class *class, dev_t devt)
|
+{
|
+ class_device_destroy(class, devt);
|
+ devfs_remove(devfs_name);
|
+}
|
+#endif
|
+
|
+#ifdef CONFIG_PM
|
+// Prototype to GobiNetSuspend function
|
+int GobiNetSuspend(
|
+ struct usb_interface * pIntf,
|
+ pm_message_t powerEvent );
|
+#endif /* CONFIG_PM */
|
+
|
+// IOCTL to generate a client ID for this service type
|
+#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
|
+
|
+// IOCTL to get the VIDPID of the device
|
+#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
|
+
|
+// IOCTL to get the MEID of the device
|
+#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
|
+
|
+#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL (0x8BE0 + 4)
|
+
|
+// CDC GET_ENCAPSULATED_RESPONSE packet
|
+#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll
|
+#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll
|
+/* The following masks filter the common part of the encapsulated response
|
+ * packet value for Gobi and QMI devices, ie. ignore usb interface number
|
+ */
|
+#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll
|
+#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll
|
+
|
+const int i = 1;
|
+#define is_bigendian() ( (*(char*)&i) == 0 )
|
+#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\
|
+{\
|
+ *pcdcrsp = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \
|
+ : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \
|
+ *pmask = is_bigendian() ? CDC_RSP_MASK_BE \
|
+ : CDC_RSP_MASK_LE; \
|
+}
|
+
|
+// CDC CONNECTION_SPEED_CHANGE indication packet
|
+#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll
|
+#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll
|
+/* The following masks filter the common part of the connection speed change
|
+ * packet value for Gobi and QMI devices
|
+ */
|
+#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll
|
+#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll
|
+#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\
|
+{\
|
+ *pcdccscp = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \
|
+ : CDC_CONNECTION_SPEED_CHANGE_LE ; \
|
+ *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \
|
+ : CDC_CONNSPD_MASK_LE; \
|
+}
|
+
|
+#define SET_CONTROL_LINE_STATE_REQUEST_TYPE 0x21
|
+#define SET_CONTROL_LINE_STATE_REQUEST 0x22
|
+#define CONTROL_DTR 0x01
|
+#define CONTROL_RTS 0x02
|
+
|
+/*=========================================================================*/
|
+// UserspaceQMIFops
|
+// QMI device's userspace file operations
|
+/*=========================================================================*/
|
+struct file_operations UserspaceQMIFops =
|
+{
|
+ .owner = THIS_MODULE,
|
+ .read = UserspaceRead,
|
+ .write = UserspaceWrite,
|
+#ifdef CONFIG_COMPAT
|
+ .compat_ioctl = UserspaceunlockedIOCTL,
|
+#endif
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 ))
|
+ .unlocked_ioctl = UserspaceunlockedIOCTL,
|
+#else
|
+ .ioctl = UserspaceIOCTL,
|
+#endif
|
+ .open = UserspaceOpen,
|
+ .flush = UserspaceClose,
|
+ .poll = UserspacePoll,
|
+};
|
+
|
+/*=========================================================================*/
|
+// Generic functions
|
+/*=========================================================================*/
|
+u8 QMIXactionIDGet( sGobiUSBNet *pDev)
|
+{
|
+ u8 transactionID;
|
+
|
+ if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) )
|
+ {
|
+ transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
|
+ }
|
+
|
+ return transactionID;
|
+}
|
+
|
+static struct usb_endpoint_descriptor *GetEndpoint(
|
+ struct usb_interface *pintf,
|
+ int type,
|
+ int dir )
|
+{
|
+ int i;
|
+ struct usb_host_interface *iface = pintf->cur_altsetting;
|
+ struct usb_endpoint_descriptor *pendp;
|
+
|
+ for( i = 0; i < iface->desc.bNumEndpoints; i++)
|
+ {
|
+ pendp = &iface->endpoint[i].desc;
|
+ if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir)
|
+ &&
|
+ (usb_endpoint_type(pendp) == type) )
|
+ {
|
+ return pendp;
|
+ }
|
+ }
|
+
|
+ return NULL;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ IsDeviceValid (Public Method)
|
+
|
+DESCRIPTION:
|
+ Basic test to see if device memory is valid
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool IsDeviceValid( sGobiUSBNet * pDev )
|
+{
|
+ if (pDev == NULL)
|
+ {
|
+ return false;
|
+ }
|
+
|
+ if (pDev->mbQMIValid == false)
|
+ {
|
+ return false;
|
+ }
|
+
|
+ return true;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ PrintHex (Public Method)
|
+
|
+DESCRIPTION:
|
+ Print Hex data, for debug purposes
|
+
|
+PARAMETERS:
|
+ pBuffer [ I ] - Data buffer
|
+ bufSize [ I ] - Size of data buffer
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void PrintHex(
|
+ void * pBuffer,
|
+ u16 bufSize )
|
+{
|
+ char * pPrintBuf;
|
+ u16 pos;
|
+ int status;
|
+
|
+ if (debug != 1)
|
+ {
|
+ return;
|
+ }
|
+
|
+ pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC );
|
+ if (pPrintBuf == NULL)
|
+ {
|
+ DBG( "Unable to allocate buffer\n" );
|
+ return;
|
+ }
|
+ memset( pPrintBuf, 0 , bufSize * 3 + 1 );
|
+
|
+ for (pos = 0; pos < bufSize; pos++)
|
+ {
|
+ status = snprintf( (pPrintBuf + (pos * 3)),
|
+ 4,
|
+ "%02X ",
|
+ *(u8 *)(pBuffer + pos) );
|
+ if (status != 3)
|
+ {
|
+ DBG( "snprintf error %d\n", status );
|
+ kfree( pPrintBuf );
|
+ return;
|
+ }
|
+ }
|
+
|
+ DBG( " : %s\n", pPrintBuf );
|
+
|
+ kfree( pPrintBuf );
|
+ pPrintBuf = NULL;
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiSetDownReason (Public Method)
|
+
|
+DESCRIPTION:
|
+ Sets mDownReason and turns carrier off
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Device specific memory
|
+ reason [ I ] - Reason device is down
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void GobiSetDownReason(
|
+ sGobiUSBNet * pDev,
|
+ u8 reason )
|
+{
|
+ set_bit( reason, &pDev->mDownReason );
|
+ DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
|
+
|
+ netif_carrier_off( pDev->mpNetDev->net );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiClearDownReason (Public Method)
|
+
|
+DESCRIPTION:
|
+ Clear mDownReason and may turn carrier on
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Device specific memory
|
+ reason [ I ] - Reason device is no longer down
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void GobiClearDownReason(
|
+ sGobiUSBNet * pDev,
|
+ u8 reason )
|
+{
|
+ clear_bit( reason, &pDev->mDownReason );
|
+
|
+ DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
|
+#if 0 //(LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 ))
|
+ netif_carrier_on( pDev->mpNetDev->net );
|
+#else
|
+ if (pDev->mDownReason == 0)
|
+ {
|
+ netif_carrier_on( pDev->mpNetDev->net );
|
+ }
|
+#endif
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GobiTestDownReason (Public Method)
|
+
|
+DESCRIPTION:
|
+ Test mDownReason and returns whether reason is set
|
+
|
+PARAMETERS
|
+ pDev [ I ] - Device specific memory
|
+ reason [ I ] - Reason device is down
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool GobiTestDownReason(
|
+ sGobiUSBNet * pDev,
|
+ u8 reason )
|
+{
|
+ return test_bit( reason, &pDev->mDownReason );
|
+}
|
+
|
+/*=========================================================================*/
|
+// Driver level asynchronous read functions
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ResubmitIntURB (Public Method)
|
+
|
+DESCRIPTION:
|
+ Resubmit interrupt URB, re-using same values
|
+
|
+PARAMETERS
|
+ pIntURB [ I ] - Interrupt URB
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int ResubmitIntURB( struct urb * pIntURB )
|
+{
|
+ int status;
|
+ int interval;
|
+
|
+ // Sanity test
|
+ if ( (pIntURB == NULL)
|
+ || (pIntURB->dev == NULL) )
|
+ {
|
+ return -EINVAL;
|
+ }
|
+
|
+ // Interval needs reset after every URB completion
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
|
+ interval = max((int)(pIntURB->ep->desc.bInterval),
|
+ (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3);
|
+#else
|
+ interval = s_interval;
|
+#endif
|
+
|
+ // Reschedule interrupt URB
|
+ usb_fill_int_urb( pIntURB,
|
+ pIntURB->dev,
|
+ pIntURB->pipe,
|
+ pIntURB->transfer_buffer,
|
+ pIntURB->transfer_buffer_length,
|
+ pIntURB->complete,
|
+ pIntURB->context,
|
+ interval );
|
+ status = usb_submit_urb( pIntURB, GFP_ATOMIC );
|
+ if (status != 0)
|
+ {
|
+ DBG( "Error re-submitting Int URB %d\n", status );
|
+ }
|
+
|
+ return status;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ReadCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ Put the data in storage and notify anyone waiting for data
|
+
|
+PARAMETERS
|
+ pReadURB [ I ] - URB this callback is run for
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+void ReadCallback( struct urb * pReadURB )
|
+#else
|
+void ReadCallback(struct urb *pReadURB, struct pt_regs *regs)
|
+#endif
|
+{
|
+ int result;
|
+ u16 clientID;
|
+ sClientMemList * pClientMem;
|
+ void * pData;
|
+ void * pDataCopy;
|
+ u16 dataSize;
|
+ sGobiUSBNet * pDev;
|
+ unsigned long flags;
|
+ u16 transactionID;
|
+
|
+ if (pReadURB == NULL)
|
+ {
|
+ DBG( "bad read URB\n" );
|
+ return;
|
+ }
|
+
|
+ pDev = pReadURB->context;
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return;
|
+ }
|
+
|
+#ifdef READ_QMI_URB_ERROR
|
+ del_timer(&pDev->mQMIDev.mReadUrbTimer);
|
+ if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0))
|
+ pReadURB->status = 0;
|
+#endif
|
+
|
+ if (pReadURB->status != 0)
|
+ {
|
+ DBG( "Read status = %d\n", pReadURB->status );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+
|
+ return;
|
+ }
|
+ DBG( "Read %d bytes\n", pReadURB->actual_length );
|
+
|
+ pData = pReadURB->transfer_buffer;
|
+ dataSize = pReadURB->actual_length;
|
+
|
+ PrintHex( pData, dataSize );
|
+
|
+#ifdef READ_QMI_URB_ERROR
|
+ if (dataSize < (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1)) {
|
+ dataSize = (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1);
|
+ memset(pReadURB->transfer_buffer + pReadURB->actual_length, 0x00, dataSize - pReadURB->actual_length);
|
+ INFO( "Read %d / %d bytes\n", pReadURB->actual_length, dataSize);
|
+ }
|
+#endif
|
+
|
+ result = ParseQMUX( &clientID,
|
+ pData,
|
+ dataSize );
|
+ if (result < 0)
|
+ {
|
+ DBG( "Read error parsing QMUX %d\n", result );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+
|
+ return;
|
+ }
|
+
|
+ // Grab transaction ID
|
+
|
+ // Data large enough?
|
+ if (dataSize < result + 3)
|
+ {
|
+ DBG( "Data buffer too small to parse\n" );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+
|
+ return;
|
+ }
|
+
|
+ // Transaction ID size is 1 for QMICTL, 2 for others
|
+ if (clientID == QMICTL)
|
+ {
|
+ transactionID = *(u8*)(pData + result + 1);
|
+ }
|
+ else
|
+ {
|
+ transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) );
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Find memory storage for this service and Client ID
|
+ // Not using FindClientMem because it can't handle broadcasts
|
+ pClientMem = pDev->mQMIDev.mpClientMemList;
|
+
|
+ while (pClientMem != NULL)
|
+ {
|
+ if (pClientMem->mClientID == clientID
|
+ || (pClientMem->mClientID | 0xff00) == clientID)
|
+ {
|
+ // Make copy of pData
|
+ pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
|
+ if (pDataCopy == NULL)
|
+ {
|
+ DBG( "Error allocating client data memory\n" );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+
|
+ return;
|
+ }
|
+
|
+ memcpy( pDataCopy, pData, dataSize );
|
+
|
+ if (AddToReadMemList( pDev,
|
+ pClientMem->mClientID,
|
+ transactionID,
|
+ pDataCopy,
|
+ dataSize ) == false)
|
+ {
|
+ DBG( "Error allocating pReadMemListEntry "
|
+ "read will be discarded\n" );
|
+ kfree( pDataCopy );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+
|
+ return;
|
+ }
|
+
|
+ // Success
|
+ VDBG( "Creating new readListEntry for client 0x%04X, TID %x\n",
|
+ clientID,
|
+ transactionID );
|
+
|
+ // Notify this client data exists
|
+ NotifyAndPopNotifyList( pDev,
|
+ pClientMem->mClientID,
|
+ transactionID );
|
+
|
+ // Possibly notify poll() that data exists
|
+ wake_up_interruptible_sync( &pClientMem->mWaitQueue );
|
+
|
+ // Not a broadcast
|
+ if (clientID >> 8 != 0xff)
|
+ {
|
+ break;
|
+ }
|
+ }
|
+
|
+ // Next element
|
+ pClientMem = pClientMem->mpNext;
|
+ }
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Resubmit the interrupt URB
|
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ IntCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ Data is available, fire off a read URB
|
+
|
+PARAMETERS
|
+ pIntURB [ I ] - URB this callback is run for
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+void IntCallback( struct urb * pIntURB )
|
+{
|
+#else
|
+void IntCallback(struct urb *pIntURB, struct pt_regs *regs)
|
+{
|
+#endif
|
+ int status;
|
+ u64 CDCEncResp;
|
+ u64 CDCEncRespMask;
|
+
|
+ sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return;
|
+ }
|
+
|
+ // Verify this was a normal interrupt
|
+ if (pIntURB->status != 0)
|
+ {
|
+ DBG( "IntCallback: Int status = %d\n", pIntURB->status );
|
+
|
+ // Ignore EOVERFLOW errors
|
+ if (pIntURB->status != -EOVERFLOW)
|
+ {
|
+ // Read 'thread' dies here
|
+ return;
|
+ }
|
+ }
|
+ else
|
+ {
|
+ //TODO cast transfer_buffer to struct usb_cdc_notification
|
+
|
+ // CDC GET_ENCAPSULATED_RESPONSE
|
+ CDC_GET_ENCAPSULATED_RESPONSE(&CDCEncResp, &CDCEncRespMask)
|
+
|
+ VDBG( "IntCallback: Encapsulated Response = 0x%llx\n",
|
+ (*(u64*)pIntURB->transfer_buffer));
|
+
|
+ if ((pIntURB->actual_length == 8)
|
+ && ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
|
+
|
+ {
|
+ // Time to read
|
+ usb_fill_control_urb( pDev->mQMIDev.mpReadURB,
|
+ pDev->mpNetDev->udev,
|
+ usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ),
|
+ (unsigned char *)pDev->mQMIDev.mpReadSetupPacket,
|
+ pDev->mQMIDev.mpReadBuffer,
|
+ DEFAULT_READ_URB_LENGTH,
|
+ ReadCallback,
|
+ pDev );
|
+#ifdef READ_QMI_URB_ERROR
|
+ mod_timer( &pDev->mQMIDev.mReadUrbTimer, jiffies + msecs_to_jiffies(300) );
|
+#endif
|
+ status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
|
+ if (status != 0)
|
+ {
|
+ DBG( "Error submitting Read URB %d\n", status );
|
+
|
+ // Resubmit the interrupt urb
|
+ ResubmitIntURB( pIntURB );
|
+ return;
|
+ }
|
+
|
+ // Int URB will be resubmitted during ReadCallback
|
+ return;
|
+ }
|
+ // CDC CONNECTION_SPEED_CHANGE
|
+ else if ((pIntURB->actual_length == 16)
|
+ && (CDC_GET_CONNECTION_SPEED_CHANGE(&CDCEncResp, &CDCEncRespMask))
|
+ && ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
|
+ {
|
+ DBG( "IntCallback: Connection Speed Change = 0x%llx\n",
|
+ (*(u64*)pIntURB->transfer_buffer));
|
+
|
+ // if upstream or downstream is 0, stop traffic. Otherwise resume it
|
+ if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
|
+ || (*(u32*)(pIntURB->transfer_buffer + 12) == 0))
|
+ {
|
+ GobiSetDownReason( pDev, CDC_CONNECTION_SPEED );
|
+ DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
|
+ }
|
+ else
|
+ {
|
+ GobiClearDownReason( pDev, CDC_CONNECTION_SPEED );
|
+ DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" );
|
+ }
|
+ }
|
+ else
|
+ {
|
+ DBG( "ignoring invalid interrupt in packet\n" );
|
+ PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
|
+ }
|
+ }
|
+
|
+ // Resubmit the interrupt urb
|
+ ResubmitIntURB( pIntURB );
|
+
|
+ return;
|
+}
|
+
|
+#ifdef READ_QMI_URB_ERROR
|
+static void ReadUrbTimerFunc( struct urb * pReadURB )
|
+{
|
+ int result;
|
+
|
+ INFO( "%s called (%ld).\n", __func__, jiffies );
|
+
|
+ if ((pReadURB != NULL) && (pReadURB->status == -EINPROGRESS))
|
+ {
|
+ // Asynchronously unlink URB. On success, -EINPROGRESS will be returned,
|
+ // URB status will be set to -ECONNRESET, and ReadCallback() executed
|
+ result = usb_unlink_urb( pReadURB );
|
+ INFO( "%s called usb_unlink_urb, result = %d\n", __func__, result);
|
+ }
|
+}
|
+#endif
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ StartRead (Public Method)
|
+
|
+DESCRIPTION:
|
+ Start continuous read "thread" (callback driven)
|
+
|
+ Note: In case of error, KillRead() should be run
|
+ to remove urbs and clean up memory.
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int StartRead( sGobiUSBNet * pDev )
|
+{
|
+ int interval;
|
+ struct usb_endpoint_descriptor *pendp;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Allocate URB buffers
|
+ pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL );
|
+ if (pDev->mQMIDev.mpReadURB == NULL)
|
+ {
|
+ DBG( "Error allocating read urb\n" );
|
+ return -ENOMEM;
|
+ }
|
+
|
+#ifdef READ_QMI_URB_ERROR
|
+ setup_timer( &pDev->mQMIDev.mReadUrbTimer, (void*)ReadUrbTimerFunc, (unsigned long)pDev->mQMIDev.mpReadURB );
|
+#endif
|
+
|
+ pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL );
|
+ if (pDev->mQMIDev.mpIntURB == NULL)
|
+ {
|
+ DBG( "Error allocating int urb\n" );
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // Create data buffers
|
+ pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
|
+ if (pDev->mQMIDev.mpReadBuffer == NULL)
|
+ {
|
+ DBG( "Error allocating read buffer\n" );
|
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
|
+ pDev->mQMIDev.mpIntURB = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pDev->mQMIDev.mpIntBuffer = kmalloc( 64, GFP_KERNEL );
|
+ if (pDev->mQMIDev.mpIntBuffer == NULL)
|
+ {
|
+ DBG( "Error allocating int buffer\n" );
|
+ kfree( pDev->mQMIDev.mpReadBuffer );
|
+ pDev->mQMIDev.mpReadBuffer = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
|
+ pDev->mQMIDev.mpIntURB = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ),
|
+ GFP_KERNEL );
|
+ if (pDev->mQMIDev.mpReadSetupPacket == NULL)
|
+ {
|
+ DBG( "Error allocating setup packet buffer\n" );
|
+ kfree( pDev->mQMIDev.mpIntBuffer );
|
+ pDev->mQMIDev.mpIntBuffer = NULL;
|
+ kfree( pDev->mQMIDev.mpReadBuffer );
|
+ pDev->mQMIDev.mpReadBuffer = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
|
+ pDev->mQMIDev.mpIntURB = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // CDC Get Encapsulated Response packet
|
+ pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1;
|
+ pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1;
|
+ pDev->mQMIDev.mpReadSetupPacket->mValue = 0;
|
+ pDev->mQMIDev.mpReadSetupPacket->mIndex =
|
+ cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber); /* interface number */
|
+ pDev->mQMIDev.mpReadSetupPacket->mLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
|
+
|
+ pendp = GetEndpoint(pDev->mpIntf, USB_ENDPOINT_XFER_INT, USB_DIR_IN);
|
+ if (pendp == NULL)
|
+ {
|
+ DBG( "Invalid interrupt endpoint!\n" );
|
+ kfree(pDev->mQMIDev.mpReadSetupPacket);
|
+ pDev->mQMIDev.mpReadSetupPacket = NULL;
|
+ kfree( pDev->mQMIDev.mpIntBuffer );
|
+ pDev->mQMIDev.mpIntBuffer = NULL;
|
+ kfree( pDev->mQMIDev.mpReadBuffer );
|
+ pDev->mQMIDev.mpReadBuffer = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
|
+ pDev->mQMIDev.mpIntURB = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Interval needs reset after every URB completion
|
+ interval = max((int)(pendp->bInterval),
|
+ (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
|
+ s_interval = interval;
|
+#endif
|
+
|
+ // Schedule interrupt URB
|
+ usb_fill_int_urb( pDev->mQMIDev.mpIntURB,
|
+ pDev->mpNetDev->udev,
|
+ /* QMI interrupt endpoint for the following
|
+ * interface configuration: DM, NMEA, MDM, NET
|
+ */
|
+ usb_rcvintpipe( pDev->mpNetDev->udev,
|
+ pendp->bEndpointAddress),
|
+ pDev->mQMIDev.mpIntBuffer,
|
+ min((int)le16_to_cpu(pendp->wMaxPacketSize), 64),
|
+ IntCallback,
|
+ pDev,
|
+ interval );
|
+ return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ KillRead (Public Method)
|
+
|
+DESCRIPTION:
|
+ Kill continuous read "thread"
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void KillRead( sGobiUSBNet * pDev )
|
+{
|
+ // Stop reading
|
+ if (pDev->mQMIDev.mpReadURB != NULL)
|
+ {
|
+ DBG( "Killng read URB\n" );
|
+ usb_kill_urb( pDev->mQMIDev.mpReadURB );
|
+ }
|
+
|
+ if (pDev->mQMIDev.mpIntURB != NULL)
|
+ {
|
+ DBG( "Killng int URB\n" );
|
+ usb_kill_urb( pDev->mQMIDev.mpIntURB );
|
+ }
|
+
|
+ // Release buffers
|
+ kfree( pDev->mQMIDev.mpReadSetupPacket );
|
+ pDev->mQMIDev.mpReadSetupPacket = NULL;
|
+ kfree( pDev->mQMIDev.mpReadBuffer );
|
+ pDev->mQMIDev.mpReadBuffer = NULL;
|
+ kfree( pDev->mQMIDev.mpIntBuffer );
|
+ pDev->mQMIDev.mpIntBuffer = NULL;
|
+
|
+ // Release URB's
|
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
|
+ pDev->mQMIDev.mpReadURB = NULL;
|
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
|
+ pDev->mQMIDev.mpIntURB = NULL;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Internal read/write functions
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ReadAsync (Public Method)
|
+
|
+DESCRIPTION:
|
+ Start asynchronous read
|
+ NOTE: Reading client's data store, not device
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+ pCallback [ I ] - Callback to be executed when data is available
|
+ pData [ I ] - Data buffer that willl be passed (unmodified)
|
+ to callback
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int ReadAsync(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ u16 transactionID,
|
+ void (*pCallback)(sGobiUSBNet*, u16, void *),
|
+ void * pData )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sReadMemList ** ppReadMemList;
|
+
|
+ unsigned long flags;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Find memory storage for this client ID
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find matching client ID 0x%04X\n",
|
+ clientID );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -ENXIO;
|
+ }
|
+
|
+ ppReadMemList = &(pClientMem->mpList);
|
+
|
+ // Does data already exist?
|
+ while (*ppReadMemList != NULL)
|
+ {
|
+ // Is this element our data?
|
+ if (transactionID == 0
|
+ || transactionID == (*ppReadMemList)->mTransactionID)
|
+ {
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Run our own callback
|
+ pCallback( pDev, clientID, pData );
|
+
|
+ return 0;
|
+ }
|
+
|
+ // Next
|
+ ppReadMemList = &(*ppReadMemList)->mpNext;
|
+ }
|
+
|
+ // Data not found, add ourself to list of waiters
|
+ if (AddToNotifyList( pDev,
|
+ clientID,
|
+ transactionID,
|
+ pCallback,
|
+ pData ) == false)
|
+ {
|
+ DBG( "Unable to register for notification\n" );
|
+ }
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Success
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UpSem (Public Method)
|
+
|
+DESCRIPTION:
|
+ Notification function for synchronous read
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ pData [ I ] - Buffer that holds semaphore to be up()-ed
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void UpSem(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ void * pData )
|
+{
|
+ VDBG( "0x%04X\n", clientID );
|
+
|
+ up( (struct semaphore *)pData );
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ReadSync (Public Method)
|
+
|
+DESCRIPTION:
|
+ Start synchronous read
|
+ NOTE: Reading client's data store, not device
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ ppOutBuffer [I/O] - On success, will be filled with a
|
+ pointer to read buffer
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+
|
+RETURN VALUE:
|
+ int - size of data read for success
|
+ negative errno for failure
|
+===========================================================================*/
|
+int ReadSync(
|
+ sGobiUSBNet * pDev,
|
+ void ** ppOutBuffer,
|
+ u16 clientID,
|
+ u16 transactionID )
|
+{
|
+ int result;
|
+ sClientMemList * pClientMem;
|
+ sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
|
+ struct semaphore readSem;
|
+ void * pData;
|
+ unsigned long flags;
|
+ u16 dataSize;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Find memory storage for this Client ID
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find matching client ID 0x%04X\n",
|
+ clientID );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Note: in cases where read is interrupted,
|
+ // this will verify client is still valid
|
+ while (PopFromReadMemList( pDev,
|
+ clientID,
|
+ transactionID,
|
+ &pData,
|
+ &dataSize ) == false)
|
+ {
|
+ // Data does not yet exist, wait
|
+ sema_init( &readSem, 0 );
|
+
|
+ // Add ourself to list of waiters
|
+ if (AddToNotifyList( pDev,
|
+ clientID,
|
+ transactionID,
|
+ UpSem,
|
+ &readSem ) == false)
|
+ {
|
+ DBG( "unable to register for notification\n" );
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -EFAULT;
|
+ }
|
+
|
+ // End critical section while we block
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Wait for notification
|
+ result = down_interruptible( &readSem );
|
+ if (result != 0)
|
+ {
|
+ DBG( "Interrupted %d\n", result );
|
+
|
+ // readSem will fall out of scope,
|
+ // remove from notify list so it's not referenced
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+ ppNotifyList = &(pClientMem->mpReadNotifyList);
|
+ pDelNotifyListEntry = NULL;
|
+
|
+ // Find and delete matching entry
|
+ while (*ppNotifyList != NULL)
|
+ {
|
+ if ((*ppNotifyList)->mpData == &readSem)
|
+ {
|
+ pDelNotifyListEntry = *ppNotifyList;
|
+ *ppNotifyList = (*ppNotifyList)->mpNext;
|
+ kfree( pDelNotifyListEntry );
|
+ break;
|
+ }
|
+
|
+ // Next
|
+ ppNotifyList = &(*ppNotifyList)->mpNext;
|
+ }
|
+
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -EINTR;
|
+ }
|
+
|
+ // Verify device is still valid
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Restart critical section and continue loop
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+ }
|
+
|
+ // End Critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Success
|
+ *ppOutBuffer = pData;
|
+
|
+ return dataSize;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ WriteSyncCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ Write callback
|
+
|
+PARAMETERS
|
+ pWriteURB [ I ] - URB this callback is run for
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+void WriteSyncCallback( struct urb * pWriteURB )
|
+#else
|
+void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs)
|
+#endif
|
+{
|
+ if (pWriteURB == NULL)
|
+ {
|
+ DBG( "null urb\n" );
|
+ return;
|
+ }
|
+
|
+ DBG( "Write status/size %d/%d\n",
|
+ pWriteURB->status,
|
+ pWriteURB->actual_length );
|
+
|
+ // Notify that write has completed by up()-ing semeaphore
|
+ up( (struct semaphore * )pWriteURB->context );
|
+
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ WriteSync (Public Method)
|
+
|
+DESCRIPTION:
|
+ Start synchronous write
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ pWriteBuffer [ I ] - Data to be written
|
+ writeBufferSize [ I ] - Size of data to be written
|
+ clientID [ I ] - Client ID of requester
|
+
|
+RETURN VALUE:
|
+ int - write size (includes QMUX)
|
+ negative errno for failure
|
+===========================================================================*/
|
+int WriteSync(
|
+ sGobiUSBNet * pDev,
|
+ char * pWriteBuffer,
|
+ int writeBufferSize,
|
+ u16 clientID )
|
+{
|
+ int result;
|
+ struct semaphore writeSem;
|
+ struct urb * pWriteURB;
|
+ sURBSetupPacket writeSetup;
|
+ unsigned long flags;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ pWriteURB = usb_alloc_urb( 0, GFP_KERNEL );
|
+ if (pWriteURB == NULL)
|
+ {
|
+ DBG( "URB mem error\n" );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ // Fill writeBuffer with QMUX
|
+ result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
|
+ if (result < 0)
|
+ {
|
+ usb_free_urb( pWriteURB );
|
+ return result;
|
+ }
|
+
|
+ // CDC Send Encapsulated Request packet
|
+ writeSetup.mRequestType = 0x21;
|
+ writeSetup.mRequestCode = 0;
|
+ writeSetup.mValue = 0;
|
+ writeSetup.mIndex = cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);
|
+ writeSetup.mLength = cpu_to_le16(writeBufferSize);
|
+
|
+ // Create URB
|
+ usb_fill_control_urb( pWriteURB,
|
+ pDev->mpNetDev->udev,
|
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
|
+ (unsigned char *)&writeSetup,
|
+ (void*)pWriteBuffer,
|
+ writeBufferSize,
|
+ NULL,
|
+ pDev );
|
+
|
+ DBG( "Actual Write:\n" );
|
+ PrintHex( pWriteBuffer, writeBufferSize );
|
+
|
+ sema_init( &writeSem, 0 );
|
+
|
+ pWriteURB->complete = WriteSyncCallback;
|
+ pWriteURB->context = &writeSem;
|
+
|
+ // Wake device
|
+ result = usb_autopm_get_interface( pDev->mpIntf );
|
+ if (result < 0)
|
+ {
|
+ DBG( "unable to resume interface: %d\n", result );
|
+
|
+ // Likely caused by device going from autosuspend -> full suspend
|
+ if (result == -EPERM)
|
+ {
|
+#ifdef CONFIG_PM
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
|
+ pDev->mpNetDev->udev->auto_pm = 0;
|
+#endif
|
+#endif
|
+ GobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
|
+#endif /* CONFIG_PM */
|
+ }
|
+ usb_free_urb( pWriteURB );
|
+
|
+ return result;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ if (AddToURBList( pDev, clientID, pWriteURB ) == false)
|
+ {
|
+ usb_free_urb( pWriteURB );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ usb_autopm_put_interface( pDev->mpIntf );
|
+ return -EINVAL;
|
+ }
|
+
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ result = usb_submit_urb( pWriteURB, GFP_KERNEL );
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ if (result < 0)
|
+ {
|
+ DBG( "submit URB error %d\n", result );
|
+
|
+ // Get URB back so we can destroy it
|
+ if (PopFromURBList( pDev, clientID ) != pWriteURB)
|
+ {
|
+ // This shouldn't happen
|
+ DBG( "Didn't get write URB back\n" );
|
+ }
|
+
|
+ usb_free_urb( pWriteURB );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ usb_autopm_put_interface( pDev->mpIntf );
|
+ return result;
|
+ }
|
+
|
+ // End critical section while we block
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Wait for write to finish
|
+ if (interruptible != 0)
|
+ {
|
+ // Allow user interrupts
|
+ result = down_interruptible( &writeSem );
|
+ }
|
+ else
|
+ {
|
+ // Ignore user interrupts
|
+ result = 0;
|
+ down( &writeSem );
|
+ }
|
+
|
+ // Write is done, release device
|
+ usb_autopm_put_interface( pDev->mpIntf );
|
+
|
+ // Verify device is still valid
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+
|
+ usb_free_urb( pWriteURB );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Restart critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Get URB back so we can destroy it
|
+ if (PopFromURBList( pDev, clientID ) != pWriteURB)
|
+ {
|
+ // This shouldn't happen
|
+ DBG( "Didn't get write URB back\n" );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ usb_free_urb( pWriteURB );
|
+ return -EINVAL;
|
+ }
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ if (result == 0)
|
+ {
|
+ // Write is finished
|
+ if (pWriteURB->status == 0)
|
+ {
|
+ // Return number of bytes that were supposed to have been written,
|
+ // not size of QMI request
|
+ result = writeBufferSize;
|
+ }
|
+ else
|
+ {
|
+ DBG( "bad status = %d\n", pWriteURB->status );
|
+
|
+ // Return error value
|
+ result = pWriteURB->status;
|
+ }
|
+ }
|
+ else
|
+ {
|
+ // We have been forcibly interrupted
|
+ DBG( "Interrupted %d !!!\n", result );
|
+ DBG( "Device may be in bad state and need reset !!!\n" );
|
+
|
+ // URB has not finished
|
+ usb_kill_urb( pWriteURB );
|
+ }
|
+
|
+ usb_free_urb( pWriteURB );
|
+
|
+ return result;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Internal memory management functions
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ GetClientID (Public Method)
|
+
|
+DESCRIPTION:
|
+ Request a QMI client for the input service type and initialize memory
|
+ structure
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ serviceType [ I ] - Desired QMI service type
|
+
|
+RETURN VALUE:
|
+ int - Client ID for success (positive)
|
+ Negative errno for error
|
+===========================================================================*/
|
+int GetClientID(
|
+ sGobiUSBNet * pDev,
|
+ u8 serviceType )
|
+{
|
+ u16 clientID;
|
+ sClientMemList ** ppClientMem;
|
+ int result;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+ unsigned long flags;
|
+ u8 transactionID;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device!\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Run QMI request to be asigned a Client ID
|
+ if (serviceType != 0)
|
+ {
|
+ writeBufferSize = QMICTLGetClientIDReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ transactionID = QMIXactionIDGet( pDev );
|
+
|
+ result = QMICTLGetClientIDReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ transactionID,
|
+ serviceType );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ QMICTL );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ QMICTL,
|
+ transactionID );
|
+ if (result < 0)
|
+ {
|
+ DBG( "bad read data %d\n", result );
|
+ return result;
|
+ }
|
+ readBufferSize = result;
|
+
|
+ result = QMICTLGetClientIDResp( pReadBuffer,
|
+ readBufferSize,
|
+ &clientID );
|
+
|
+ /* Upon return from QMICTLGetClientIDResp, clientID
|
+ * low address contains the Service Number (SN), and
|
+ * clientID high address contains Client Number (CN)
|
+ * For the ReadCallback to function correctly,we swap
|
+ * the SN and CN on a Big Endian architecture.
|
+ */
|
+ clientID = le16_to_cpu(clientID);
|
+
|
+ kfree( pReadBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ }
|
+ else
|
+ {
|
+ // QMI CTL will always have client ID 0
|
+ clientID = 0;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Verify client is not already allocated
|
+ if (FindClientMem( pDev, clientID ) != NULL)
|
+ {
|
+ DBG( "Client memory already exists\n" );
|
+
|
+ // End Critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -ETOOMANYREFS;
|
+ }
|
+
|
+ // Go to last entry in client mem list
|
+ ppClientMem = &pDev->mQMIDev.mpClientMemList;
|
+ while (*ppClientMem != NULL)
|
+ {
|
+ ppClientMem = &(*ppClientMem)->mpNext;
|
+ }
|
+
|
+ // Create locations for read to place data into
|
+ *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC );
|
+ if (*ppClientMem == NULL)
|
+ {
|
+ DBG( "Error allocating read list\n" );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ (*ppClientMem)->mClientID = clientID;
|
+ (*ppClientMem)->mpList = NULL;
|
+ (*ppClientMem)->mpReadNotifyList = NULL;
|
+ (*ppClientMem)->mpURBList = NULL;
|
+ (*ppClientMem)->mpNext = NULL;
|
+
|
+ // Initialize workqueue for poll()
|
+ init_waitqueue_head( &(*ppClientMem)->mWaitQueue );
|
+
|
+ // End Critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ return (int)( (*ppClientMem)->mClientID );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ ReleaseClientID (Public Method)
|
+
|
+DESCRIPTION:
|
+ Release QMI client and free memory
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void ReleaseClientID(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID )
|
+{
|
+ int result;
|
+ sClientMemList ** ppDelClientMem;
|
+ sClientMemList * pNextClientMem;
|
+ struct urb * pDelURB;
|
+ void * pDelData;
|
+ u16 dataSize;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+ unsigned long flags;
|
+ u8 transactionID;
|
+
|
+ // Is device is still valid?
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "invalid device\n" );
|
+ return;
|
+ }
|
+
|
+ DBG( "releasing 0x%04X\n", clientID );
|
+
|
+ // Run QMI ReleaseClientID if this isn't QMICTL
|
+ if (clientID != QMICTL)
|
+ {
|
+ // Note: all errors are non fatal, as we always want to delete
|
+ // client memory in latter part of function
|
+
|
+ writeBufferSize = QMICTLReleaseClientIDReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ DBG( "memory error\n" );
|
+ }
|
+ else
|
+ {
|
+ transactionID = QMIXactionIDGet( pDev );
|
+
|
+ result = QMICTLReleaseClientIDReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ transactionID,
|
+ clientID );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ DBG( "error %d filling req buffer\n", result );
|
+ }
|
+ else
|
+ {
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ QMICTL );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ DBG( "bad write status %d\n", result );
|
+ }
|
+ else
|
+ {
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ QMICTL,
|
+ transactionID );
|
+ if (result < 0)
|
+ {
|
+ DBG( "bad read status %d\n", result );
|
+ }
|
+ else
|
+ {
|
+ readBufferSize = result;
|
+
|
+ result = QMICTLReleaseClientIDResp( pReadBuffer,
|
+ readBufferSize );
|
+ kfree( pReadBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ DBG( "error %d parsing response\n", result );
|
+ }
|
+ }
|
+ }
|
+ }
|
+ }
|
+ }
|
+
|
+ // Cleaning up client memory
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Can't use FindClientMem, I need to keep pointer of previous
|
+ ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
|
+ while (*ppDelClientMem != NULL)
|
+ {
|
+ if ((*ppDelClientMem)->mClientID == clientID)
|
+ {
|
+ pNextClientMem = (*ppDelClientMem)->mpNext;
|
+
|
+ // Notify all clients
|
+ while (NotifyAndPopNotifyList( pDev,
|
+ clientID,
|
+ 0 ) == true );
|
+
|
+ // Kill and free all URB's
|
+ pDelURB = PopFromURBList( pDev, clientID );
|
+ while (pDelURB != NULL)
|
+ {
|
+ usb_kill_urb( pDelURB );
|
+ usb_free_urb( pDelURB );
|
+ pDelURB = PopFromURBList( pDev, clientID );
|
+ }
|
+
|
+ // Free any unread data
|
+ while (PopFromReadMemList( pDev,
|
+ clientID,
|
+ 0,
|
+ &pDelData,
|
+ &dataSize ) == true )
|
+ {
|
+ kfree( pDelData );
|
+ }
|
+
|
+ // Delete client Mem
|
+ if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue))
|
+ kfree( *ppDelClientMem );
|
+ else
|
+ DBG("memory leak!\n");
|
+
|
+ // Overwrite the pointer that was to this client mem
|
+ *ppDelClientMem = pNextClientMem;
|
+ }
|
+ else
|
+ {
|
+ // I now point to (a pointer of ((the node I was at)'s mpNext))
|
+ ppDelClientMem = &(*ppDelClientMem)->mpNext;
|
+ }
|
+ }
|
+
|
+ // End Critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ FindClientMem (Public Method)
|
+
|
+DESCRIPTION:
|
+ Find this client's memory
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+
|
+RETURN VALUE:
|
+ sClientMemList - Pointer to requested sClientMemList for success
|
+ NULL for error
|
+===========================================================================*/
|
+sClientMemList * FindClientMem(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID )
|
+{
|
+ sClientMemList * pClientMem;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return NULL;
|
+ }
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ pClientMem = pDev->mQMIDev.mpClientMemList;
|
+ while (pClientMem != NULL)
|
+ {
|
+ if (pClientMem->mClientID == clientID)
|
+ {
|
+ // Success
|
+ VDBG("Found client's 0x%x memory\n", clientID);
|
+ return pClientMem;
|
+ }
|
+
|
+ pClientMem = pClientMem->mpNext;
|
+ }
|
+
|
+ DBG( "Could not find client mem 0x%04X\n", clientID );
|
+ return NULL;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ AddToReadMemList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Add Data to this client's ReadMem list
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+ pData [ I ] - Data to add
|
+ dataSize [ I ] - Size of data to add
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool AddToReadMemList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ u16 transactionID,
|
+ void * pData,
|
+ u16 dataSize )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sReadMemList ** ppThisReadMemList;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n",
|
+ clientID );
|
+
|
+ return false;
|
+ }
|
+
|
+ // Go to last ReadMemList entry
|
+ ppThisReadMemList = &pClientMem->mpList;
|
+ while (*ppThisReadMemList != NULL)
|
+ {
|
+ ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
|
+ }
|
+
|
+ *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
|
+ if (*ppThisReadMemList == NULL)
|
+ {
|
+ DBG( "Mem error\n" );
|
+
|
+ return false;
|
+ }
|
+
|
+ (*ppThisReadMemList)->mpNext = NULL;
|
+ (*ppThisReadMemList)->mpData = pData;
|
+ (*ppThisReadMemList)->mDataSize = dataSize;
|
+ (*ppThisReadMemList)->mTransactionID = transactionID;
|
+
|
+ return true;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ PopFromReadMemList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Remove data from this client's ReadMem list if it matches
|
+ the specified transaction ID.
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+ ppData [I/O] - On success, will be filled with a
|
+ pointer to read buffer
|
+ pDataSize [I/O] - On succces, will be filled with the
|
+ read buffer's size
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool PopFromReadMemList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ u16 transactionID,
|
+ void ** ppData,
|
+ u16 * pDataSize )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sReadMemList * pDelReadMemList, ** ppReadMemList;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n",
|
+ clientID );
|
+
|
+ return false;
|
+ }
|
+
|
+ ppReadMemList = &(pClientMem->mpList);
|
+ pDelReadMemList = NULL;
|
+
|
+ // Find first message that matches this transaction ID
|
+ while (*ppReadMemList != NULL)
|
+ {
|
+ // Do we care about transaction ID?
|
+ if (transactionID == 0
|
+ || transactionID == (*ppReadMemList)->mTransactionID )
|
+ {
|
+ pDelReadMemList = *ppReadMemList;
|
+ VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
|
+ *ppReadMemList, pDelReadMemList );
|
+ break;
|
+ }
|
+
|
+ VDBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID );
|
+
|
+ // Next
|
+ ppReadMemList = &(*ppReadMemList)->mpNext;
|
+ }
|
+ VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
|
+ *ppReadMemList, pDelReadMemList );
|
+ if (pDelReadMemList != NULL)
|
+ {
|
+ *ppReadMemList = (*ppReadMemList)->mpNext;
|
+
|
+ // Copy to output
|
+ *ppData = pDelReadMemList->mpData;
|
+ *pDataSize = pDelReadMemList->mDataSize;
|
+ VDBG( "*ppData = 0x%p pDataSize = %u\n",
|
+ *ppData, *pDataSize );
|
+
|
+ // Free memory
|
+ kfree( pDelReadMemList );
|
+
|
+ return true;
|
+ }
|
+ else
|
+ {
|
+ DBG( "No read memory to pop, Client 0x%04X, TID = %x\n",
|
+ clientID,
|
+ transactionID );
|
+ return false;
|
+ }
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ AddToNotifyList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Add Notify entry to this client's notify List
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+ pNotifyFunct [ I ] - Callback function to be run when data is available
|
+ pData [ I ] - Data buffer that willl be passed (unmodified)
|
+ to callback
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool AddToNotifyList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ u16 transactionID,
|
+ void (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
|
+ void * pData )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sNotifyList ** ppThisNotifyList;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n", clientID );
|
+ return false;
|
+ }
|
+
|
+ // Go to last URBList entry
|
+ ppThisNotifyList = &pClientMem->mpReadNotifyList;
|
+ while (*ppThisNotifyList != NULL)
|
+ {
|
+ ppThisNotifyList = &(*ppThisNotifyList)->mpNext;
|
+ }
|
+
|
+ *ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC );
|
+ if (*ppThisNotifyList == NULL)
|
+ {
|
+ DBG( "Mem error\n" );
|
+ return false;
|
+ }
|
+
|
+ (*ppThisNotifyList)->mpNext = NULL;
|
+ (*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct;
|
+ (*ppThisNotifyList)->mpData = pData;
|
+ (*ppThisNotifyList)->mTransactionID = transactionID;
|
+
|
+ return true;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ NotifyAndPopNotifyList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Remove first Notify entry from this client's notify list
|
+ and Run function
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ transactionID [ I ] - Transaction ID or 0 for any
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool NotifyAndPopNotifyList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ u16 transactionID )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sNotifyList * pDelNotifyList, ** ppNotifyList;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n", clientID );
|
+ return false;
|
+ }
|
+
|
+ ppNotifyList = &(pClientMem->mpReadNotifyList);
|
+ pDelNotifyList = NULL;
|
+
|
+ // Remove from list
|
+ while (*ppNotifyList != NULL)
|
+ {
|
+ // Do we care about transaction ID?
|
+ if (transactionID == 0
|
+ || (*ppNotifyList)->mTransactionID == 0
|
+ || transactionID == (*ppNotifyList)->mTransactionID)
|
+ {
|
+ pDelNotifyList = *ppNotifyList;
|
+ break;
|
+ }
|
+
|
+ DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
|
+
|
+ // next
|
+ ppNotifyList = &(*ppNotifyList)->mpNext;
|
+ }
|
+
|
+ if (pDelNotifyList != NULL)
|
+ {
|
+ // Remove element
|
+ *ppNotifyList = (*ppNotifyList)->mpNext;
|
+
|
+ // Run notification function
|
+ if (pDelNotifyList->mpNotifyFunct != NULL)
|
+ {
|
+ // Unlock for callback
|
+ spin_unlock( &pDev->mQMIDev.mClientMemLock );
|
+
|
+ pDelNotifyList->mpNotifyFunct( pDev,
|
+ clientID,
|
+ pDelNotifyList->mpData );
|
+
|
+ // Restore lock
|
+ spin_lock( &pDev->mQMIDev.mClientMemLock );
|
+ }
|
+
|
+ // Delete memory
|
+ kfree( pDelNotifyList );
|
+
|
+ return true;
|
+ }
|
+ else
|
+ {
|
+ DBG( "no one to notify for TID %x\n", transactionID );
|
+
|
+ return false;
|
+ }
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ AddToURBList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Add URB to this client's URB list
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+ pURB [ I ] - URB to be added
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool AddToURBList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ struct urb * pURB )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sURBList ** ppThisURBList;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n", clientID );
|
+ return false;
|
+ }
|
+
|
+ // Go to last URBList entry
|
+ ppThisURBList = &pClientMem->mpURBList;
|
+ while (*ppThisURBList != NULL)
|
+ {
|
+ ppThisURBList = &(*ppThisURBList)->mpNext;
|
+ }
|
+
|
+ *ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
|
+ if (*ppThisURBList == NULL)
|
+ {
|
+ DBG( "Mem error\n" );
|
+ return false;
|
+ }
|
+
|
+ (*ppThisURBList)->mpNext = NULL;
|
+ (*ppThisURBList)->mpURB = pURB;
|
+
|
+ return true;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ PopFromURBList (Public Method)
|
+
|
+DESCRIPTION:
|
+ Remove URB from this client's URB list
|
+
|
+ Caller MUST have lock on mClientMemLock
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Requester's client ID
|
+
|
+RETURN VALUE:
|
+ struct urb - Pointer to requested client's URB
|
+ NULL for error
|
+===========================================================================*/
|
+struct urb * PopFromURBList(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID )
|
+{
|
+ sClientMemList * pClientMem;
|
+ sURBList * pDelURBList;
|
+ struct urb * pURB;
|
+
|
+#ifdef CONFIG_SMP
|
+ // Verify Lock
|
+ if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
|
+ {
|
+ DBG( "unlocked\n" );
|
+ BUG();
|
+ }
|
+#endif
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pDev, clientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n", clientID );
|
+ return NULL;
|
+ }
|
+
|
+ // Remove from list
|
+ if (pClientMem->mpURBList != NULL)
|
+ {
|
+ pDelURBList = pClientMem->mpURBList;
|
+ pClientMem->mpURBList = pClientMem->mpURBList->mpNext;
|
+
|
+ // Copy to output
|
+ pURB = pDelURBList->mpURB;
|
+
|
+ // Delete memory
|
+ kfree( pDelURBList );
|
+
|
+ return pURB;
|
+ }
|
+ else
|
+ {
|
+ DBG( "No URB's to pop\n" );
|
+
|
+ return NULL;
|
+ }
|
+}
|
+
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
|
+#ifndef f_dentry
|
+#define f_dentry f_path.dentry
|
+#endif
|
+#endif
|
+
|
+/*=========================================================================*/
|
+// Internal userspace wrappers
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceunlockedIOCTL (Public Method)
|
+
|
+DESCRIPTION:
|
+ Internal wrapper for Userspace IOCTL interface
|
+
|
+PARAMETERS
|
+ pFilp [ I ] - userspace file descriptor
|
+ cmd [ I ] - IOCTL command
|
+ arg [ I ] - IOCTL argument
|
+
|
+RETURN VALUE:
|
+ long - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+long UserspaceunlockedIOCTL(
|
+ struct file * pFilp,
|
+ unsigned int cmd,
|
+ unsigned long arg )
|
+{
|
+ int result;
|
+ u32 devVIDPID;
|
+
|
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+
|
+ if (pFilpData == NULL)
|
+ {
|
+ DBG( "Bad file data\n" );
|
+ return -EBADF;
|
+ }
|
+
|
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
|
+ {
|
+ DBG( "Invalid device! Updating f_ops\n" );
|
+ pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
|
+ return -ENXIO;
|
+ }
|
+
|
+ switch (cmd)
|
+ {
|
+ case IOCTL_QMI_GET_SERVICE_FILE:
|
+ DBG( "Setting up QMI for service %lu\n", arg );
|
+ if ((u8)arg == 0)
|
+ {
|
+ DBG( "Cannot use QMICTL from userspace\n" );
|
+ return -EINVAL;
|
+ }
|
+
|
+ // Connection is already setup
|
+ if (pFilpData->mClientID != (u16)-1)
|
+ {
|
+ DBG( "Close the current connection before opening a new one\n" );
|
+ return -EBADR;
|
+ }
|
+
|
+ result = GetClientID( pFilpData->mpDev, (u8)arg );
|
+// it seems QMIWDA only allow one client, if the last quectel-CM donot realese it (killed by SIGKILL).
|
+// can force release it at here
|
+#if 1
|
+ if (result < 0 && (u8)arg == QMIWDA)
|
+ {
|
+ ReleaseClientID( pFilpData->mpDev, QMIWDA | (1 << 8) );
|
+ result = GetClientID( pFilpData->mpDev, (u8)arg );
|
+ }
|
+#endif
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ pFilpData->mClientID = (u16)result;
|
+ DBG("pFilpData->mClientID = 0x%x\n", pFilpData->mClientID );
|
+ return 0;
|
+ break;
|
+
|
+
|
+ case IOCTL_QMI_GET_DEVICE_VIDPID:
|
+ if (arg == 0)
|
+ {
|
+ DBG( "Bad VIDPID buffer\n" );
|
+ return -EINVAL;
|
+ }
|
+
|
+ // Extra verification
|
+ if (pFilpData->mpDev->mpNetDev == 0)
|
+ {
|
+ DBG( "Bad mpNetDev\n" );
|
+ return -ENOMEM;
|
+ }
|
+ if (pFilpData->mpDev->mpNetDev->udev == 0)
|
+ {
|
+ DBG( "Bad udev\n" );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16)
|
+ + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) );
|
+
|
+ result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 );
|
+ if (result != 0)
|
+ {
|
+ DBG( "Copy to userspace failure %d\n", result );
|
+ }
|
+
|
+ return result;
|
+
|
+ break;
|
+
|
+ case IOCTL_QMI_GET_DEVICE_MEID:
|
+ if (arg == 0)
|
+ {
|
+ DBG( "Bad MEID buffer\n" );
|
+ return -EINVAL;
|
+ }
|
+
|
+ result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 );
|
+ if (result != 0)
|
+ {
|
+ DBG( "Copy to userspace failure %d\n", result );
|
+ }
|
+
|
+ return result;
|
+
|
+ break;
|
+
|
+ default:
|
+ return -EBADRQC;
|
+ }
|
+}
|
+
|
+/*=========================================================================*/
|
+// Userspace wrappers
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceOpen (Public Method)
|
+
|
+DESCRIPTION:
|
+ Userspace open
|
+ IOCTL must be called before reads or writes
|
+
|
+PARAMETERS
|
+ pInode [ I ] - kernel file descriptor
|
+ pFilp [ I ] - userspace file descriptor
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+int UserspaceOpen(
|
+ struct inode * pInode,
|
+ struct file * pFilp )
|
+{
|
+ sQMIFilpStorage * pFilpData;
|
+
|
+ // Optain device pointer from pInode
|
+ sQMIDev * pQMIDev = container_of( pInode->i_cdev,
|
+ sQMIDev,
|
+ mCdev );
|
+ sGobiUSBNet * pDev = container_of( pQMIDev,
|
+ sGobiUSBNet,
|
+ mQMIDev );
|
+
|
+ if (pDev->mbMdm9x07)
|
+ {
|
+ atomic_inc(&pDev->refcount);
|
+ if (!pDev->mbQMIReady) {
|
+ if (wait_for_completion_interruptible_timeout(&pDev->mQMIReadyCompletion, 15*HZ) <= 0) {
|
+ if (atomic_dec_and_test(&pDev->refcount)) {
|
+ kfree( pDev );
|
+ }
|
+ return -ETIMEDOUT;
|
+ }
|
+ }
|
+ atomic_dec(&pDev->refcount);
|
+ }
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Setup data in pFilp->private_data
|
+ pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
|
+ if (pFilp->private_data == NULL)
|
+ {
|
+ DBG( "Mem error\n" );
|
+ return -ENOMEM;
|
+ }
|
+
|
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+ pFilpData->mClientID = (u16)-1;
|
+ atomic_inc(&pDev->refcount);
|
+ pFilpData->mpDev = pDev;
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceIOCTL (Public Method)
|
+
|
+DESCRIPTION:
|
+ Userspace IOCTL functions
|
+
|
+PARAMETERS
|
+ pUnusedInode [ I ] - (unused) kernel file descriptor
|
+ pFilp [ I ] - userspace file descriptor
|
+ cmd [ I ] - IOCTL command
|
+ arg [ I ] - IOCTL argument
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+int UserspaceIOCTL(
|
+ struct inode * pUnusedInode,
|
+ struct file * pFilp,
|
+ unsigned int cmd,
|
+ unsigned long arg )
|
+{
|
+ // call the internal wrapper function
|
+ return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg );
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceClose (Public Method)
|
+
|
+DESCRIPTION:
|
+ Userspace close
|
+ Release client ID and free memory
|
+
|
+PARAMETERS
|
+ pFilp [ I ] - userspace file descriptor
|
+ unusedFileTable [ I ] - (unused) file table
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
|
+int UserspaceClose(
|
+ struct file * pFilp,
|
+ fl_owner_t unusedFileTable )
|
+#else
|
+int UserspaceClose( struct file * pFilp )
|
+#endif
|
+{
|
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+ struct task_struct * pEachTask;
|
+ struct fdtable * pFDT;
|
+ int count = 0;
|
+ int used = 0;
|
+ unsigned long flags;
|
+
|
+ if (pFilpData == NULL)
|
+ {
|
+ DBG( "bad file data\n" );
|
+ return -EBADF;
|
+ }
|
+
|
+ // Fallthough. If f_count == 1 no need to do more checks
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
|
+ if (atomic_read( &pFilp->f_count ) != 1)
|
+#else
|
+ if (atomic_long_read( &pFilp->f_count ) != 1)
|
+#endif
|
+ {
|
+ rcu_read_lock();
|
+ for_each_process( pEachTask )
|
+ {
|
+ task_lock(pEachTask);
|
+ if (pEachTask == NULL || pEachTask->files == NULL)
|
+ {
|
+ // Some tasks may not have files (e.g. Xsession)
|
+ task_unlock(pEachTask);
|
+ continue;
|
+ }
|
+ spin_lock_irqsave( &pEachTask->files->file_lock, flags );
|
+ task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
|
+ pFDT = files_fdtable( pEachTask->files );
|
+ for (count = 0; count < pFDT->max_fds; count++)
|
+ {
|
+ // Before this function was called, this file was removed
|
+ // from our task's file table so if we find it in a file
|
+ // table then it is being used by another task
|
+ if (pFDT->fd[count] == pFilp)
|
+ {
|
+ used++;
|
+ break;
|
+ }
|
+ }
|
+ spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
|
+ }
|
+ rcu_read_unlock();
|
+
|
+ if (used > 0)
|
+ {
|
+ DBG( "not closing, as this FD is open by %d other process\n", used );
|
+ return 0;
|
+ }
|
+ }
|
+
|
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
|
+ {
|
+ DBG( "Invalid device! Updating f_ops\n" );
|
+ pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
|
+ return -ENXIO;
|
+ }
|
+
|
+ DBG( "0x%04X\n", pFilpData->mClientID );
|
+
|
+ // Disable pFilpData so they can't keep sending read or write
|
+ // should this function hang
|
+ // Note: memory pointer is still saved in pFilpData to be deleted later
|
+ pFilp->private_data = NULL;
|
+
|
+ if (pFilpData->mClientID != (u16)-1)
|
+ {
|
+ if (pFilpData->mpDev->mbDeregisterQMIDevice)
|
+ pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
|
+ else
|
+ ReleaseClientID( pFilpData->mpDev,
|
+ pFilpData->mClientID );
|
+ }
|
+ atomic_dec(&pFilpData->mpDev->refcount);
|
+
|
+ kfree( pFilpData );
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceRead (Public Method)
|
+
|
+DESCRIPTION:
|
+ Userspace read (synchronous)
|
+
|
+PARAMETERS
|
+ pFilp [ I ] - userspace file descriptor
|
+ pBuf [ I ] - read buffer
|
+ size [ I ] - size of read buffer
|
+ pUnusedFpos [ I ] - (unused) file position
|
+
|
+RETURN VALUE:
|
+ ssize_t - Number of bytes read for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+ssize_t UserspaceRead(
|
+ struct file * pFilp,
|
+ char __user * pBuf,
|
+ size_t size,
|
+ loff_t * pUnusedFpos )
|
+{
|
+ int result;
|
+ void * pReadData = NULL;
|
+ void * pSmallReadData;
|
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+
|
+ if (pFilpData == NULL)
|
+ {
|
+ DBG( "Bad file data\n" );
|
+ return -EBADF;
|
+ }
|
+
|
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
|
+ {
|
+ DBG( "Invalid device! Updating f_ops\n" );
|
+ pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
|
+ return -ENXIO;
|
+ }
|
+
|
+ if (pFilpData->mClientID == (u16)-1)
|
+ {
|
+ DBG( "Client ID must be set before reading 0x%04X\n",
|
+ pFilpData->mClientID );
|
+ return -EBADR;
|
+ }
|
+
|
+ // Perform synchronous read
|
+ result = ReadSync( pFilpData->mpDev,
|
+ &pReadData,
|
+ pFilpData->mClientID,
|
+ 0 );
|
+ if (result <= 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // Discard QMUX header
|
+ result -= QMUXHeaderSize();
|
+ pSmallReadData = pReadData + QMUXHeaderSize();
|
+
|
+ if (result > size)
|
+ {
|
+ DBG( "Read data is too large for amount user has requested\n" );
|
+ kfree( pReadData );
|
+ return -EOVERFLOW;
|
+ }
|
+
|
+ DBG( "pBuf = 0x%p pSmallReadData = 0x%p, result = %d",
|
+ pBuf, pSmallReadData, result );
|
+
|
+ if (copy_to_user( pBuf, pSmallReadData, result ) != 0)
|
+ {
|
+ DBG( "Error copying read data to user\n" );
|
+ result = -EFAULT;
|
+ }
|
+
|
+ // Reader is responsible for freeing read buffer
|
+ kfree( pReadData );
|
+
|
+ return result;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspaceWrite (Public Method)
|
+
|
+DESCRIPTION:
|
+ Userspace write (synchronous)
|
+
|
+PARAMETERS
|
+ pFilp [ I ] - userspace file descriptor
|
+ pBuf [ I ] - write buffer
|
+ size [ I ] - size of write buffer
|
+ pUnusedFpos [ I ] - (unused) file position
|
+
|
+RETURN VALUE:
|
+ ssize_t - Number of bytes read for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+ssize_t UserspaceWrite(
|
+ struct file * pFilp,
|
+ const char __user * pBuf,
|
+ size_t size,
|
+ loff_t * pUnusedFpos )
|
+{
|
+ int status;
|
+ void * pWriteBuffer;
|
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+
|
+ if (pFilpData == NULL)
|
+ {
|
+ DBG( "Bad file data\n" );
|
+ return -EBADF;
|
+ }
|
+
|
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
|
+ {
|
+ DBG( "Invalid device! Updating f_ops\n" );
|
+ pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
|
+ return -ENXIO;
|
+ }
|
+
|
+ if (pFilpData->mClientID == (u16)-1)
|
+ {
|
+ DBG( "Client ID must be set before writing 0x%04X\n",
|
+ pFilpData->mClientID );
|
+ return -EBADR;
|
+ }
|
+
|
+ // Copy data from user to kernel space
|
+ pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+ status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size );
|
+ if (status != 0)
|
+ {
|
+ DBG( "Unable to copy data from userspace %d\n", status );
|
+ kfree( pWriteBuffer );
|
+ return status;
|
+ }
|
+
|
+ status = WriteSync( pFilpData->mpDev,
|
+ pWriteBuffer,
|
+ size + QMUXHeaderSize(),
|
+ pFilpData->mClientID );
|
+
|
+ kfree( pWriteBuffer );
|
+
|
+ // On success, return requested size, not full QMI reqest size
|
+ if (status == size + QMUXHeaderSize())
|
+ {
|
+ return size;
|
+ }
|
+ else
|
+ {
|
+ return status;
|
+ }
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ UserspacePoll (Public Method)
|
+
|
+DESCRIPTION:
|
+ Used to determine if read/write operations are possible without blocking
|
+
|
+PARAMETERS
|
+ pFilp [ I ] - userspace file descriptor
|
+ pPollTable [I/O] - Wait object to notify the kernel when data
|
+ is ready
|
+
|
+RETURN VALUE:
|
+ unsigned int - bitmask of what operations can be done immediately
|
+===========================================================================*/
|
+unsigned int UserspacePoll(
|
+ struct file * pFilp,
|
+ struct poll_table_struct * pPollTable )
|
+{
|
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
|
+ sClientMemList * pClientMem;
|
+ unsigned long flags;
|
+
|
+ // Always ready to write
|
+ unsigned long status = POLLOUT | POLLWRNORM;
|
+
|
+ if (pFilpData == NULL)
|
+ {
|
+ DBG( "Bad file data\n" );
|
+ return POLLERR;
|
+ }
|
+
|
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
|
+ {
|
+ DBG( "Invalid device! Updating f_ops\n" );
|
+ pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
|
+ return POLLERR;
|
+ }
|
+
|
+ if (pFilpData->mpDev->mbDeregisterQMIDevice)
|
+ {
|
+ DBG( "DeregisterQMIDevice ing\n" );
|
+ return POLLHUP | POLLERR;
|
+ }
|
+
|
+ if (pFilpData->mClientID == (u16)-1)
|
+ {
|
+ DBG( "Client ID must be set before polling 0x%04X\n",
|
+ pFilpData->mClientID );
|
+ return POLLERR;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Get this client's memory location
|
+ pClientMem = FindClientMem( pFilpData->mpDev,
|
+ pFilpData->mClientID );
|
+ if (pClientMem == NULL)
|
+ {
|
+ DBG( "Could not find this client's memory 0x%04X\n",
|
+ pFilpData->mClientID );
|
+
|
+ spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock,
|
+ flags );
|
+ return POLLERR;
|
+ }
|
+
|
+ poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable );
|
+
|
+ if (pClientMem->mpList != NULL)
|
+ {
|
+ status |= POLLIN | POLLRDNORM;
|
+ }
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Always ready to write
|
+ return (status | POLLOUT | POLLWRNORM);
|
+}
|
+
|
+/*=========================================================================*/
|
+// Initializer and destructor
|
+/*=========================================================================*/
|
+int QMICTLSyncProc(sGobiUSBNet *pDev)
|
+{
|
+ void *pWriteBuffer;
|
+ void *pReadBuffer;
|
+ int result;
|
+ u16 writeBufferSize;
|
+ u16 readBufferSize;
|
+ u8 transactionID;
|
+ unsigned long flags;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return -EFAULT;
|
+ }
|
+
|
+ writeBufferSize= QMICTLSyncReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ transactionID = QMIXactionIDGet(pDev);
|
+
|
+ /* send a QMI_CTL_SYNC_REQ (0x0027) */
|
+ result = QMICTLSyncReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ transactionID );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ QMICTL );
|
+
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ // QMI CTL Sync Response
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ QMICTL,
|
+ transactionID );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ result = QMICTLSyncResp( pReadBuffer,
|
+ (u16)result );
|
+
|
+ kfree( pReadBuffer );
|
+
|
+ if (result < 0) /* need to re-sync */
|
+ {
|
+ DBG( "sync response error code %d\n", result );
|
+ /* start timer and wait for the response */
|
+ /* process response */
|
+ return result;
|
+ }
|
+
|
+#if 1 //free these ununsed qmi response, or when these transactionID re-used, they will be regarded as qmi response of the qmi request that have same transactionID
|
+ // Enter critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Free any unread data
|
+ while (PopFromReadMemList( pDev, QMICTL, 0, &pReadBuffer, &readBufferSize) == true) {
|
+ kfree( pReadBuffer );
|
+ }
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+#endif
|
+
|
+ // Success
|
+ return 0;
|
+}
|
+
|
+static int qmi_sync_thread(void *data) {
|
+ sGobiUSBNet * pDev = (sGobiUSBNet *)data;
|
+ int result = 0;
|
+
|
+#if 1
|
+ // Device is not ready for QMI connections right away
|
+ // Wait up to 30 seconds before failing
|
+ if (QMIReady( pDev, 30000 ) == false)
|
+ {
|
+ DBG( "Device unresponsive to QMI\n" );
|
+ goto __qmi_sync_finished;
|
+ }
|
+
|
+ // Initiate QMI CTL Sync Procedure
|
+ DBG( "Sending QMI CTL Sync Request\n" );
|
+ result = QMICTLSyncProc(pDev);
|
+ if (result != 0)
|
+ {
|
+ DBG( "QMI CTL Sync Procedure Error\n" );
|
+ goto __qmi_sync_finished;
|
+ }
|
+ else
|
+ {
|
+ DBG( "QMI CTL Sync Procedure Successful\n" );
|
+ }
|
+
|
+ // Setup Data Format
|
+ result = QMIWDASetDataFormat (pDev);
|
+ if (result != 0)
|
+ {
|
+ goto __qmi_sync_finished;
|
+ }
|
+
|
+ // Setup WDS callback
|
+ result = SetupQMIWDSCallback( pDev );
|
+ if (result != 0)
|
+ {
|
+ goto __qmi_sync_finished;
|
+ }
|
+
|
+ // Fill MEID for device
|
+ result = QMIDMSGetMEID( pDev );
|
+ if (result != 0)
|
+ {
|
+ goto __qmi_sync_finished;
|
+ }
|
+#endif
|
+
|
+__qmi_sync_finished:
|
+ pDev->mbQMIReady = true;
|
+ complete_all(&pDev->mQMIReadyCompletion);
|
+ if (atomic_dec_and_test(&pDev->refcount)) {
|
+ kfree( pDev );
|
+ }
|
+ return result;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ RegisterQMIDevice (Public Method)
|
+
|
+DESCRIPTION:
|
+ QMI Device initialization function
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+int RegisterQMIDevice( sGobiUSBNet * pDev )
|
+{
|
+ int result;
|
+ int GobiQMIIndex = 0;
|
+ dev_t devno;
|
+ char * pDevName;
|
+
|
+ if (pDev->mQMIDev.mbCdevIsInitialized == true)
|
+ {
|
+ // Should never happen, but always better to check
|
+ DBG( "device already exists\n" );
|
+ return -EEXIST;
|
+ }
|
+
|
+ pDev->mbQMIValid = true;
|
+ pDev->mbDeregisterQMIDevice = false;
|
+
|
+ // Set up for QMICTL
|
+ // (does not send QMI message, just sets up memory)
|
+ result = GetClientID( pDev, QMICTL );
|
+ if (result != 0)
|
+ {
|
+ pDev->mbQMIValid = false;
|
+ return result;
|
+ }
|
+ atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
|
+
|
+ // Start Async reading
|
+ result = StartRead( pDev );
|
+ if (result != 0)
|
+ {
|
+ pDev->mbQMIValid = false;
|
+ return result;
|
+ }
|
+
|
+ if (pDev->mbMdm9x07)
|
+ {
|
+ usb_control_msg( pDev->mpNetDev->udev,
|
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
|
+ SET_CONTROL_LINE_STATE_REQUEST,
|
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
|
+ CONTROL_DTR,
|
+ /* USB interface number to receive control message */
|
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
|
+ NULL,
|
+ 0,
|
+ 100 );
|
+ }
|
+
|
+ //for EC21&25, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe).
|
+ if (pDev->mbMdm9x07)
|
+ {
|
+ struct task_struct *qmi_sync_task;
|
+ atomic_inc(&pDev->refcount);
|
+ init_completion(&pDev->mQMIReadyCompletion);
|
+ pDev->mbQMIReady = false;
|
+ qmi_sync_task = kthread_run(qmi_sync_thread, (void *)pDev, "qmi_sync/%d", pDev->mpNetDev->udev->devnum);
|
+ if (IS_ERR(qmi_sync_task)) {
|
+ atomic_dec(&pDev->refcount);
|
+ DBG( "Create qmi_sync_thread fail\n" );
|
+ return PTR_ERR(qmi_sync_task);
|
+ }
|
+ goto __register_chardev_qccmi;
|
+ }
|
+
|
+ // Device is not ready for QMI connections right away
|
+ // Wait up to 30 seconds before failing
|
+ if (QMIReady( pDev, 30000 ) == false)
|
+ {
|
+ DBG( "Device unresponsive to QMI\n" );
|
+ return -ETIMEDOUT;
|
+ }
|
+
|
+ // Initiate QMI CTL Sync Procedure
|
+ DBG( "Sending QMI CTL Sync Request\n" );
|
+ result = QMICTLSyncProc(pDev);
|
+ if (result != 0)
|
+ {
|
+ DBG( "QMI CTL Sync Procedure Error\n" );
|
+ return result;
|
+ }
|
+ else
|
+ {
|
+ DBG( "QMI CTL Sync Procedure Successful\n" );
|
+ }
|
+
|
+ // Setup Data Format
|
+ result = QMIWDASetDataFormat (pDev);
|
+ if (result != 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // Setup WDS callback
|
+ result = SetupQMIWDSCallback( pDev );
|
+ if (result != 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // Fill MEID for device
|
+ result = QMIDMSGetMEID( pDev );
|
+ if (result != 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+__register_chardev_qccmi:
|
+ // allocate and fill devno with numbers
|
+ result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // Create cdev
|
+ cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
|
+ pDev->mQMIDev.mCdev.owner = THIS_MODULE;
|
+ pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
|
+ pDev->mQMIDev.mbCdevIsInitialized = true;
|
+
|
+ result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
|
+ if (result != 0)
|
+ {
|
+ DBG( "error adding cdev\n" );
|
+ return result;
|
+ }
|
+
|
+ // Match interface number (usb# or eth#)
|
+ if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "eth" ))) {
|
+ pDevName += strlen( "eth" );
|
+ } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "usb" ))) {
|
+ pDevName += strlen( "usb" );
|
+#if 1 //openWRT like use ppp# or lte#
|
+ } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "ppp" ))) {
|
+ pDevName += strlen( "ppp" );
|
+ } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "lte" ))) {
|
+ pDevName += strlen( "lte" );
|
+#endif
|
+ } else {
|
+ DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name );
|
+ return -ENXIO;
|
+ }
|
+ GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 );
|
+ if (GobiQMIIndex < 0)
|
+ {
|
+ DBG( "Bad minor number\n" );
|
+ return -ENXIO;
|
+ }
|
+
|
+ // Always print this output
|
+ printk( KERN_INFO "creating qcqmi%d\n",
|
+ GobiQMIIndex );
|
+
|
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 ))
|
+ // kernel 2.6.27 added a new fourth parameter to device_create
|
+ // void * drvdata : the data to be added to the device for callbacks
|
+ device_create( pDev->mQMIDev.mpDevClass,
|
+ &pDev->mpIntf->dev,
|
+ devno,
|
+ NULL,
|
+ "qcqmi%d",
|
+ GobiQMIIndex );
|
+#else
|
+ device_create( pDev->mQMIDev.mpDevClass,
|
+ &pDev->mpIntf->dev,
|
+ devno,
|
+ "qcqmi%d",
|
+ GobiQMIIndex );
|
+#endif
|
+
|
+ pDev->mQMIDev.mDevNum = devno;
|
+
|
+ // Success
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ DeregisterQMIDevice (Public Method)
|
+
|
+DESCRIPTION:
|
+ QMI Device cleanup function
|
+
|
+ NOTE: When this function is run the device is no longer valid
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void DeregisterQMIDevice( sGobiUSBNet * pDev )
|
+{
|
+ struct inode * pOpenInode;
|
+ struct list_head * pInodeList;
|
+ struct task_struct * pEachTask;
|
+ struct fdtable * pFDT;
|
+ struct file * pFilp;
|
+ unsigned long flags;
|
+ int count = 0;
|
+ int tries;
|
+ int result;
|
+
|
+ // Should never happen, but check anyway
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "wrong device\n" );
|
+ return;
|
+ }
|
+
|
+ pDev->mbDeregisterQMIDevice = true;
|
+
|
+ // Release all clients
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+ while (pDev->mQMIDev.mpClientMemList != NULL)
|
+ {
|
+ u16 mClientID = pDev->mQMIDev.mpClientMemList->mClientID;
|
+ if (waitqueue_active(&pDev->mQMIDev.mpClientMemList->mWaitQueue)) {
|
+ DBG("WaitQueue 0x%04X\n", mClientID);
|
+ wake_up_interruptible_sync( &pDev->mQMIDev.mpClientMemList->mWaitQueue );
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ msleep(10);
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+ continue;
|
+ }
|
+
|
+ DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
|
+
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ ReleaseClientID( pDev, mClientID );
|
+ // NOTE: pDev->mQMIDev.mpClientMemList will
|
+ // be updated in ReleaseClientID()
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+ }
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Stop all reads
|
+ KillRead( pDev );
|
+
|
+ pDev->mbQMIValid = false;
|
+
|
+ if (pDev->mQMIDev.mbCdevIsInitialized == false)
|
+ {
|
+ return;
|
+ }
|
+
|
+ // Find each open file handle, and manually close it
|
+
|
+ // Generally there will only be only one inode, but more are possible
|
+ list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list )
|
+ {
|
+ // Get the inode
|
+ pOpenInode = container_of( pInodeList, struct inode, i_devices );
|
+ if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
|
+ {
|
+ // Look for this inode in each task
|
+
|
+ rcu_read_lock();
|
+ for_each_process( pEachTask )
|
+ {
|
+ task_lock(pEachTask);
|
+ if (pEachTask == NULL || pEachTask->files == NULL)
|
+ {
|
+ // Some tasks may not have files (e.g. Xsession)
|
+ task_unlock(pEachTask);
|
+ continue;
|
+ }
|
+ // For each file this task has open, check if it's referencing
|
+ // our inode.
|
+ spin_lock_irqsave( &pEachTask->files->file_lock, flags );
|
+ task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
|
+ pFDT = files_fdtable( pEachTask->files );
|
+ for (count = 0; count < pFDT->max_fds; count++)
|
+ {
|
+ pFilp = pFDT->fd[count];
|
+ if (pFilp != NULL && pFilp->f_dentry != NULL)
|
+ {
|
+ if (pFilp->f_dentry->d_inode == pOpenInode)
|
+ {
|
+ // Close this file handle
|
+ rcu_assign_pointer( pFDT->fd[count], NULL );
|
+ spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
|
+
|
+ DBG( "forcing close of open file handle\n" );
|
+ filp_close( pFilp, pEachTask->files );
|
+
|
+ spin_lock_irqsave( &pEachTask->files->file_lock, flags );
|
+ }
|
+ }
|
+ }
|
+ spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
|
+ }
|
+ rcu_read_unlock();
|
+ }
|
+ }
|
+
|
+ // Send SetControlLineState request (USB_CDC)
|
+ result = usb_control_msg( pDev->mpNetDev->udev,
|
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
|
+ SET_CONTROL_LINE_STATE_REQUEST,
|
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
|
+ 0, // DTR not present
|
+ /* USB interface number to receive control message */
|
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
|
+ NULL,
|
+ 0,
|
+ 100 );
|
+ if (result < 0)
|
+ {
|
+ DBG( "Bad SetControlLineState status %d\n", result );
|
+ }
|
+
|
+ // Remove device (so no more calls can be made by users)
|
+ if (IS_ERR( pDev->mQMIDev.mpDevClass ) == false)
|
+ {
|
+ device_destroy( pDev->mQMIDev.mpDevClass,
|
+ pDev->mQMIDev.mDevNum );
|
+ }
|
+
|
+ // Hold onto cdev memory location until everyone is through using it.
|
+ // Timeout after 30 seconds (10 ms interval). Timeout should never happen,
|
+ // but exists to prevent an infinate loop just in case.
|
+ for (tries = 0; tries < 30 * 100; tries++)
|
+ {
|
+ int ref = gobi_atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
|
+ if (ref > 1)
|
+ {
|
+ DBG( "cdev in use by %d tasks\n", ref - 1 );
|
+ msleep( 10 );
|
+ }
|
+ else
|
+ {
|
+ break;
|
+ }
|
+ }
|
+
|
+ cdev_del( &pDev->mQMIDev.mCdev );
|
+
|
+ unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
|
+
|
+ return;
|
+}
|
+
|
+/*=========================================================================*/
|
+// Driver level client management
|
+/*=========================================================================*/
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIReady (Public Method)
|
+
|
+DESCRIPTION:
|
+ Send QMI CTL GET VERSION INFO REQ and SET DATA FORMAT REQ
|
+ Wait for response or timeout
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ timeout [ I ] - Milliseconds to wait for response
|
+
|
+RETURN VALUE:
|
+ bool
|
+===========================================================================*/
|
+bool QMIReady(
|
+ sGobiUSBNet * pDev,
|
+ u16 timeout )
|
+{
|
+ int result;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+ struct semaphore readSem;
|
+ u16 curTime;
|
+ unsigned long flags;
|
+ u8 transactionID;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return false;
|
+ }
|
+
|
+ writeBufferSize = QMICTLReadyReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return false;
|
+ }
|
+
|
+ // An implimentation of down_timeout has not been agreed on,
|
+ // so it's been added and removed from the kernel several times.
|
+ // We're just going to ignore it and poll the semaphore.
|
+
|
+ // Send a write every 1000 ms and see if we get a response
|
+ for (curTime = 0; curTime < timeout; curTime += 1000)
|
+ {
|
+ // Start read
|
+ sema_init( &readSem, 0 );
|
+
|
+ transactionID = QMIXactionIDGet( pDev );
|
+
|
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
|
+ if (result != 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return false;
|
+ }
|
+
|
+ // Fill buffer
|
+ result = QMICTLReadyReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ transactionID );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return false;
|
+ }
|
+
|
+ // Disregard status. On errors, just try again
|
+ WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ QMICTL );
|
+
|
+ msleep( 1000 );
|
+ if (down_trylock( &readSem ) == 0)
|
+ {
|
+ // Enter critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Pop the read data
|
+ if (PopFromReadMemList( pDev,
|
+ QMICTL,
|
+ transactionID,
|
+ &pReadBuffer,
|
+ &readBufferSize ) == true)
|
+ {
|
+ // Success
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // We don't care about the result
|
+ kfree( pReadBuffer );
|
+
|
+ break;
|
+ }
|
+ else
|
+ {
|
+ // Read mismatch/failure, unlock and continue
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ }
|
+ }
|
+ else
|
+ {
|
+ // Enter critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ // Timeout, remove the async read
|
+ NotifyAndPopNotifyList( pDev, QMICTL, transactionID );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+ }
|
+ }
|
+
|
+ kfree( pWriteBuffer );
|
+
|
+ // Did we time out?
|
+ if (curTime >= timeout)
|
+ {
|
+ return false;
|
+ }
|
+
|
+ DBG( "QMI Ready after %u milliseconds\n", curTime );
|
+
|
+ // Success
|
+ return true;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDSCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ QMI WDS callback function
|
+ Update net stats or link state
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+ clientID [ I ] - Client ID
|
+ pData [ I ] - Callback data (unused)
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+void QMIWDSCallback(
|
+ sGobiUSBNet * pDev,
|
+ u16 clientID,
|
+ void * pData )
|
+{
|
+ bool bRet;
|
+ int result;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+
|
+#if 0
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
|
+ struct net_device_stats * pStats = &(pDev->mpNetDev->stats);
|
+#else
|
+ struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
|
+#endif
|
+#endif
|
+
|
+ u32 TXOk = (u32)-1;
|
+ u32 RXOk = (u32)-1;
|
+ u32 TXErr = (u32)-1;
|
+ u32 RXErr = (u32)-1;
|
+ u32 TXOfl = (u32)-1;
|
+ u32 RXOfl = (u32)-1;
|
+ u64 TXBytesOk = (u64)-1;
|
+ u64 RXBytesOk = (u64)-1;
|
+ bool bLinkState;
|
+ bool bReconfigure;
|
+ unsigned long flags;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return;
|
+ }
|
+
|
+ // Critical section
|
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ bRet = PopFromReadMemList( pDev,
|
+ clientID,
|
+ 0,
|
+ &pReadBuffer,
|
+ &readBufferSize );
|
+
|
+ // End critical section
|
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
|
+
|
+ if (bRet == false)
|
+ {
|
+ DBG( "WDS callback failed to get data\n" );
|
+ return;
|
+ }
|
+
|
+ // Default values
|
+ bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
|
+ bReconfigure = false;
|
+
|
+ result = QMIWDSEventResp( pReadBuffer,
|
+ readBufferSize,
|
+ &TXOk,
|
+ &RXOk,
|
+ &TXErr,
|
+ &RXErr,
|
+ &TXOfl,
|
+ &RXOfl,
|
+ &TXBytesOk,
|
+ &RXBytesOk,
|
+ &bLinkState,
|
+ &bReconfigure );
|
+ if (result < 0)
|
+ {
|
+ DBG( "bad WDS packet\n" );
|
+ }
|
+ else
|
+ {
|
+#if 0 //usbbet.c will do this job
|
+ // Fill in new values, ignore max values
|
+ if (TXOfl != (u32)-1)
|
+ {
|
+ pStats->tx_fifo_errors = TXOfl;
|
+ }
|
+
|
+ if (RXOfl != (u32)-1)
|
+ {
|
+ pStats->rx_fifo_errors = RXOfl;
|
+ }
|
+
|
+ if (TXErr != (u32)-1)
|
+ {
|
+ pStats->tx_errors = TXErr;
|
+ }
|
+
|
+ if (RXErr != (u32)-1)
|
+ {
|
+ pStats->rx_errors = RXErr;
|
+ }
|
+
|
+ if (TXOk != (u32)-1)
|
+ {
|
+ pStats->tx_packets = TXOk + pStats->tx_errors;
|
+ }
|
+
|
+ if (RXOk != (u32)-1)
|
+ {
|
+ pStats->rx_packets = RXOk + pStats->rx_errors;
|
+ }
|
+
|
+ if (TXBytesOk != (u64)-1)
|
+ {
|
+ pStats->tx_bytes = TXBytesOk;
|
+ }
|
+
|
+ if (RXBytesOk != (u64)-1)
|
+ {
|
+ pStats->rx_bytes = RXBytesOk;
|
+ }
|
+#endif
|
+
|
+ if (bReconfigure == true)
|
+ {
|
+ DBG( "Net device link reset\n" );
|
+ GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
|
+ GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
|
+ }
|
+ else
|
+ {
|
+ if (bLinkState == true)
|
+ {
|
+ if (GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
|
+ DBG( "Net device link is connected\n" );
|
+ GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
|
+ }
|
+ }
|
+ else
|
+ {
|
+ if (!GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
|
+ DBG( "Net device link is disconnected\n" );
|
+ GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
|
+ }
|
+ }
|
+ }
|
+ }
|
+
|
+ kfree( pReadBuffer );
|
+
|
+ // Setup next read
|
+ result = ReadAsync( pDev,
|
+ clientID,
|
+ 0,
|
+ QMIWDSCallback,
|
+ pData );
|
+ if (result != 0)
|
+ {
|
+ DBG( "unable to setup next async read\n" );
|
+ }
|
+
|
+ return;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ SetupQMIWDSCallback (Public Method)
|
+
|
+DESCRIPTION:
|
+ Request client and fire off reqests and start async read for
|
+ QMI WDS callback
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ int - 0 for success
|
+ Negative errno for failure
|
+===========================================================================*/
|
+int SetupQMIWDSCallback( sGobiUSBNet * pDev )
|
+{
|
+ int result;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ u16 WDSClientID;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = GetClientID( pDev, QMIWDS );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ WDSClientID = result;
|
+
|
+#if 0 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use quectel-CM, or cannot obtain IP by udhcpc
|
+ if (pDev->mbMdm9x07)
|
+ {
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+
|
+ writeBufferSize = QMIWDSSetQMUXBindMuxDataPortSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ result = QMIWDSSetQMUXBindMuxDataPortReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ 3 );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ WDSClientID );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ WDSClientID,
|
+ 3 );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ readBufferSize = result;
|
+
|
+ kfree( pReadBuffer );
|
+ }
|
+#endif
|
+
|
+ // QMI WDS Set Event Report
|
+ writeBufferSize = QMIWDSSetEventReportReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ result = QMIWDSSetEventReportReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ 1 );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ WDSClientID );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // QMI WDS Get PKG SRVC Status
|
+ writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ 2 );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ WDSClientID );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // Setup asnyc read callback
|
+ result = ReadAsync( pDev,
|
+ WDSClientID,
|
+ 0,
|
+ QMIWDSCallback,
|
+ NULL );
|
+ if (result != 0)
|
+ {
|
+ DBG( "unable to setup async read\n" );
|
+ return result;
|
+ }
|
+
|
+ // Send SetControlLineState request (USB_CDC)
|
+ // Required for Autoconnect
|
+ result = usb_control_msg( pDev->mpNetDev->udev,
|
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
|
+ SET_CONTROL_LINE_STATE_REQUEST,
|
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
|
+ CONTROL_DTR,
|
+ /* USB interface number to receive control message */
|
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
|
+ NULL,
|
+ 0,
|
+ 100 );
|
+ if (result < 0)
|
+ {
|
+ DBG( "Bad SetControlLineState status %d\n", result );
|
+ return result;
|
+ }
|
+
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIDMSGetMEID (Public Method)
|
+
|
+DESCRIPTION:
|
+ Register DMS client
|
+ send MEID req and parse response
|
+ Release DMS client
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+int QMIDMSGetMEID( sGobiUSBNet * pDev )
|
+{
|
+ int result;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+ u16 DMSClientID;
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = GetClientID( pDev, QMIDMS );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ DMSClientID = result;
|
+
|
+ // QMI DMS Get Serial numbers Req
|
+ writeBufferSize = QMIDMSGetMEIDReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ result = QMIDMSGetMEIDReq( pWriteBuffer,
|
+ writeBufferSize,
|
+ 1 );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ DMSClientID );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // QMI DMS Get Serial numbers Resp
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ DMSClientID,
|
+ 1 );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ readBufferSize = result;
|
+
|
+ result = QMIDMSGetMEIDResp( pReadBuffer,
|
+ readBufferSize,
|
+ &pDev->mMEID[0],
|
+ 14 );
|
+ kfree( pReadBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ DBG( "bad get MEID resp\n" );
|
+
|
+ // Non fatal error, device did not return any MEID
|
+ // Fill with 0's
|
+ memset( &pDev->mMEID[0], '0', 14 );
|
+ }
|
+
|
+ ReleaseClientID( pDev, DMSClientID );
|
+
|
+ // Success
|
+ return 0;
|
+}
|
+
|
+/*===========================================================================
|
+METHOD:
|
+ QMIWDASetDataFormat (Public Method)
|
+
|
+DESCRIPTION:
|
+ Register WDA client
|
+ send Data format request and parse response
|
+ Release WDA client
|
+
|
+PARAMETERS:
|
+ pDev [ I ] - Device specific memory
|
+
|
+RETURN VALUE:
|
+ None
|
+===========================================================================*/
|
+int QMIWDASetDataFormat( sGobiUSBNet * pDev )
|
+{
|
+ int result;
|
+ void * pWriteBuffer;
|
+ u16 writeBufferSize;
|
+ void * pReadBuffer;
|
+ u16 readBufferSize;
|
+ u16 WDAClientID;
|
+
|
+ DBG("\n");
|
+
|
+ if (IsDeviceValid( pDev ) == false)
|
+ {
|
+ DBG( "Invalid device\n" );
|
+ return -EFAULT;
|
+ }
|
+
|
+ result = GetClientID( pDev, QMIWDA );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ WDAClientID = result;
|
+
|
+ // QMI WDA Set Data Format Request
|
+ writeBufferSize = QMIWDASetDataFormatReqSize();
|
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
|
+ if (pWriteBuffer == NULL)
|
+ {
|
+ return -ENOMEM;
|
+ }
|
+
|
+ result = QMIWDASetDataFormatReq( pWriteBuffer,
|
+ writeBufferSize, pDev->mbRawIPMode,
|
+ 1 );
|
+ if (result < 0)
|
+ {
|
+ kfree( pWriteBuffer );
|
+ return result;
|
+ }
|
+
|
+ result = WriteSync( pDev,
|
+ pWriteBuffer,
|
+ writeBufferSize,
|
+ WDAClientID );
|
+ kfree( pWriteBuffer );
|
+
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+
|
+ // QMI DMS Get Serial numbers Resp
|
+ result = ReadSync( pDev,
|
+ &pReadBuffer,
|
+ WDAClientID,
|
+ 1 );
|
+ if (result < 0)
|
+ {
|
+ return result;
|
+ }
|
+ readBufferSize = result;
|
+
|
+ result = QMIWDASetDataFormatResp( pReadBuffer,
|
+ readBufferSize, pDev->mbRawIPMode );
|
+
|
+ kfree( pReadBuffer );
|
+
|
+#if 1 //def DATA_MODE_RP
|
+ pDev->mbRawIPMode = (result == 2); /* LinkProt: 0x1 - ETH; 0x2 - rawIP */
|
+ if (pDev->mbRawIPMode) {
|
+ pDev->mpNetDev->net->flags |= IFF_NOARP;
|
+ }
|
+#endif
|
+
|
+ if (result < 0)
|
+ {
|
+ DBG( "Data Format Cannot be set\n" );
|
+ }
|
+
|
+ ReleaseClientID( pDev, WDAClientID );
|
+
|
+ // Success
|
+ return 0;
|
+}
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/ec20/QMIDevice.h linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.h
|
--- linux-at91/drivers/net/usb/ec20/QMIDevice.h 1970-01-01 08:00:00.000000000 +0800
|
+++ linux-at91-loragw/drivers/net/usb/ec20/QMIDevice.h 2024-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 <linux/etherdevice.h>
|
+#include <linux/ethtool.h>
|
+#include <linux/mii.h>
|
+#include <linux/usb.h>
|
+#include <linux/version.h>
|
+#include <linux/cdev.h>
|
+#include <linux/kthread.h>
|
+#include <linux/poll.h>
|
+#include <linux/completion.h>
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 ))
|
+static inline void skb_reset_mac_header(struct sk_buff *skb)
|
+{
|
+ skb->mac.raw = skb->data;
|
+}
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
|
+#define bool u8
|
+#ifndef URB_FREE_BUFFER
|
+#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self
|
+#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */
|
+#endif
|
+
|
+/**
|
+ * usb_endpoint_type - get the endpoint's transfer type
|
+ * @epd: endpoint to be checked
|
+ *
|
+ * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according
|
+ * to @epd's transfer type.
|
+ */
|
+static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd)
|
+{
|
+ return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
+}
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 ))
|
+/**
|
+ * usb_endpoint_dir_in - check if the endpoint has IN direction
|
+ * @epd: endpoint to be checked
|
+ *
|
+ * Returns true if the endpoint is of type IN, otherwise it returns false.
|
+ */
|
+static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd)
|
+{
|
+ return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN);
|
+}
|
+
|
+/**
|
+ * usb_endpoint_dir_out - check if the endpoint has OUT direction
|
+ * @epd: endpoint to be checked
|
+ *
|
+ * Returns true if the endpoint is of type OUT, otherwise it returns false.
|
+ */
|
+static inline int usb_endpoint_dir_out(
|
+ const struct usb_endpoint_descriptor *epd)
|
+{
|
+ return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
|
+}
|
+
|
+/**
|
+ * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type
|
+ * @epd: endpoint to be checked
|
+ *
|
+ * Returns true if the endpoint is of type interrupt, otherwise it returns
|
+ * false.
|
+ */
|
+static inline int usb_endpoint_xfer_int(
|
+ const struct usb_endpoint_descriptor *epd)
|
+{
|
+ return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
|
+ USB_ENDPOINT_XFER_INT);
|
+}
|
+
|
+static inline int usb_autopm_set_interface(struct usb_interface *intf)
|
+{ return 0; }
|
+
|
+static inline int usb_autopm_get_interface(struct usb_interface *intf)
|
+{ return 0; }
|
+
|
+static inline int usb_autopm_get_interface_async(struct usb_interface *intf)
|
+{ return 0; }
|
+
|
+static inline void usb_autopm_put_interface(struct usb_interface *intf)
|
+{ }
|
+static inline void usb_autopm_put_interface_async(struct usb_interface *intf)
|
+{ }
|
+static inline void usb_autopm_enable(struct usb_interface *intf)
|
+{ }
|
+static inline void usb_autopm_disable(struct usb_interface *intf)
|
+{ }
|
+static inline void usb_mark_last_busy(struct usb_device *udev)
|
+{ }
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
|
+ #include "usbnet.h"
|
+#else
|
+ #include <linux/usb/usbnet.h>
|
+#endif
|
+
|
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 ))
|
+ #include <linux/fdtable.h>
|
+#else
|
+ #include <linux/file.h>
|
+#endif
|
+
|
+// Used in recursion, defined later below
|
+struct sGobiUSBNet;
|
+
|
+/*=========================================================================*/
|
+// Struct sReadMemList
|
+//
|
+// Structure that defines an entry in a Read Memory linked list
|
+/*=========================================================================*/
|
+typedef struct sReadMemList
|
+{
|
+ /* Data buffer */
|
+ void * mpData;
|
+
|
+ /* Transaction ID */
|
+ u16 mTransactionID;
|
+
|
+ /* Size of data buffer */
|
+ u16 mDataSize;
|
+
|
+ /* Next entry in linked list */
|
+ struct sReadMemList * mpNext;
|
+
|
+} sReadMemList;
|
+
|
+/*=========================================================================*/
|
+// Struct sNotifyList
|
+//
|
+// Structure that defines an entry in a Notification linked list
|
+/*=========================================================================*/
|
+typedef struct sNotifyList
|
+{
|
+ /* Function to be run when data becomes available */
|
+ void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *);
|
+
|
+ /* Transaction ID */
|
+ u16 mTransactionID;
|
+
|
+ /* Data to provide as parameter to mpNotifyFunct */
|
+ void * mpData;
|
+
|
+ /* Next entry in linked list */
|
+ struct sNotifyList * mpNext;
|
+
|
+} sNotifyList;
|
+
|
+/*=========================================================================*/
|
+// Struct sURBList
|
+//
|
+// Structure that defines an entry in a URB linked list
|
+/*=========================================================================*/
|
+typedef struct sURBList
|
+{
|
+ /* The current URB */
|
+ struct urb * mpURB;
|
+
|
+ /* Next entry in linked list */
|
+ struct sURBList * mpNext;
|
+
|
+} sURBList;
|
+
|
+/*=========================================================================*/
|
+// Struct sClientMemList
|
+//
|
+// Structure that defines an entry in a Client Memory linked list
|
+// Stores data specific to a Service Type and Client ID
|
+/*=========================================================================*/
|
+typedef struct sClientMemList
|
+{
|
+ /* Client ID for this Client */
|
+ u16 mClientID;
|
+
|
+ /* Linked list of Read entries */
|
+ /* Stores data read from device before sending to client */
|
+ sReadMemList * mpList;
|
+
|
+ /* Linked list of Notification entries */
|
+ /* Stores notification functions to be run as data becomes
|
+ available or the device is removed */
|
+ sNotifyList * mpReadNotifyList;
|
+
|
+ /* Linked list of URB entries */
|
+ /* Stores pointers to outstanding URBs which need canceled
|
+ when the client is deregistered or the device is removed */
|
+ sURBList * mpURBList;
|
+
|
+ /* Next entry in linked list */
|
+ struct sClientMemList * mpNext;
|
+
|
+ /* Wait queue object for poll() */
|
+ wait_queue_head_t mWaitQueue;
|
+
|
+} sClientMemList;
|
+
|
+/*=========================================================================*/
|
+// Struct sURBSetupPacket
|
+//
|
+// Structure that defines a USB Setup packet for Control URBs
|
+// Taken from USB CDC specifications
|
+/*=========================================================================*/
|
+typedef struct sURBSetupPacket
|
+{
|
+ /* Request type */
|
+ u8 mRequestType;
|
+
|
+ /* Request code */
|
+ u8 mRequestCode;
|
+
|
+ /* Value */
|
+ u16 mValue;
|
+
|
+ /* Index */
|
+ u16 mIndex;
|
+
|
+ /* Length of Control URB */
|
+ u16 mLength;
|
+
|
+} sURBSetupPacket;
|
+
|
+// Common value for sURBSetupPacket.mLength
|
+#define DEFAULT_READ_URB_LENGTH 0x1000
|
+
|
+#ifdef CONFIG_PM
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+/*=========================================================================*/
|
+// Struct sAutoPM
|
+//
|
+// Structure used to manage AutoPM thread which determines whether the
|
+// device is in use or may enter autosuspend. Also submits net
|
+// transmissions asynchronously.
|
+/*=========================================================================*/
|
+typedef struct sAutoPM
|
+{
|
+ /* Thread for atomic autopm function */
|
+ struct task_struct * mpThread;
|
+
|
+ /* Signal for completion when it's time for the thread to work */
|
+ struct completion mThreadDoWork;
|
+
|
+ /* Time to exit? */
|
+ bool mbExit;
|
+
|
+ /* List of URB's queued to be sent to the device */
|
+ sURBList * mpURBList;
|
+
|
+ /* URB list lock (for adding and removing elements) */
|
+ spinlock_t mURBListLock;
|
+
|
+ /* Length of the URB list */
|
+ atomic_t mURBListLen;
|
+
|
+ /* Active URB */
|
+ struct urb * mpActiveURB;
|
+
|
+ /* Active URB lock (for adding and removing elements) */
|
+ spinlock_t mActiveURBLock;
|
+
|
+ /* Duplicate pointer to USB device interface */
|
+ struct usb_interface * mpIntf;
|
+
|
+} sAutoPM;
|
+#endif
|
+#endif /* CONFIG_PM */
|
+
|
+/*=========================================================================*/
|
+// Struct sQMIDev
|
+//
|
+// Structure that defines the data for the QMI device
|
+/*=========================================================================*/
|
+typedef struct sQMIDev
|
+{
|
+ /* Device number */
|
+ dev_t mDevNum;
|
+
|
+ /* Device class */
|
+ struct class * mpDevClass;
|
+
|
+ /* cdev struct */
|
+ struct cdev mCdev;
|
+
|
+ /* is mCdev initialized? */
|
+ bool mbCdevIsInitialized;
|
+
|
+ /* Pointer to read URB */
|
+ struct urb * mpReadURB;
|
+
|
+//#define READ_QMI_URB_ERROR
|
+#ifdef READ_QMI_URB_ERROR
|
+ struct timer_list mReadUrbTimer;
|
+#endif
|
+
|
+ /* Read setup packet */
|
+ sURBSetupPacket * mpReadSetupPacket;
|
+
|
+ /* Read buffer attached to current read URB */
|
+ void * mpReadBuffer;
|
+
|
+ /* Inturrupt URB */
|
+ /* Used to asynchronously notify when read data is available */
|
+ struct urb * mpIntURB;
|
+
|
+ /* Buffer used by Inturrupt URB */
|
+ void * mpIntBuffer;
|
+
|
+ /* Pointer to memory linked list for all clients */
|
+ sClientMemList * mpClientMemList;
|
+
|
+ /* Spinlock for client Memory entries */
|
+ spinlock_t mClientMemLock;
|
+
|
+ /* Transaction ID associated with QMICTL "client" */
|
+ atomic_t mQMICTLTransactionID;
|
+
|
+} sQMIDev;
|
+
|
+/*=========================================================================*/
|
+// Struct sGobiUSBNet
|
+//
|
+// Structure that defines the data associated with the Qualcomm USB device
|
+/*=========================================================================*/
|
+typedef struct sGobiUSBNet
|
+{
|
+ atomic_t refcount;
|
+
|
+ /* Net device structure */
|
+ struct usbnet * mpNetDev;
|
+
|
+#if 1 //def DATA_MODE_RP
|
+ bool mbMdm9x07;
|
+ /* QMI "device" work in IP Mode or ETH Mode */
|
+ bool mbRawIPMode;
|
+#ifdef CONFIG_BRIDGE
|
+ unsigned char mHostMAC[6];
|
+#endif
|
+#endif
|
+
|
+ struct completion mQMIReadyCompletion;
|
+ bool mbQMIReady;
|
+
|
+ /* Usb device interface */
|
+ struct usb_interface * mpIntf;
|
+
|
+ /* Pointers to usbnet_open and usbnet_stop functions */
|
+ int (* mpUSBNetOpen)(struct net_device *);
|
+ int (* mpUSBNetStop)(struct net_device *);
|
+
|
+ /* Reason(s) why interface is down */
|
+ /* Used by Gobi*DownReason */
|
+ unsigned long mDownReason;
|
+#define NO_NDIS_CONNECTION 0
|
+#define CDC_CONNECTION_SPEED 1
|
+#define DRIVER_SUSPENDED 2
|
+#define NET_IFACE_STOPPED 3
|
+
|
+ /* QMI "device" status */
|
+ bool mbQMIValid;
|
+
|
+ bool mbDeregisterQMIDevice;
|
+
|
+ /* QMI "device" memory */
|
+ sQMIDev mQMIDev;
|
+
|
+ /* Device MEID */
|
+ char mMEID[14];
|
+
|
+#ifdef CONFIG_PM
|
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
|
+ /* AutoPM thread */
|
+ sAutoPM mAutoPM;
|
+#endif
|
+#endif /* CONFIG_PM */
|
+} sGobiUSBNet;
|
+
|
+/*=========================================================================*/
|
+// Struct sQMIFilpStorage
|
+//
|
+// Structure that defines the storage each file handle contains
|
+// Relates the file handle to a client
|
+/*=========================================================================*/
|
+typedef struct sQMIFilpStorage
|
+{
|
+ /* Client ID */
|
+ u16 mClientID;
|
+
|
+ /* Device pointer */
|
+ sGobiUSBNet * mpDev;
|
+
|
+} sQMIFilpStorage;
|
+
|
diff -Nuar -x include-prefixes -x .gitignore linux-at91/drivers/net/usb/Kconfig linux-at91-loragw/drivers/net/usb/Kconfig
|
--- linux-at91/drivers/net/usb/Kconfig 2019-07-10 18:07:41.000000000 +0800
|
+++ linux-at91-loragw/drivers/net/usb/Kconfig 2024-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() */
|